flutter/flutter
github.com/flutter/flutter ↗ · 4000 files · 32.1 MB · scan capped
[![Discord badge][]][Discord instructions] [![Twitter handle][]][Twitter badge] [![BlueSky badge][]][BlueSky handle] Flutter is Google's SDK for crafting beautiful, fast user experiences for mobile, web, and desktop from a single codebase.
Overview
flutter/flutter is the monorepo for the Flutter SDK. It contains the Dart framework (packages/flutter), the `flutter` CLI tool (packages/flutter_tools) that users invoke through the bin/flutter shell script, the merged C++ engine source (engine/src/flutter) built with GN/Ninja and pinned third-party deps in DEPS, and a large test/CI estate (dev/, .ci.yaml, .github/workflows). The tool bootstraps itself by downloading a Dart SDK and compiling an app-jit snapshot before dispatching to ~35 subcommands; the engine exposes a stable C embedder API plus platform embedders for Android, iOS/macOS, Windows, Linux, and Fuchsia.
System map
The repo is a multi-language monorepo: shell entrypoints (bin/) bootstrap a Dart CLI (packages/flutter_tools), the Dart framework (packages/flutter) is the runtime library apps import, and the engine C++ code (engine/src/flutter) is built with GN and pinned external deps via DEPS. Runtime boundaries are: user shell -> flutter tool process -> device/emulator process running the engine -> Dart isolate running dart:ui + framework. Storage is the local bin/cache directory plus pub caches; external integrations are Google Cloud Storage artifact downloads, pub.dev, Gradle/Xcode toolchains, and the VM service protocol. CI is driven by LUCI via .ci.yaml which defers to dev/bots/test.dart shards; GitHub Actions only runs auxiliary checks.
- bin/flutter resolves its own path through symlinks via follow_links(), sources bin/internal/shared.sh, and calls shared::execute(); on Windows-like shells (MINGW/CYGWIN/MSYS) it execs flutter.bat instead.
- shared::execute() in shared.sh computes FLUTTER_ROOT, runs update_engine_version.sh, downloads the Dart SDK via bin/internal/update_dart_sdk.sh when the snapshot stamp is stale, compiles bin/cache/flutter_tools.snapshot with `dart --snapshot-kind=app-jit`, then execs the snapshot.
- Snapshot invalidation is keyed on `git rev-parse HEAD` + FLUTTER_TOOL_ARGS stored in flutter_tools.stamp, and on pubspec.yaml being newer than pubspec.lock; updates are serialized with a flock/shlock/mkdir lock on fd 7 at bin/cache/.upgrade_lock.
- The Dart-side tool entrypoint is packages/flutter_tools/bin/flutter_tools.dart, which just calls executable.main() in lib/executable.dart; main() builds a context overrides map and the command list via generateCommands(), then calls runner.run().
- runner.run() wraps execution in runInContext and runZonedGuarded, bootstraps ToolDependencies from ~35 globals (fs, platform, processManager, xcode, androidSdk, cache, analytics...), constructs FlutterCommandRunner, registers commands, and funnels all errors through _handleToolError.
- The repo is a pub workspace: root pubspec.yaml lists ~70 workspace members spanning packages/*, dev/*, and examples/*, so a single pub get resolves the whole tree.
- The engine lives in-repo under engine/src/flutter with a GN build (engine/src/flutter/BUILD.gn); third-party revisions (dart_revision, skia_revision, clang_version, reclient) are pinned in DEPS and synced with gclient.
- The Dart<->C++ runtime boundary is dart:ui in engine/src/flutter/lib/ui: functions in hooks.dart are annotated @pragma('vm:entry-point') and are invoked directly by the engine (e.g. _runMain, _addView) to deliver platform events into PlatformDispatcher.
- The embedder boundary is a stable C API in engine/src/flutter/shell/platform/embedder/embedder.h, with per-platform embedders under shell/platform/{android,darwin,windows,linux,fuchsia}; Android's embedder is the Java io.flutter.embedding.engine.FlutterEngine.
- Prebuilt engine artifacts are downloaded by packages/flutter_tools/lib/src/cache.dart from https://storage.googleapis.com (realm- and FLUTTER_STORAGE_BASE_URL-adjustable), organized by DevelopmentArtifact values gated behind Feature flags, and stamped under bin/cache.
- CI source of truth is .ci.yaml (~7800 lines) consumed by LUCI/Cocoon; the flutter_drone recipe defers to dev/bots/test.dart with the shard key; GitHub workflows like tool-test-general.yml only run focused checks (e.g. SHARD=tool_tests SUBSHARD=general).
- Physical-device end-to-end testing runs through dev/devicelab with its own task runner (dev/devicelab/bin/run.dart) and task library.
Feature catalog
User-facing surface is dominated by the `flutter` CLI: generateCommands() registers ~35 FlutterCommand subclasses covering the whole app lifecycle (create, run, build, test, analyze, doctor, drive, install, screenshot, symbolize, upgrade/downgrade, channel, precache, clean, config, devices, emulators, logs, attach, daemon, debug-adapter, widget-preview, shell-completion, gen-l10n, assemble, plus hidden dev commands like update-packages and ide-config). Build subcommands fan out per platform (apk, appbundle, aar, ios, ios-framework, macos, macos-framework, linux, windows, web, swift-package). The framework packages provide the runtime libraries (material, cupertino, widgets, services, etc.) and companion packages (flutter_test, flutter_driver, integration_test, flutter_goldens, flutter_localizations, flutter_web_plugins, fuchsia_remote_debug_protocol).
- generateCommands() in executable.dart is the full CLI command table: AnalyzeCommand, AssembleCommand, AttachCommand, BuildCommand, ChannelCommand, CleanCommand, ConfigCommand, CustomDevicesCommand, CreateCommand, DaemonCommand, DebugAdapterCommand, DevicesCommand, DoctorCommand, DowngradeCommand, DriveCommand, EmulatorsCommand, GenerateCommand, GenerateLocalizationsCommand, InstallCommand, LogsCommand, PackagesCommand, PrecacheCommand, RunCommand, ScreenshotCommand, ShellCompletionCommand, TestCommand, WidgetPreviewCommand, UpgradeCommand, SymbolizeCommand, IdeConfigCommand, UpdatePackagesCommand.
- RunCommandBase (shared by `flutter run` and `flutter drive`) exposes dozens of debugging flags: build modes, --dart-define, flavors, DDS options, DevTools options, trace-* profilers, --enable-impeller, --wasm, web renderer selection, wireless device refresh, and start-paused.
- `flutter run` picks HotRunner when shouldUseHotMode(buildInfo) is true (and not web), otherwise ColdRunner; the choice determines whether hot reload/restart keybindings are offered.
- `flutter test` is implemented by TestCommand (commands/test.dart) and runs tests through the flutter_tester device or Chrome for web tests; it can build native assets via TestCompilerNativeAssetsBuilder.
- `flutter doctor` runs a list of DoctorValidators (android_workflow, xcode, etc.) and prints diagnostics; DoctorCommand receives the shared globals.doctor singleton until migration to DI completes.
- `flutter daemon` exposes a JSON-over-stdio protocol for IDEs: messages are bracketed [json] frames parsed into DaemonMessage/DaemonEventData, support binary payloads via _binaryLength, and dispatch to registered Domain handlers (device, app, devtools, emulator, daemon domains).
- `flutter create` scaffolds projects from Mustache templates in packages/flutter_tools/templates (app, module, package, plugin, add_to_app, package_ffi, etc.) rendered via MustacheTemplateRenderer.
- `flutter build` is a BuildCommand umbrella whose subcommands are defined in commands/build_*.dart; Android builds go through AndroidGradleBuilder (android/gradle.dart), Apple builds through Xcode wrappers, web through Dart->JS/Wasm compilers.
- Feature flags gate platform support: FeatureFlags.allFeatures lists ~21 flags (web, linux/macos/windows desktop, android, ios, fuchsia, custom devices, native assets, swift package manager, windowing, lldb debugging, UIScene migration, riscv64, hcpp, tool extensions) toggled via `flutter config` and Feature.configSetting.
- `flutter precache` downloads DevelopmentArtifacts (android_gen_snapshot, android_maven, ios, web, macos, windows, linux, fuchsia, flutter_runner, universal, informative) into bin/cache/artifacts.
- packages/flutter is the framework library exporting widgets/material/cupertino/services/rendering/etc.; packages/flutter_test provides the test harness, flutter_driver and integration_test drive apps end-to-end, flutter_goldens compares golden images against Skia Gold.
- Hidden/dev-only commands include AssembleCommand (direct build-system target invocation used by Xcode/Gradle backends), UpdatePackagesCommand, IdeConfigCommand, DebugAdapterCommand (DAP server for editors), and ShellCompletionCommand.
Data flow
Tool data flow: argv -> executable.main -> FlutterCommandRunner (args package) -> FlutterCommand.verifyThenRun -> runCommand; all services resolve through an AppContext DI layer fed by globals.dart and the overrides map in executable.main. Runtime data flow: `flutter run` compiles Dart to kernel via ResidentCompiler, syncs sources+assets to the on-device DevFS over HTTP, and talks to the Dart VM service (vm_service package) for reload/attach; hot restart alternates a `.swap` dill name to dodge memory-mapped files. Framework data flow: runApp mounts the widget tree, drawFrame pumps build->layout->paint->composite into a Scene sent to the engine; MethodChannel calls serialize via codecs over BinaryMessenger into dart:ui's PlatformDispatcher.sendPlatformMessage, then through the engine's platform message handler to the embedder.
- Argument parsing is two-stage: findCommandName() in executable.dart does a throwaway parse to locate the command (handling -h/-? //? help idioms), then FlutterCommandRunner's real ArgParser (allowTrailingOptions: false) dispatches to the FlutterCommand.
- FlutterDevice.create() wires the per-device compile pipeline: a DevelopmentShaderCompiler wrapping ShaderCompiler for Impeller shaders, and a ResidentCompiler (via residentCompilerFactory) for incremental Dart kernel generation targeting device.targetPlatform.
- DevFS (devfs.dart) is the transport for hot development: it uploads compiled kernel files and asset diffs to a per-app filesystem served by the VM service on the device.
- HotRunner._reloadSources() orchestrates reload: projectFileInvalidator computes changed files, ResidentCompiler.recompile emits a partial dill, devFS pushes it, _reloadSourcesService calls VM reloadSources, then _reassembleHelper triggers WidgetsBinding reassemble in the app.
- Hot restart rewrites main.dart.dill into the device DevFS under alternating names (with/without .swap) because the previous dill may still be memory-mapped by the Windows embedder.
- The BuildSystem executes a Target DAG where each Target declares Source-pattern inputs/outputs; FileStore hashes file contents (md5/timestamp) and stamp files record inputs+outputs per run so unchanged targets are skipped; work is pooled at kMaxOpenFiles=64 concurrent file ops.
- App startup in the framework: runApp(app) -> WidgetsFlutterBinding.ensureInitialized() -> binding.wrapWithDefaultView(app) -> scheduleAttachRootWidget -> scheduleWarmUpFrame; frames then flow through handleBeginFrame/handleDrawFrame (documented 10-phase pipeline) -> drawFrame -> scene handed to the engine.
- Platform channel flow: MethodChannel._invokeMethod encodes calls with a MethodCodec, sends through binaryMessenger -> dart:ui PlatformDispatcher.sendPlatformMessage -> engine.cc's platform message handling -> the embedder's registered channel handler; built-in channels are named in engine.cc (flutter/assets, flutter/lifecycle, flutter/navigation, flutter/localization, flutter/settings, flutter/isolate).
- Tool persistent state lives in bin/cache plus user config: Config (flutter config settings), PersistentToolState, and unified_analytics telemetry gated by --disable-analytics/--enable-analytics/--suppress-analytics handled in runner.run before dispatch.
- Crash path: unhandled errors in the zone go to _handleToolErrorImpl -> CrashReportSender.sendReport (skipped when running on a bot or with --local-engine) and a local flutter_*.log file is written to the cwd (falling back to system temp) containing command, exception, stack, and `flutter doctor` output.
- The daemon protocol multiplexes IDE traffic: DaemonConnection parses [json] envelopes and optional binary streams, dispatches to Domain handlers registered with registerDomain() (app, device, emulator, devtools), and emits events back over stdout.
Behavior traces
Three end-to-end flows were traced. (1) CLI bootstrap: bin/flutter -> shared.sh -> Dart SDK fetch + snapshot compile -> flutter_tools.dart -> executable.main -> runner.run -> FlutterCommandRunner -> FlutterCommand.runCommand. (2) `flutter run` hot path: RunCommand picks HotRunner -> FlutterDevice.create (ResidentCompiler + shader compiler) -> device launch -> attach to VM service -> keystroke-triggered _reloadSources -> DevFS push -> VM reloadSources -> reassemble. (3) On-device app start: embedder creates Shell/Engine -> RuntimeController.LaunchRootIsolate -> dart:ui _runMain -> user main() -> runApp -> WidgetsFlutterBinding frame pipeline -> rasterized by the engine's Animator/Rasterizer.
- Bootstrap order (posix): bin/flutter -> follow_links -> source bin/internal/shared.sh -> shared::execute -> update_engine_version.sh -> staleness check on flutter_tools.stamp -> _wait_for_lock -> update_dart_sdk.sh -> pub_upgrade_with_retry (10 tries) -> `dart --snapshot-kind=app-jit` compile -> exec dart flutter_tools.snapshot "$@".
- Guard rails in bootstrap: shared::execute refuses to run without git on PATH and without $FLUTTER_ROOT/.git (must be a real clone), warns when EUID==0 outside Docker/CI, and sources bin/internal/bootstrap.sh first if present.
- Tool dispatch order: bin/flutter_tools.dart main -> executable.main (sets Cache.flutterRoot via defaultFlutterRoot) -> runner.run -> runInContext -> ToolDependencies.bootstrap -> FlutterCommandRunner -> commands().forEach(runner.addCommand) -> runner.run(args) -> FlutterCommand.verifyThenRun -> runCommand.
- Error exit contract: UsageException -> exit 64; ToolExit -> its exitCode or 1; ProcessExit(immediate) -> direct exit; missing-git ProcessException -> friendly message + exit 1; anything else -> crash report path with exit 1.
- `flutter run` flow: RunCommand.runCommand computes BuildInfo via getBuildInfo(), picks hotMode = shouldUseHotMode(buildInfo), constructs HotRunner(flutterDevices, debuggingOptions...) or ColdRunner, then ResidentRunner.run() launches the app on each Device and attaches to the VM service (vmservice.dart).
- Hot reload flow: user presses r -> HotRunner.reload -> _reloadSources -> projectFileInvalidator finds changed sources -> ResidentCompiler.recompile -> devFS update -> _reloadSourcesService issues VM-service reloadSources -> _reassembleHelper invokes reassemble in the app -> DeviceReloadReport aggregated per view; timings tracked via named Stopwatches (reloadSources:reload, :vm, :reassemble).
- Engine app launch flow: the embedder (embedder.h API or platform embedder like FlutterEngine.java) creates a Shell, which constructs Engine with a RuntimeController; Engine::Run/LaunchRootIsolate starts the Dart isolate, which enters dart:ui's _runMain (hooks.dart) -> user's main() -> runApp -> WidgetsFlutterBinding.ensureInitialized + scheduleAttachRootWidget + scheduleWarmUpFrame.
- Frame pipeline order (documented in binding.dart): transient callbacks -> handleBeginFrame (animations, transient frame callbacks) -> microtasks -> handleDrawFrame -> build/layout/paint -> compositing -> semantics -> finalization; super.drawFrame() ordering keeps the layer stack consistent.
- `flutter test` flow on CI: LUCI target -> flutter_drone recipe -> `dart dev/bots/test.dart` -> selectShard() picks the shard runner (e.g. frameworkTestsRunner, _runToolTests) -> suite runner invokes `flutter test` with flutterTestArgs; --local-engine flags are exported as FLUTTER_LOCAL_ENGINE env vars for subprocesses.
- First-run persistence flow: when artifacts are missing, commands call Cache.updateAll -> downloads zip from storage.googleapis.com -> unzip into bin/cache/artifacts -> stamp files written; failed downloads leave no partial artifacts because downloads go through temp files (cache.dart Net/HttpClient path).
Testing signals
Testing is extensive and shard-oriented. dev/bots/test.dart is the single entrypoint: it maps SHARD/SUBSHARD env vars to suite runners under dev/bots/suite_runners and randomizes test order within shards (seeded via --test-randomize-ordering-seed). Framework unit/widget tests live in packages/flutter/test mirroring lib/; tool tests live in packages/flutter_tools/test split into shard dirs (general.shard, commands.shard/{hermetic,permeable}, web.shard, integration.shard, android_*_integration.shard). Engine C++ unit tests sit next to sources (*_unittests.cc). Golden-image testing goes through packages/flutter_goldens (Skia Gold), device-level integration through dev/devicelab and dev/integration_tests, and coverage via .github/workflows/coverage.yml.
- dev/bots/test.dart is the repo-wide test entrypoint: `dart dev/bots/test.dart` runs all tests; SHARD and SUBSHARD env vars select subsets (e.g. SHARD=framework_tests SUBSHARD=widgets, SHARD=tool_tests SUBSHARD=general); --dry-run lists what would run.
- Tests within a shard are executed in random order to catch inter-test dependencies; --test-randomize-ordering-seed=<n> reproduces a given order.
- Suite runners are modular: dev/bots/suite_runners/run_*.dart files each own one shard (framework, web, analyze, customer_testing, docs, verify_binaries_codesigned, fuchsia_precache, skp_generator, android hardware smoke, java17 integration, preview integration).
- Framework tests live in packages/flutter/test mirroring lib/ structure (animation, cupertino, material, widgets, services, semantics, rendering, ...); flutter_test provides testWidgets/pumpWidget and _goldens_io.dart/_goldens_web.dart for golden comparisons.
- Tool tests are sharded by directory: packages/flutter_tools/test/{general.shard,commands.shard,web.shard,integration.shard,android_java17_integration.shard,android_preview_integration.shard}; commands.shard is further split into hermetic and permeable subdirs.
- Engine C++ code uses gtest-style unit tests co-located with sources (engine_unittests.cc, animator_unittests.cc, rasterizer_unittests.cc, pipeline_unittests.cc, persistent_cache_unittests.cc) under engine/src/flutter/shell/common; there is also engine/src/flutter/testing for harnesses.
- Device-lab integration tests run on real hardware via dev/devicelab (bin/run.dart task runner + lib/tasks task library); dev/integration_tests contains ~35 test apps wired into the pub workspace.
- CI wiring: .ci.yaml declares per-commit LUCI targets with platform_properties (OS, cores, device_type, kvm, dependencies like android_sdk/open_jdk/gradle_dists versions) and enabled_branches master + release candidates.
- GitHub Actions runs auxiliary checks only: tool-test-general.yml runs the tool_tests general subshard on PRs touching dev/, packages/flutter_tools/, bin/, engine/, .ci.yaml, DEPS; coverage.yml handles coverage.
- Static analysis is enforced: root analysis_options.yaml + analysis_options_common.yaml apply repo-wide lints, and dev/bots/analyze.dart is a custom analysis shard runner (plus check_code_samples.dart, check_examples_cross_imports.dart).
- Test ownership is tracked in TESTOWNERS at repo root; dev/forbidden_from_release_tests guards that certain tests never run on release branches.
- Coverage/testing gaps are partially self-reported: framework_coverage is its own shard (run_framework_coverage_tests.dart) implying measured coverage enforcement rather than incidental. (inferred)
Risk map
The biggest risk surface is the bootstrap supply chain: every fresh clone downloads and executes a Dart SDK and prebuilt engine artifacts over HTTPS, with host-overridable base URLs. Crash reporting and analytics exfiltrate command lines and stack traces (gated but present). Cross-platform shell logic is duplicated between .sh/.bat pairs that must be manually kept in sync. The locking scheme degrades to mkdir on NFS, and cache invalidation leans on file mtimes. The merged monorepo concentrates blast radius: .ci.yaml is ~7800 lines, DEPS pins a dozen external revisions, and 'isolated' files must not leak into google3 builds.
- Supply-chain: bin/internal/update_dart_sdk.sh downloads the Dart SDK from ${FLUTTER_STORAGE_BASE_URL:-https://storage.googleapis.com} at first run; cache.dart similarly downloads engine artifacts and CIPD packages from Google endpoints — a hostile mirror or MITM upstream would execute code on dev machines.
- Telemetry exfiltration: runner.dart sends analytics events and, on crashes, sends a report containing the full command line, exception type, stack trace, and flutter doctor output via CrashReportSender — suppressed on bots and for --local-engine runs but on by default.
- dev/bots/serviceaccount.enc is an encrypted service-account blob committed to the repo — a secrets-adjacent file whose decryption path lives in CI infrastructure.
- Duplicated bootstrap logic: bin/flutter, flutter.bat, shared.sh, and shared.bat carry mirrored logic and header comments explicitly warn to keep them consistent — drift between them is a recurring hazard.
- Upgrade locking degrades gracefully but unevenly: flock -> shlock -> mkdir fallback; flock errors are swallowed (NFS needs write fds), and the mkdir lock relies on atomic directory creation, so concurrent first-runs on exotic filesystems can race.
- Cache invalidation relies on pubspec.yaml-vs-pubspec.lock mtime comparison plus a hand-maintained compilekey (revision:FLUTTER_TOOL_ARGS); a NOP pub upgrade touches pubspec.lock (issue 171024 workaround) — subtle staleness bugs have occurred here.
- Hot restart correctness depends on a filename-swapping hack (.swap dill) because Windows embedders memory-map the previous dill — fragile coupling between tool behavior and embedder file semantics.
- google3 divergence: executable.main notes WebRunner/DevTools/mustache deps differ in google3 and files under lib/src/isolated are 'intentionally excluded from google3 tooling' — edits crossing that boundary break internal builds invisibly to OSS CI. (inferred)
- DEPS pins dart_revision, skia_revision, clang_version, reclient_version and more; comments warn that dart rolls must also update transitive deps via //tools/dart/create_updated_flutter_deps.py — manual edits produce skewed toolchains.
- .ci.yaml is ~7847 lines declaring hundreds of LUCI targets with pinned dependency versions (android_sdk 37v2, open_jdk 21, multiple gradle dists) — a single malformed property breaks scheduling across the fleet.
- The tool writes to shared mutable state in $FLUTTER_ROOT/bin/cache and only warns (with a paperclip) when run as root outside containers/CI — careless sudo usage corrupts the cache for the owning user.
- Error-handling depth: _handleToolErrorImpl nests a catch-all inside the crash-report path and deliberately throws ProcessExit(immediate) to break potential infinite error loops — complex error semantics easy to regress.
Extension points
The repo is built for extension at several layers. CLI: add a FlutterCommand subclass and register it in generateCommands(). DI: almost every service resolves through AppContext/globals, and runner.run accepts a per-type overrides map used for google3 and tests. Build: the BuildSystem runs Target subclasses registered via BuildTargets. Platform support: Feature flags + DevelopmentArtifacts + Device/DeviceDiscovery implementations (incl. custom-devices JSON config). Tool-side: an experimental ExtensionManager/ExtensionEntryPoint mechanism loads tool extensions (linux prototype wired in already). IDE surface: daemon Domains. Engine side: the embedder.h C API plus platform channels/MethodChannel for plugins, and dart_plugin_registrant for Dart-side plugin init.
- New CLI commands: subclass FlutterCommand (runner/flutter_command.dart), register an instance in the generateCommands() list in executable.dart — this is the single command table for the whole tool.
- Feature flags: add a Feature constant and a getter on FeatureFlags; flags get a configSetting consumed by `flutter config` and gate artifacts/commands — documented in docs/contributing/Feature-flags.md.
- Build steps: implement a Target subclass (inputs/outputs via Source patterns + a build action) and register it through the BuildTargets abstraction (BuildTargetsImpl in isolated/build_targets.dart overrides the interface).
- Dependency injection: services are fetched with context.get<T>()/globals.* (base/context.dart, globals.dart) and runner.run takes an overrides map <Type, Generator> — executable.main already uses it for Logger, AnsiTerminal, WebRunnerFactory, DevtoolsLauncher, TemplateRenderer, PreRunValidator, BuildTargets.
- Tool extensions: ExtensionManager + ExtensionEntryPoint (experimental/extension_manager.dart, extension_discovery.dart) load external tool extensions; the linux prototype entry point is already wired into executable.main behind isToolExtensionsEnabled.
- New device types: implement Device + DeviceDiscovery under the device manager (flutter_device_manager.dart), or declare SSH-based custom devices consumed by CustomDevicesCommand and custom_devices config.
- Doctor/validators: DoctorValidator implementations are aggregated in doctor.dart workflows (android_workflow, iosWorkflow); AnalyzeCommand additionally accepts a list of ProjectValidator plugins (GeneralInfoProjectValidator, VariableDumpMachineProjectValidator).
- IDE daemon protocol: new RPC surfaces are added as Domain subclasses registered via registerDomain() in DaemonCommand (existing domains cover app, device, emulator, daemon, devtools).
- Hot-runner hooks: HotRunnerConfig exposes setupHotRestart/setupHotReload/updateDevFSComplete/runPreShutdownOperations overridable via context.get<HotRunnerConfig>() — used by embedders like google3 to customize reload behavior.
- Engine platform ports: implement the embedder.h C API (or a platform embedder under shell/platform/) and bridge platform channels; Dart-side plugins register through dart_plugin_registrant.dart.
- Project scaffolding: `flutter create` templates live in packages/flutter_tools/templates (app, module, plugin, package_ffi...) rendered through TemplateRenderer; ExtensionTemplateManager can add extension-provided templates.
- Test infrastructure plugins: dev/bots suite runners follow the ShardRunner typedef — adding a shard means writing a run_*.dart under dev/bots/suite_runners and registering it in the selectShard map plus a matching .ci.yaml target.
Contributor handoff
This repo punishes agents that skip the bootstrap layer: the CLI only works from a real git clone with git on PATH, and editing flutter_tools' pubspec triggers a full snapshot rebuild on next run. Shell logic is duplicated between .sh and .bat files and must be kept in sync. The DI layer (context.get/globals + overrides) is the sanctioned way to reach services — do not construct singletons directly. Most engine-facing changes need a local engine build via --local-engine flags. Tests run through dev/bots/test.dart with SHARD/SUBSHARD, and the authoritative CI config is .ci.yaml, not .github/workflows.
- Read first (tool work): bin/internal/shared.sh (bootstrap+locking), packages/flutter_tools/lib/executable.dart (command table + overrides), lib/runner.dart (error/exit semantics), lib/src/runner/flutter_command.dart + flutter_command_runner.dart (arg plumbing), lib/src/base/context.dart + globals.dart (DI).
- The tool hard-fails if $FLUTTER_ROOT/.git is missing or git is not on PATH — packaged archives rely on a different code path, so test changes against a real clone.
- Editing packages/flutter_tools/pubspec.yaml invalidates the tool snapshot (mtime check) — the next `flutter` invocation rebuilds it; commit pubspec.lock consistently or users hit unexpected rebuilds.
- Keep paired scripts in sync by convention: bin/flutter <-> flutter.bat, shared.sh <-> shared.bat, update_dart_sdk.sh <-> .ps1, update_engine_version.sh <-> .ps1 — headers explicitly demand parity.
- Files under packages/flutter_tools/lib/src/isolated are excluded from google3; anything depending on dwds/devtools sources must stay there or be injected via the overrides map — moving imports out silently breaks internal builds.
- Run tests the repo's way: `dart dev/bots/test.dart` with SHARD=/SUBSHARD= (e.g. SHARD=tool_tests SUBSHARD=general), or `flutter test` inside a package dir; pass --local-engine=host_debug_unopt when validating against a locally built engine.
- Framework edits: source in packages/flutter/lib/src/<area> mirrors tests in packages/flutter/test/<area>; golden-image changes flow through packages/flutter_goldens and Skia Gold — expect golden tests to fail until images are triaged.
- Engine edits live in engine/src/flutter (GN/Ninja, not pub): unit tests are *_unittests.cc beside sources; DEPS holds pinned dep revisions that should be rolled with the documented tooling rather than hand-edited.
- Lint/style: every Dart file starts with the BSD license header; root analysis_options.yaml/analysis_options_common.yaml enforce strict lints; dev/bots/analyze.dart plus cross-import checkers run in CI — run them before pushing.
- CI truth source is .ci.yaml + dev/bots/test.dart shards; .github/workflows only runs auxiliary PR checks — adding a CI target means editing .ci.yaml (and likely registering a suite runner), not a GitHub workflow.
- Error paths are contractual: throw ToolExit/throwToolExit for user-facing failures (maps to exit codes), UsageException gives exit 64 — never call exit() directly from command code because shutdown hooks and crash reporting are bypassed.
- Common trap: 'files scanned' inventories miss most of this repo — the file walk capped at 4000 of a much larger tree; rely on grep/glob inside packages/flutter_tools/lib/src and engine/src/flutter rather than assuming a small codebase. (inferred)
Languages
| Language | Files | Share of source |
|---|---|---|
| C++ | 1823 | 42% |
| C | 1784 | 41% |
| Markdown | 480 | docs/config |
| Java | 314 | 7% |
| YAML | 314 | docs/config |
| JSON | 256 | docs/config |
| Swift | 141 | 3% |
| Kotlin | 107 | 2% |
Start reading here
Probable source entrypoint: packages/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
packages/ (1028 files)
Repository structure
| Top-level path | Files |
|---|---|
engine/ | 1028 |
dev/ | 1028 |
packages/ | 1028 |
examples/ | 570 |
docs/ | 205 |
.github/ | 42 |
.agents/ | 41 |
bin/ | 30 |
Docs found: CONTRIBUTING.mdREADME.mddocs/Flutter-Self-Service-Index.mddocs/In-case-of-emergency.mddocs/README.mddocs/about/Flutter's-repository-architecture.mddocs/about/Glossary.mddocs/about/Project-teams.mddocs/about/The-Engine-architecture.mddocs/about/The-Framework-architecture.md
CI & config entrypoints
README.md— Repository readme.github/workflows/content-aware-hash.yml— GitHub Actions workflow.github/workflows/coverage.yml— GitHub Actions workflow.github/workflows/cut-release-branch.yml— GitHub Actions workflow.github/workflows/delete-bot-branches.yml— GitHub Actions workflow.github/workflows/easy-cp.yml— GitHub Actions workflow.github/workflows/freeze.yml— GitHub Actions workflow.github/workflows/labeler.yml— GitHub Actions workflow.github/workflows/mac-verify-binaries.yml— GitHub Actions workflow.github/workflows/merge-changelog.yml— GitHub Actions workflow
Structural health signals
Average score 7.3/10 across 96 analyzed source files; 25 hotspots surfaced.
engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java— 1136 lines, score 5 (Large file, Deep nesting)engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java— 963 lines, score 5 (Large file, Deep nesting)engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java— 1214 lines, score 5.4 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
acb8fd51143a2026-09-22 — Roll Dart SDK from 907d0c1bcd40 to b3460e4fb2b2 (1 revision) (#193144)a2810c0ff0802026-09-22 — Roll Dart SDK from 4c572fafd056 to 907d0c1bcd40 (4 revisions) (#193141)f6c140c9d5332026-09-22 — Roll pub packages (#193138)a3ca57056ced2026-09-21 — Roll pub packages (#193129)860ac7c6182e2026-09-21 — [Linux] Request a frame when a view starts rendering (#192911)