Code signing metadata
This covers the process of how to add / update code signing metadata of flutter engine binaries.
Flutter engine binaries are built with GN and ninja, referencing pre-defined configurations such as ci/builders JSON files. During flutter releases, engineers need to code sign mac engine binaries to assure users that they come from a known source, have not been tampered with, and should not be quarantined by Gatekeepers.
Each of the Flutter engine binaries are either code signed with entitlements, or code signed without entitlements. (An entitlement, along with information from the developer account, grant particular permissions to binaries, such as capability to access the user's home automation network.) For example, impellerc is code signed with flutter entitlements, whereas .dylib files are usually code signed without entitlements.
- BUILD.gn files: files that include build rules of GN targets. An example is the BUILD.gn file of flutter engine.
- leaf node of an engine binary: the minimal gn target that could produce such an engine binary. That is, this target does not have any dependencies on other gn targets that could build this engine binary.
- dependencies: Every gn target could have dependencies on other gn targets.
The dependency of a gn target is defined in the
deps
field of the target's build rule.
Generally, there are two ways to generate an engine binary:
-
Through build rules defined in BUILD.gn files.
-
Through global generator scripts. (these scripts are normally .py files)
To distinguish between the two, an engine binary is built through global
generator if it is listed in the archives
-> destination
field of the
builder JSON
(mac_ios_engine.json
or
mac_host_engine.json).
For example, darwin-x64/FlutterEmbedder.framework.zip
. Whereas binaries built
with BUILD.gn files are listed among the builds
field of the JSON file. For
example, darwin-x64/artifacts.zip
. We will provide examples for both
scenarios.
-
Find the leaf node where the target engine binary is built. To do so, Recursively trace the
deps
field of the engine artifact. The paths indeps
field of the GN target correspond to the paths of other GN targets that are dependencies of the current GN target. -
Add / Update the
metadata
field of the leaf node. For a new engine binary:2.1 if it should be code signed with entitlements, add [the name of the engine binary] to the
entitlement_file_path
field inmetadata
.2.2 if the binary shouldn't be code signed with entitlements, add [the name of the engine binary] to the
without_entitlement_file_path
field inmetadata
. -
If a
entitlement_file_path
or awithout_entitlement_file_path
field does not exist:note: this step is only needed if the target includes solely binaries that have never been code signed before. This step also requires some background on flutter engine and gn build rules.
Add a
metadata
field in the gn target of the leaf node, and put the name of the binary in this field. e.g.metadata = { entitlement_file_path = [ "libtessellator.dylib" ] }
In the same file that produces the engine artifact(zip file), add a build rule to collect the data keys. e.g.
generated_file("artifacts_entitlement_config") { outputs = [ "$target_gen_dir/entitlements.txt" ] data_keys = [ "entitlement_file_path" ] deps = [ "//flutter/lib/snapshot:generate_snapshot_bin" ] if (flutter_runtime_mode == "debug") { deps += [ "//flutter/impeller/compiler:impellerc", "//flutter/impeller/tessellator:tessellator_shared", "//flutter/shell/testing:testing", "//flutter/tools/path_ops:path_ops", ] } }
Finally, embed the file with collected data keys in the zip artifact. e.g.
if (host_os == "mac") { deps += [ ":artifacts_entitlement_config" ] files += [ { source = "$target_gen_dir/entitlements.txt" destination = "entitlements.txt" }, ] }
Suppose impellerc is a binary that exist in a zip bundle called artifacts.zip. Then impellerc is the name of the binary, and artifacts.zip is the flutter engine artifact.
-
Following step 1, the
deps
field of the GN target of artifacts.zip includes the path of impeller dependency://flutter/impeller/compiler:impellerc
. Following this path, we locate the GN file atflutter/impeller/compiler/BUILD.gn
, and find the leaf node that builds impellerc:impeller_component("impellerc")
. -
Following step 2, since
impellerc
should be code signed with entitlements, we go to themetadata
field of the impellerc target, and add the nameimpellerc
to theentitlement_file_path
array inside themetadata
field.
You can reference the BUILD.gn file of impellerc.
-
Find the generator script path listed under
generators
->tasks
->script
of the ci/builder JSON files (mac_ios_engine.json or mac_host_engine.json).The generator script related to iOS is located at
sky/tools/create_full_ios_framework.py
, and generator script related to macOS is located atsky/tools/create_macos_framework.py
. -
Add / Update the variables ending with
with_entitlements
/without_entitlements
suffix from the generator script you found in step one.As an example, you can find variables
ios_file_without_entitlements
andios_file_with_entitlements
in sky/tools/create_full_ios_framework.py; and find variablesfilepath_without_entitlements
andfilepath_with_entitlements
in sky/tools/create_macos_framework.py2.1 if the binary should be code signed with entitlements, add [the name of the binary] to the variable name with the
with_entitlements
suffix. (ios_file_with_entitlements
orfilepath_with_entitlements
depending on which script)2.2 if the binary shouldn't be code signed with entitlements, add [the name of the binary] to the variable name with the
without_entitlements
suffix.
Suppose Flutter.xcframework/ios-arm64/Flutter.framework/Flutter
is a binary
that exist in a zip bundle called ios/artifacts.zip
.
-
Following step 1, in mac_ios_engine.json, it builds the artifact with the
flutter/sky/tools/create_full_ios_framework.py
script. -
Following step 2, since
Flutter.xcframework/ios-arm64/Flutter.framework/Flutter
shouldn't be code signed with entitlements, we add the binary nameFlutter.xcframework/ios-arm64/Flutter.framework/Flutter
to theios_file_without_entitlements
variable.
You can reference the generator script create_full_ios_framework.py.
The code signing functionality is implemented as a recipe module in flutter recipes. Therefore it can also be used to code sign arbitrary flutter artifacts built through recipe, for example, flutter iOS usb dependencies.
To code sign, after the artifacts are built, pass the file paths into the code signing recipe module and invoke the function. An example is how engine V2 invokes the code signing recipe module.
- Home of the Wiki
- Roadmap
- API Reference (stable)
- API Reference (main)
- Glossary
- Contributor Guide
- Chat on Discord
- Design documents
- Code of Conduct
- Issue triage reports (latest)
- Our Values
- Tree hygiene
- Issue hygiene and Triage
- Style guide for Flutter repo
- Project teams
- Contributor access
- What should I work on?
- Popular issues
- Running and writing tests
- Release process
- Flutter Framework Gardener Rotation
- Rolling Dart
- Manual Engine Roll with Breaking Commits
- Updating Material Design Fonts & Icons
- Postmortems and Retrospectives
- Hotfix Documentation Best Practices
- In case of emergency
- Landing Changes With Autosubmit
- Setting up the Framework development environment
- The Framework architecture
- API Docs code block generation
- Running examples
- Using the Dart analyzer
- The flutter run variants
- Test coverage for package:flutter
- Writing a golden-file test for package:flutter
- Managing template image assets
- Setting up the Engine development environment
- Compiling the engine
- Debugging the engine
- Using Sanitizers with the Flutter Engine
- Testing the engine
- The Engine architecture
- Flutter's modes
- Crashes
- more...
- Setting up the Packages development environment
- Plugins and Packages repository structure
- Contributing to Plugins and Packages
- Understanding Packages tests
- Plugin Tests
- Releasing a Plugin or Package
- more...