Repository navigation
[tool] Define modular dependency injection containers and bootstrapper - #190724
Conversation
36e2980 to
cf41bc2
Compare
cf41bc2 to
378a4ba
Compare
378a4ba to
85aa4f1
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces a structured dependency injection and context bootstrapping mechanism for flutter_tools by adding AndroidContext, AppleContext, ToolContext, and a central ToolDependencies bootstrapper, along with corresponding tests and fakes. Feedback on the implementation highlights that tool_dependencies.dart should use the resolved finalShutdownHooks and finalSystemClock.now() to maintain a single source of truth and ensure hermetic time-dependent behavior. Additionally, the fake context classes in fakes.dart should be refactored to use late final fields instead of returning new instances on every getter invocation, preserving state persistence and referential transparency in tests.
| delegate: LocalFileSystem( | ||
| LocalSignals.instance, | ||
| Signals.defaultExitSignals, | ||
| shutdownHooks ?? ShutdownHooks(), |
There was a problem hiding this comment.
A new ShutdownHooks instance is created here if shutdownHooks is null, instead of using the already resolved finalShutdownHooks defined on line 136. This breaks the single source of truth for shutdown hooks, meaning any hooks registered on finalShutdownHooks won't be coordinated with the LocalFileSystem's shutdown hooks.
| shutdownHooks ?? ShutdownHooks(), | |
| finalShutdownHooks, |
References
- Avoid duplicating state: Keep only one source of truth. (link)
| @override | ||
| Artifacts get artifacts => _artifacts ?? FakeArtifacts(fileSystem: fs); | ||
|
|
||
| @override | ||
| BotDetector get botDetector => _botDetector ?? const FakeBotDetector(false); | ||
|
|
||
| @override | ||
| Cache get cache => _cache ?? FakeCache(fileSystem: fs); | ||
|
|
||
| @override | ||
| Config get config => _config ?? FakeConfig(); | ||
|
|
||
| @override | ||
| CustomDevicesConfig get customDevicesConfig => | ||
| _customDevicesConfig ?? | ||
| CustomDevicesConfig.test(fileSystem: fs, logger: logger, platform: platform); | ||
|
|
||
| @override | ||
| FlutterVersion get flutterVersion => _flutterVersion ?? FakeFlutterVersion(); | ||
|
|
||
| @override | ||
| FileSystem get fs => _fs ?? MemoryFileSystem.test(); | ||
|
|
||
| @override | ||
| Git get git => _git ?? Git(currentPlatform: platform, runProcessWith: processUtils); | ||
|
|
||
| @override | ||
| LocalEngineLocator get localEngineLocator => | ||
| _localEngineLocator ?? | ||
| LocalEngineLocator( | ||
| userMessages: userMessages, | ||
| logger: logger, | ||
| platform: platform, | ||
| fileSystem: fs, | ||
| flutterRoot: Cache.flutterRoot ?? '', | ||
| ); | ||
|
|
||
| @override | ||
| Logger get logger => _logger ?? BufferLogger.test(); | ||
|
|
||
| @override | ||
| TestCompilerNativeAssetsBuilder? get nativeAssetsBuilder => _nativeAssetsBuilder; | ||
|
|
||
| @override | ||
| OperatingSystemUtils get os => _os ?? FakeOperatingSystemUtils(); | ||
|
|
||
| @override | ||
| OutputPreferences get outputPreferences => _outputPreferences ?? OutputPreferences.test(); | ||
|
|
||
| @override | ||
| Platform get platform => _platform ?? FakePlatform(); | ||
|
|
||
| @override | ||
| PreRunValidator get preRunValidator => _preRunValidator ?? const NoOpPreRunValidator(); | ||
|
|
||
| @override | ||
| ProcessManager get processManager => _processManager ?? FakeProcessManager.any(); | ||
|
|
||
| @override | ||
| ProcessUtils get processUtils => | ||
| _processUtils ?? ProcessUtils(processManager: processManager, logger: logger); | ||
|
|
||
| @override | ||
| FlutterProjectFactory get projectFactory => | ||
| _projectFactory ?? FlutterProjectFactory(fileSystem: fs, logger: logger); | ||
|
|
||
| @override | ||
| ShutdownHooks get shutdownHooks => _shutdownHooks ?? ShutdownHooks(); | ||
|
|
||
| @override | ||
| Signals get signals => _signals ?? Signals.test(); | ||
|
|
||
| @override | ||
| Stdio get stdio => _stdio ?? FakeStdio(); | ||
|
|
||
| class FakeArtifacts extends Fake implements Artifacts {} | ||
| @override | ||
| SystemClock get systemClock => _systemClock ?? const SystemClock(); | ||
|
|
||
| @override | ||
| AnsiTerminal get terminal => _terminal ?? AnsiTerminal(stdio: stdio, platform: platform); | ||
|
|
||
| @override | ||
| UserMessages get userMessages => _userMessages ?? UserMessages(); | ||
|
|
||
| @override | ||
| FileSystemUtils get fileSystemUtils => FileSystemUtils(fileSystem: fs, platform: platform); |
There was a problem hiding this comment.
In FakeToolContext, returning new instances of dependencies (like MemoryFileSystem, BufferLogger, FakeCache, etc.) on every getter invocation breaks referential transparency and state persistence. For example, accessing toolContext.fs twice will return two completely different, isolated in-memory file systems, causing state changes in one to be lost in the other.
Using late final fields instead of getters ensures that these dependencies are lazily initialized on first access and then cached for all subsequent accesses, while still allowing them to reference each other safely during initialization.
@override
late final Artifacts artifacts = _artifacts ?? FakeArtifacts(fileSystem: fs);
@override
late final BotDetector botDetector = _botDetector ?? const FakeBotDetector(false);
@override
late final Cache cache = _cache ?? FakeCache(fileSystem: fs);
@override
late final Config config = _config ?? FakeConfig();
@override
late final CustomDevicesConfig customDevicesConfig =
_customDevicesConfig ??
CustomDevicesConfig.test(fileSystem: fs, logger: logger, platform: platform);
@override
late final FlutterVersion flutterVersion = _flutterVersion ?? FakeFlutterVersion();
@override
late final FileSystem fs = _fs ?? MemoryFileSystem.test();
@override
late final Git git = _git ?? Git(currentPlatform: platform, runProcessWith: processUtils);
@override
late final LocalEngineLocator localEngineLocator =
_localEngineLocator ??
LocalEngineLocator(
userMessages: userMessages,
logger: logger,
platform: platform,
fileSystem: fs,
flutterRoot: Cache.flutterRoot ?? '',
);
@override
late final Logger logger = _logger ?? BufferLogger.test();
@override
TestCompilerNativeAssetsBuilder? get nativeAssetsBuilder => _nativeAssetsBuilder;
@override
late final OperatingSystemUtils os = _os ?? FakeOperatingSystemUtils();
@override
late final OutputPreferences outputPreferences = _outputPreferences ?? OutputPreferences.test();
@override
late final Platform platform = _platform ?? FakePlatform();
@override
late final PreRunValidator preRunValidator = _preRunValidator ?? const NoOpPreRunValidator();
@override
late final ProcessManager processManager = _processManager ?? FakeProcessManager.any();
@override
late final ProcessUtils processUtils =
_processUtils ?? ProcessUtils(processManager: processManager, logger: logger);
@override
late final FlutterProjectFactory projectFactory =
_projectFactory ?? FlutterProjectFactory(fileSystem: fs, logger: logger);
@override
late final ShutdownHooks shutdownHooks = _shutdownHooks ?? ShutdownHooks();
@override
late final Signals signals = _signals ?? Signals.test();
@override
late final Stdio stdio = _stdio ?? FakeStdio();
@override
late final SystemClock systemClock = _systemClock ?? const SystemClock();
@override
late final AnsiTerminal terminal = _terminal ?? AnsiTerminal(stdio: stdio, platform: platform);
@override
late final UserMessages userMessages = _userMessages ?? UserMessages();
@override
FileSystemUtils get fileSystemUtils => FileSystemUtils(fileSystem: fs, platform: platform);References
- Suggest simplification and refactoring: Assess whether the code can be made simpler or refactored to enhance readability and maintainability. (link)
| @override | ||
| CocoaPods get cocoaPods => _cocoaPods ?? FakeCocoaPods(); | ||
|
|
||
| @override | ||
| CocoaPodsValidator get cocoapodsValidator => _cocoapodsValidator ?? FakeCocoaPodsValidator(); | ||
|
|
||
| @override | ||
| IOSSimulatorUtils get iosSimulatorUtils => _iosSimulatorUtils ?? FakeIOSSimulatorUtils(); | ||
|
|
||
| @override | ||
| IOSWorkflow get iosWorkflow => _iosWorkflow ?? FakeIOSWorkflow(); | ||
|
|
||
| @override | ||
| PlistParser get plistParser => _plistParser ?? FakePlistParser(); | ||
|
|
||
| @override | ||
| XCDevice get xcdevice => _xcdevice ?? FakeXCDevice(); | ||
|
|
||
| @override | ||
| Xcode get xcode => _xcode ?? FakeXcode(); | ||
|
|
||
| @override | ||
| XcodeProjectInterpreter get xcodeProjectInterpreter => FakeXcodeProjectInterpreter(); | ||
| } |
There was a problem hiding this comment.
Similar to FakeToolContext, FakeAppleContext should cache its dependencies using late final fields to ensure referential transparency and state persistence across multiple accesses in tests.
@override
late final CocoaPods cocoaPods = _cocoaPods ?? FakeCocoaPods();
@override
late final CocoaPodsValidator cocoapodsValidator = _cocoapodsValidator ?? FakeCocoaPodsValidator();
@override
late final IOSSimulatorUtils iosSimulatorUtils = _iosSimulatorUtils ?? FakeIOSSimulatorUtils();
@override
late final IOSWorkflow iosWorkflow = _iosWorkflow ?? FakeIOSWorkflow();
@override
late final PlistParser plistParser = _plistParser ?? FakePlistParser();
@override
late final XCDevice xcdevice = _xcdevice ?? FakeXCDevice();
@override
late final Xcode xcode = _xcode ?? FakeXcode();
@override
late final XcodeProjectInterpreter xcodeProjectInterpreter = FakeXcodeProjectInterpreter();References
- Suggest simplification and refactoring: Assess whether the code can be made simpler or refactored to enhance readability and maintainability. (link)
| AnsiTerminal( | ||
| stdio: finalStdio, | ||
| platform: finalPlatform, | ||
| now: DateTime.now(), |
There was a problem hiding this comment.
| @override | ||
| GradleUtils get gradleUtils => _gradleUtils ?? FakeGradleUtils(); |
There was a problem hiding this comment.
Similar to FakeToolContext, FakeAndroidContext should cache its gradleUtils instance using a late final field to avoid returning a new instance on every access.
| @override | |
| GradleUtils get gradleUtils => _gradleUtils ?? FakeGradleUtils(); | |
| @override | |
| late final GradleUtils gradleUtils = _gradleUtils ?? FakeGradleUtils(); |
…s in ToolDependencies
…#12453) Manual roll Flutter from 27b098811f3b to c2437523d308 (179 revisions) Manual roll requested by [email protected] flutter/flutter@27b0988...c243752 2026-08-12 [email protected] Enable Gradle CI cache on all test targets that require android_sdk (flutter/flutter#190723) 2026-08-12 [email protected] [analysis] Reland "Added initial implementation of the flutter_analyzer_plugin (#175679)" (flutter/flutter#191022) 2026-08-12 [email protected] Switch testing to gradle bin distribution type instead of all (flutter/flutter#190738) 2026-08-12 [email protected] Convert Mockito instances in Kotlin to Mockk (flutter/flutter#189884) 2026-08-12 [email protected] Report individual test results to LUCI ResultDB (flutter/flutter#190254) 2026-08-12 [email protected] Toggleable reaction duration respects overrides (flutter/flutter#190857) 2026-08-12 [email protected] Roll Skia from e00dbd7448c4 to fee7272f5bc2 (1 revision) (flutter/flutter#191007) 2026-08-12 [email protected] Started caching text shadows by content. (flutter/flutter#190681) 2026-08-12 [email protected] Adds agent skill for spawning led tasks. (flutter/flutter#190937) 2026-08-12 [email protected] Roll Packages from aaaf246 to 94485f1 (8 revisions) (flutter/flutter#191008) 2026-08-12 [email protected] flutter_tools: validate plugin identifiers before generating registrant code (flutter/flutter#190462) 2026-08-12 [email protected] Roll Skia from 112f36148949 to e00dbd7448c4 (3 revisions) (flutter/flutter#190993) 2026-08-12 [email protected] Roll Skia from 7d366c802307 to 112f36148949 (3 revisions) (flutter/flutter#190983) 2026-08-12 [email protected] remove bringup for flavors test (flutter/flutter#190940) 2026-08-12 [email protected] Roll Skia from 1f10a20bdd61 to 7d366c802307 (2 revisions) (flutter/flutter#190980) 2026-08-12 [email protected] Remove `--no-sim-use-hardfp` flag (flutter/flutter#190790) 2026-08-12 [email protected] Roll pub packages (flutter/flutter#190977) 2026-08-12 [email protected] Roll Skia from 339bedab6766 to 1f10a20bdd61 (1 revision) (flutter/flutter#190975) 2026-08-12 [email protected] RawTooltip respects AnimationStyle updates and reverseCurve (flutter/flutter#190889) 2026-08-12 [email protected] ci: Support --target_arch option in prepare_package.dart (flutter/flutter#190960) 2026-08-12 [email protected] Roll Fuchsia Linux SDK from SFq4FVodIOQAS26Lr... to -uHuSGv3wt7QAlDwa... (flutter/flutter#190973) 2026-08-12 [email protected] Removes building of ci/android_debug_x86 as nobody should be consuming it. (flutter/flutter#190951) 2026-08-12 [email protected] Adds error about wimp_heavy not being implemented. (flutter/flutter#189945) 2026-08-11 [email protected] Add clang, cmake, and ninja deps to Linux windowing_test (flutter/flutter#190119) 2026-08-11 [email protected] [AGP 9.1.0 Migration #1] Add Android Gradle Plugin Public API migration documentation (flutter/flutter#190842) 2026-08-11 [email protected] [flutter_tools] Fix deadlock in debug adapters when process exits early (flutter/flutter#190931) 2026-08-11 [email protected] [tool] Define modular dependency injection containers and bootstrapper (flutter/flutter#190724) 2026-08-11 [email protected] [web] Unify MaskFilter and ColorFilter primitives across CanvasKit and Skwasm (flutter/flutter#190314) 2026-08-11 [email protected] Roll pub packages (flutter/flutter#190958) 2026-08-11 [email protected] [Impeller] Move image upload scheduling waits to GPU disable (flutter/flutter#190445) 2026-08-11 [email protected] Started generating the windows platform for macrobenchmarks (flutter/flutter#190932) 2026-08-11 [email protected] [web] Unify ui.Vertices (flutter/flutter#190563) 2026-08-11 [email protected] Fix accessibility_inspector service extensions map mutability (flutter/flutter#190888) 2026-08-11 [email protected] Add batch3 a11y_assessment for vpat (flutter/flutter#189042) 2026-08-11 [email protected] Remove Xcode environment when building swift tools in Xcode pre-action (flutter/flutter#190848) 2026-08-11 [email protected] [tool] Add missing play element in web test index.html to fix warning (flutter/flutter#190675) 2026-08-11 [email protected] Offload blocking work in ProcessTextPlugin to the background (flutter/flutter#189823) 2026-08-11 [email protected] [flutter_tools] Replace usages of package:dds/dap.dart with package:dap_adapters/dap_adapters.dart (flutter/flutter#190667) 2026-08-11 [email protected] Always update swift package dependencies (flutter/flutter#190886) 2026-08-11 [email protected] [flutter_tools] Add --preset option to flutter test (flutter/flutter#190878) 2026-08-11 [email protected] Include the examples cross imports checker in the analzyer. (flutter/flutter#190674) 2026-08-11 [email protected] [devicelab] Remove orphaned screenshot test files (flutter/flutter#190879) 2026-08-11 [email protected] Remove the bringup flag from the linux_arm_host_desktop_engine builder (flutter/flutter#190935) 2026-08-11 [email protected] Reduce web_skwasm_tests subshards from 8 to 2 (flutter/flutter#190728) ...
…ired non-nullable contexts (flutter#190922) ## Summary Part 2 of the modular dependency injection migration (stacked on flutter#190724). Wires explicit dependency injection into the Flutter tool entrypoints: - Wires `ToolDependencies.bootstrap` into `runner.dart` to initialize dependencies at startup. - Updates `FlutterCommandRunner` to accept non-nullable `toolContext`, `androidContext`, `appleContext`, and `toolDependencies`. - Updates `executable.dart` to forward `ToolDependencies` into `generateCommands`. - Updates `test_flutter_command_runner.dart` and hermetic test doubles. Part of flutter#47161
… discovery (flutter#191972) ## Description This PR optimizes `AndroidSdk` initialization by deferring expensive synchronous filesystem operations out of the constructor. ### Why this is needed now As part of the modular dependency injection migration ([flutter#190724](flutter#190724)), context objects like `AndroidContext` are created upfront during `ToolDependencies.bootstrap` / runner initialization rather than via the old global ambient context (`AppContext`). Previously, `AndroidSdk` was initialized lazily only when a command or service queried `globals.androidSdk` or `context.get<AndroidSdk>()`. With upfront `AndroidContext` creation, `AndroidSdk.locateAndroidSdk()` runs on tool startup for every command. Because the `AndroidSdk` constructor eagerly executed `reinitialize()`, it performed synchronous directory listings across `build-tools` and `platforms` directories and read `build.prop` files on disk. This introduced unnecessary disk I/O and latency to tool startup, including for commands completely unrelated to Android (such as `flutter config` or iOS-specific workflows). ### Changes - Defers synchronous scanning of build-tools and platform directories (`reinitialize()`) out of the `AndroidSdk` constructor so that object instantiation is fast and side-effect free. - `sdkVersions` and `latestVersion` are evaluated lazily on first access. - Retains `_fileSystem` on the instance so that explicit calls to `reinitialize()` continue to refresh platform and version state with the expected filesystem. ## Related Issues Part of [flutter#47161](flutter#47161) (flutter_tools modular dependency injection migration). ## Tests - Updated existing test in `packages/flutter_tools/test/general.shard/android/android_sdk_test.dart` (`constructing an AndroidSdk handles no matching lines in build.prop`) to access `sdk.latestVersion` so that `build.prop` parsing error-handling is executed during lazy initialization. - Added unit tests verifying: - Constructor instantiation does not initialize `sdkVersions` or `latestVersion` or scan platform directories. - `sdkVersions` and `latestVersion` evaluate lazily on first access across independent instances. - Calling `reinitialize()` updates `sdkVersions` and `latestVersion` and correctly falls back to stored `_fileSystem`.
Summary
Part 1 of the modular dependency injection migration.
Defines the foundational modular dependency injection containers and bootstrapper for
flutter_tools:ToolContext: Layer 1 OS wrappers (HostEnvironment) and Layer 2 SDK state (ToolConfiguration).AndroidContextandAppleContextfor clean platform isolation.ToolDependencies: Topologically instantiates and manages the dependency graph at startup with explicit overrides and lazy closures.dependency_injection_test.dartand fake context doubles.Followed by Part 2 (#190922) for runner wiring.
Part of #47161