Customizing Flutter Engine Build and Release Process
The article explains how to streamline Flutter Engine customization by rewriting build and release scripts—linking engine versions to Flutter tags, using gclient, gn, and ninja to fetch dependencies, generate Ninja files, compile, debug locally, package the framework, and automate publishing with version‑locked .gclient templates, enabling one‑click builds and future storage‑mirroring.
When customizing the Flutter Engine, a clear understanding of its compilation, packaging, and release workflow is essential. Existing scripts were fragmented, hard to maintain, and did not support out‑of‑the‑box usage, especially after the Engine upgrade from 1.5.4 to 1.9.1.
To address these issues the scripts were rewritten to simplify the entire process. The Engine version is tied to the Flutter Framework tag via engine.version, and the DEPS file lists all dependencies such as Skia and BoringSSL that may need custom modifications.
The required toolchain consists of:
gclient – source management (https://www.chromium.org/developers/how-tos/depottools/gclient)
gn – generates Ninja build files (https://gn.googlesource.com/gn)
ninja – performs the actual compilation (https://ninja-build.org/)
Typical workflow:
Create a working directory (e.g., engine) and add a .gclient configuration file.
Run gclient sync to fetch the Engine source and its dependencies.
Generate build files and compile. Example for iOS debug simulation:
./flutter/tools/gn --ios --simulator --unoptimized # generate Ninja files ninja -C out/ios_debug_sim_unopt && ninja -C out/host_debug_unoptOther build variants use --runtime-mode=debug|release|profile and --ios-cpu=arm|arm64 flags.
Debugging can be done with a local engine:
flutter run --local-engine-src-path=/Users/Luke/Projects/engine/src --local-engine=ios_debug_sim_unoptBreakpoints can be set in Xcode or via lldb commands such as br set -f FlutterViewController.mm -l 123.
For publishing, a dedicated Git repository holds the release scripts and a .gclient template per Engine version. Example template (simplified):
solutions = [{
"managed": False,
"name": "src/flutter",
"url": "[email protected]:xxxx/[email protected]",
"custom_deps": {
"src/third_party/skia": "[email protected]:xxxx/[email protected]",
"src/third_party/boringssl/src": "[email protected]:xxxx/[email protected]"
},
"deps_file": "DEPS",
"safesync_url": ""
}],After compilation, the Engine artifacts are assembled and copied into the Flutter cache:
cp -rf src/out/ios_debug/Flutter.framework tmp/ lipo -create -output tmp/Flutter.framework/Flutter \
src/out/ios_debug/Flutter.framework/Flutter \
src/out/ios_debug_arm/Flutter.framework/Flutter \
src/out/ios_debug_sim/Flutter.framework/Flutter zip -r Flutter.framework.zip tmp/Flutter.frameworkFinally the framework and the gen_snapshot binaries are placed into ${flutter_path}/bin/cache/artifacts/engine/ios/ and the temporary files are removed.
Benefits of this approach include one‑click builds, version‑locked dependencies via the .gclient template, and clear separation between common utilities and version‑specific scripts.
Future work aims to replace the manual cache copy with a custom Flutter infra mirror by setting the FLUTTER_STORAGE_BASE_URL environment variable.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
