Make AppContext immutable and race-free (#15984)
This updates AppContext per the recommendations in #15352
Fixes #15352
diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart
index 3877aac..45897b1 100644
--- a/packages/flutter_tools/lib/src/android/android_studio.dart
+++ b/packages/flutter_tools/lib/src/android/android_studio.dart
@@ -12,8 +12,7 @@
import '../globals.dart';
import '../ios/plist_utils.dart';
-AndroidStudio get androidStudio =>
- context.putIfAbsent(AndroidStudio, AndroidStudio.latestValid);
+AndroidStudio get androidStudio => context[AndroidStudio];
// Android Studio layout:
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index 7ce5ba5..4b3649d 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -17,7 +17,7 @@
import '../globals.dart';
import 'android_sdk.dart';
-AndroidWorkflow get androidWorkflow => context.putIfAbsent(AndroidWorkflow, () => new AndroidWorkflow());
+AndroidWorkflow get androidWorkflow => context[AndroidWorkflow];
enum LicensesAccepted {
none,
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index e1c8b01..f539a5a 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -84,8 +84,8 @@
abstract class Artifacts {
static Artifacts get instance => context[Artifacts];
- static void useLocalEngine(String engineSrcPath, EngineBuildPaths engineBuildPaths) {
- context.setVariable(Artifacts, new LocalEngineArtifacts(engineSrcPath, engineBuildPaths.targetEngine, engineBuildPaths.hostEngine));
+ static LocalEngineArtifacts getLocalEngine(String engineSrcPath, EngineBuildPaths engineBuildPaths) {
+ return new LocalEngineArtifacts(engineSrcPath, engineBuildPaths.targetEngine, engineBuildPaths.hostEngine);
}
// Returns the requested [artifact] for the [platform] and [mode] combination.
diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index 0915d17..44194d6 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -21,9 +21,9 @@
/// Injected factory class for spawning [AssetBundle] instances.
abstract class AssetBundleFactory {
/// The singleton instance, pulled from the [AppContext].
- static AssetBundleFactory get instance => context == null
- ? _kManifestFactory
- : context.putIfAbsent(AssetBundleFactory, () => _kManifestFactory);
+ static AssetBundleFactory get instance => context[AssetBundleFactory];
+
+ static AssetBundleFactory get defaultInstance => _kManifestFactory;
/// Creates a new [AssetBundle].
AssetBundle createBundle();
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 2f70774..57ab3b9 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -17,7 +17,7 @@
import 'file_system.dart';
import 'process.dart';
-GenSnapshot get genSnapshot => context.putIfAbsent(GenSnapshot, () => const GenSnapshot());
+GenSnapshot get genSnapshot => context[GenSnapshot];
/// A snapshot build configuration.
class SnapshotType {
diff --git a/packages/flutter_tools/lib/src/base/context.dart b/packages/flutter_tools/lib/src/base/context.dart
index 1b488e8..459adc4 100644
--- a/packages/flutter_tools/lib/src/base/context.dart
+++ b/packages/flutter_tools/lib/src/base/context.dart
@@ -3,79 +3,182 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:collection';
-typedef void ErrorHandler(dynamic error, StackTrace stackTrace);
+import 'package:meta/meta.dart';
-/// A singleton for application functionality. This singleton can be different
-/// on a per-Zone basis.
-AppContext get context => Zone.current['context'];
+/// Generates an [AppContext] value.
+///
+/// Generators are allowed to return `null`, in which case the context will
+/// store the `null` value as the value for that type.
+typedef dynamic Generator();
+/// An exception thrown by [AppContext] when you try to get a [Type] value from
+/// the context, and the instantiation of the value results in a dependency
+/// cycle.
+class ContextDependencyCycleException implements Exception {
+ ContextDependencyCycleException._(this.cycle);
+
+ /// The dependency cycle (last item depends on first item).
+ final List<Type> cycle;
+
+ @override
+ String toString() => 'Dependency cycle detected: ${cycle.join(' -> ')}';
+}
+
+/// The current [AppContext], as determined by the [Zone] hierarchy.
+///
+/// This will be the first context found as we scan up the zone hierarchy, or
+/// the "root" context if a context cannot be found in the hierarchy. The root
+/// context will not have any values associated with it.
+///
+/// This is guaranteed to never return `null`.
+AppContext get context => Zone.current[_Key.key] ?? AppContext._root;
+
+/// A lookup table (mapping types to values) and an implied scope, in which
+/// code is run.
+///
+/// [AppContext] is used to define a singleton injection context for code that
+/// is run within it. Each time you call [run], a child context (and a new
+/// scope) is created.
+///
+/// Child contexts are created and run using zones. To read more about how
+/// zones work, see https://www.dartlang.org/articles/libraries/zones.
class AppContext {
- final Map<Type, dynamic> _instances = <Type, dynamic>{};
- Zone _zone;
+ AppContext._(
+ this._parent,
+ this.name, [
+ this._overrides = const <Type, Generator>{},
+ this._fallbacks = const <Type, Generator>{},
+ ]);
- AppContext() : _zone = Zone.current;
+ final String name;
+ final AppContext _parent;
+ final Map<Type, Generator> _overrides;
+ final Map<Type, Generator> _fallbacks;
+ final Map<Type, dynamic> _values = <Type, dynamic>{};
- bool isSet(Type type) {
- if (_instances.containsKey(type))
- return true;
+ List<Type> _reentrantChecks;
- final AppContext parent = _calcParent(_zone);
- return parent != null ? parent.isSet(type) : false;
- }
+ /// Bootstrap context.
+ static final AppContext _root = new AppContext._(null, 'ROOT');
- dynamic getVariable(Type type) {
- if (_instances.containsKey(type))
- return _instances[type];
+ dynamic _boxNull(dynamic value) => value ?? _BoxedNull.instance;
- final AppContext parent = _calcParent(_zone);
- return parent?.getVariable(type);
- }
+ dynamic _unboxNull(dynamic value) => value == _BoxedNull.instance ? null : value;
- void setVariable(Type type, dynamic instance) {
- _instances[type] = instance;
- }
-
- dynamic operator[](Type type) => getVariable(type);
-
- dynamic putIfAbsent(Type type, dynamic ifAbsent()) {
- dynamic value = getVariable(type);
- if (value != null) {
- return value;
- }
- value = ifAbsent();
- setVariable(type, value);
- return value;
- }
-
- AppContext _calcParent(Zone zone) {
- final Zone parentZone = zone.parent;
- if (parentZone == null)
+ /// Returns the generated value for [type] if such a generator exists.
+ ///
+ /// If [generators] does not contain a mapping for the specified [type], this
+ /// returns `null`.
+ ///
+ /// If a generator existed and generated a `null` value, this will return a
+ /// boxed value indicating null.
+ ///
+ /// If a value for [type] has already been generated by this context, the
+ /// existing value will be returned, and the generator will not be invoked.
+ ///
+ /// If the generator ends up triggering a reentrant call, it signals a
+ /// dependency cycle, and a [ContextDependencyCycleException] will be thrown.
+ dynamic _generateIfNecessary(Type type, Map<Type, Generator> generators) {
+ if (!generators.containsKey(type))
return null;
- final AppContext parentContext = parentZone['context'];
- return parentContext == this
- ? _calcParent(parentZone)
- : parentContext;
+ return _values.putIfAbsent(type, () {
+ _reentrantChecks ??= <Type>[];
+
+ final int index = _reentrantChecks.indexOf(type);
+ if (index >= 0) {
+ // We're already in the process of trying to generate this type.
+ throw new ContextDependencyCycleException._(
+ new UnmodifiableListView<Type>(_reentrantChecks.sublist(index)));
+ }
+
+ _reentrantChecks.add(type);
+ try {
+ return _boxNull(generators[type]());
+ } finally {
+ _reentrantChecks.removeLast();
+ if (_reentrantChecks.isEmpty)
+ _reentrantChecks = null;
+ }
+ });
}
- Future<dynamic> runInZone(dynamic method(), {
- ZoneBinaryCallback<dynamic, dynamic, StackTrace> onError
+ /// Gets the value associated with the specified [type], or `null` if no
+ /// such value has been associated.
+ dynamic operator [](Type type) {
+ dynamic value = _generateIfNecessary(type, _overrides);
+ if (value == null && _parent != null)
+ value = _parent[type];
+ return _unboxNull(value ?? _generateIfNecessary(type, _fallbacks));
+ }
+
+ /// Runs [body] in a child context and returns the value returned by [body].
+ ///
+ /// If [overrides] is specified, the child context will return corresponding
+ /// values when consulted via [operator[]].
+ ///
+ /// If [fallbacks] is specified, the child context will return corresponding
+ /// values when consulted via [operator[]] only if its parent context didn't
+ /// return such a value.
+ ///
+ /// If [name] is specified, the child context will be assigned the given
+ /// name. This is useful for debugging purposes and is analogous to naming a
+ /// thread in Java.
+ V run<V>({
+ @required V body(),
+ String name,
+ Map<Type, Generator> overrides,
+ Map<Type, Generator> fallbacks,
}) {
- return runZoned(
- () => _run(method),
- zoneValues: <String, dynamic>{ 'context': this },
- onError: onError
+ final AppContext child = new AppContext._(
+ this,
+ name,
+ new Map<Type, Generator>.unmodifiable(overrides ?? const <Type, Generator>{}),
+ new Map<Type, Generator>.unmodifiable(fallbacks ?? const <Type, Generator>{}),
+ );
+ return runZoned<V>(
+ body,
+ zoneValues: <_Key, AppContext>{_Key.key: child},
);
}
- Future<dynamic> _run(dynamic method()) async {
- final Zone previousZone = _zone;
- try {
- _zone = Zone.current;
- return await method();
- } finally {
- _zone = previousZone;
+ @override
+ String toString() {
+ final StringBuffer buf = new StringBuffer();
+ String indent = '';
+ AppContext ctx = this;
+ while (ctx != null) {
+ buf.write('AppContext');
+ if (ctx.name != null)
+ buf.write('[${ctx.name}]');
+ if (ctx._overrides.isNotEmpty)
+ buf.write('\n$indent overrides: [${ctx._overrides.keys.join(', ')}]');
+ if (ctx._fallbacks.isNotEmpty)
+ buf.write('\n$indent fallbacks: [${ctx._fallbacks.keys.join(', ')}]');
+ if (ctx._parent != null)
+ buf.write('\n$indent parent: ');
+ ctx = ctx._parent;
+ indent += ' ';
}
+ return buf.toString();
}
}
+
+/// Private key used to store the [AppContext] in the [Zone].
+class _Key {
+ const _Key();
+
+ static const _Key key = const _Key();
+
+ @override
+ String toString() => 'context';
+}
+
+/// Private object that denotes a generated `null` value.
+class _BoxedNull {
+ const _BoxedNull();
+
+ static const _BoxedNull instance = const _BoxedNull();
+}
diff --git a/packages/flutter_tools/lib/src/base/file_system.dart b/packages/flutter_tools/lib/src/base/file_system.dart
index 736fff4..74fa9dc 100644
--- a/packages/flutter_tools/lib/src/base/file_system.dart
+++ b/packages/flutter_tools/lib/src/base/file_system.dart
@@ -22,19 +22,15 @@
///
/// By default it uses local disk-based implementation. Override this in tests
/// with [MemoryFileSystem].
-FileSystem get fs => context == null ? _kLocalFs : context[FileSystem];
+FileSystem get fs => context[FileSystem] ?? _kLocalFs;
-/// Enables recording of file system activity to the specified base recording
-/// [location].
-///
-/// This sets the [active file system](fs) to one that records all invocation
-/// activity before delegating to a [LocalFileSystem].
+/// Gets a [FileSystem] that will record file system activity to the specified
+/// base recording [location].
///
/// Activity will be recorded in a subdirectory of [location] named `"file"`.
/// It is permissible for [location] to represent an existing non-empty
/// directory as long as there is no collision with the `"file"` subdirectory.
-void enableRecordingFileSystem(String location) {
- final FileSystem originalFileSystem = fs;
+RecordingFileSystem getRecordingFileSystem(String location) {
final Directory dir = getRecordingSink(location, _kRecordingType);
final RecordingFileSystem fileSystem = new RecordingFileSystem(
delegate: _kLocalFs, destination: dir);
@@ -42,22 +38,19 @@
await fileSystem.recording.flush(
pendingResultTimeout: const Duration(seconds: 5),
);
- context.setVariable(FileSystem, originalFileSystem);
}, ShutdownStage.SERIALIZE_RECORDING);
- context.setVariable(FileSystem, fileSystem);
+ return fileSystem;
}
-/// Enables file system replay mode.
-///
-/// This sets the [active file system](fs) to one that replays invocation
-/// activity from a previously recorded set of invocations.
+/// Gets a [FileSystem] that replays invocation activity from a previously
+/// recorded set of invocations.
///
/// [location] must represent a directory to which file system activity has
/// been recorded (i.e. the result of having been previously passed to
-/// [enableRecordingFileSystem]), or a [ToolExit] will be thrown.
-void enableReplayFileSystem(String location) {
+/// [getRecordingFileSystem]), or a [ToolExit] will be thrown.
+ReplayFileSystem getReplayFileSystem(String location) {
final Directory dir = getReplaySource(location, _kRecordingType);
- context.setVariable(FileSystem, new ReplayFileSystem(recording: dir));
+ return new ReplayFileSystem(recording: dir);
}
/// Create the ancestor directories of a file path if they do not already exist.
diff --git a/packages/flutter_tools/lib/src/base/flags.dart b/packages/flutter_tools/lib/src/base/flags.dart
index 429b03c..81442a9 100644
--- a/packages/flutter_tools/lib/src/base/flags.dart
+++ b/packages/flutter_tools/lib/src/base/flags.dart
@@ -8,7 +8,7 @@
/// command-line flags and options that were specified during the invocation of
/// the Flutter tool.
-Flags get flags => context?.getVariable(Flags) ?? const _EmptyFlags();
+Flags get flags => context[Flags];
/// Encapsulation of the command-line flags and options that were specified
/// during the invocation of the Flutter tool.
@@ -52,8 +52,8 @@
}
}
-class _EmptyFlags implements Flags {
- const _EmptyFlags();
+class EmptyFlags implements Flags {
+ const EmptyFlags();
@override
ArgResults get _globalResults => null;
diff --git a/packages/flutter_tools/lib/src/base/io.dart b/packages/flutter_tools/lib/src/base/io.dart
index 326c487..5500d2a 100644
--- a/packages/flutter_tools/lib/src/base/io.dart
+++ b/packages/flutter_tools/lib/src/base/io.dart
@@ -155,23 +155,8 @@
io.IOSink get stderr => io.stderr;
}
-io.IOSink get stderr {
- if (context == null)
- return io.stderr;
- final Stdio contextStreams = context[Stdio];
- return contextStreams.stderr;
-}
+io.IOSink get stderr => context[Stdio].stderr;
-Stream<List<int>> get stdin {
- if (context == null)
- return io.stdin;
- final Stdio contextStreams = context[Stdio];
- return contextStreams.stdin;
-}
+Stream<List<int>> get stdin => context[Stdio].stdin;
-io.IOSink get stdout {
- if (context == null)
- return io.stdout;
- final Stdio contextStreams = context[Stdio];
- return contextStreams.stdout;
-}
+io.IOSink get stdout => context[Stdio].stdout;
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart
index 0d7bb12..ea844cb 100644
--- a/packages/flutter_tools/lib/src/base/os.dart
+++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -11,7 +11,7 @@
import 'process_manager.dart';
/// Returns [OperatingSystemUtils] active in the current app context (i.e. zone).
-OperatingSystemUtils get os => context.putIfAbsent(OperatingSystemUtils, () => new OperatingSystemUtils());
+OperatingSystemUtils get os => context[OperatingSystemUtils];
abstract class OperatingSystemUtils {
factory OperatingSystemUtils() {
diff --git a/packages/flutter_tools/lib/src/base/platform.dart b/packages/flutter_tools/lib/src/base/platform.dart
index dd96240..c781499 100644
--- a/packages/flutter_tools/lib/src/base/platform.dart
+++ b/packages/flutter_tools/lib/src/base/platform.dart
@@ -14,26 +14,29 @@
const Platform _kLocalPlatform = const LocalPlatform();
const String _kRecordingType = 'platform';
-Platform get platform => context == null ? _kLocalPlatform : context[Platform];
+Platform get platform => context[Platform] ?? _kLocalPlatform;
-/// Enables serialization of the current [platform] to the specified base
-/// recording [location].
+/// Serializes the current [platform] to the specified base recording
+/// [location].
///
/// Platform metadata will be recorded in a subdirectory of [location] named
/// `"platform"`. It is permissible for [location] to represent an existing
/// non-empty directory as long as there is no collision with the `"platform"`
/// subdirectory.
-Future<Null> enableRecordingPlatform(String location) async {
+///
+/// Returns the existing platform.
+Future<Platform> getRecordingPlatform(String location) async {
final Directory dir = getRecordingSink(location, _kRecordingType);
final File file = _getPlatformManifest(dir);
await file.writeAsString(platform.toJson(), flush: true);
+ return platform;
}
-Future<Null> enableReplayPlatform(String location) async {
+Future<FakePlatform> getReplayPlatform(String location) async {
final Directory dir = getReplaySource(location, _kRecordingType);
final File file = _getPlatformManifest(dir);
final String json = await file.readAsString();
- context.setVariable(Platform, new FakePlatform.fromJson(json));
+ return new FakePlatform.fromJson(json);
}
File _getPlatformManifest(Directory dir) {
diff --git a/packages/flutter_tools/lib/src/base/port_scanner.dart b/packages/flutter_tools/lib/src/base/port_scanner.dart
index e1c0a45..a3e7167 100644
--- a/packages/flutter_tools/lib/src/base/port_scanner.dart
+++ b/packages/flutter_tools/lib/src/base/port_scanner.dart
@@ -7,14 +7,9 @@
import 'context.dart';
import 'io.dart';
-const PortScanner _kLocalPortScanner = const HostPortScanner();
const int _kMaxSearchIterations = 20;
-PortScanner get portScanner {
- return context == null
- ? _kLocalPortScanner
- : context.putIfAbsent(PortScanner, () => _kLocalPortScanner);
-}
+PortScanner get portScanner => context[PortScanner];
abstract class PortScanner {
const PortScanner();
diff --git a/packages/flutter_tools/lib/src/base/process_manager.dart b/packages/flutter_tools/lib/src/base/process_manager.dart
index bb39578..18b5703 100644
--- a/packages/flutter_tools/lib/src/base/process_manager.dart
+++ b/packages/flutter_tools/lib/src/base/process_manager.dart
@@ -16,43 +16,32 @@
const ProcessManager _kLocalProcessManager = const LocalProcessManager();
/// The active process manager.
-ProcessManager get processManager {
- return context == null
- ? _kLocalProcessManager
- : context[ProcessManager];
-}
+ProcessManager get processManager => context[ProcessManager] ?? _kLocalProcessManager;
-/// Enables recording of process invocation activity to the specified base
-/// recording [location].
-///
-/// This sets the [active process manager](processManager) to one that records
-/// all process activity before delegating to a [LocalProcessManager].
+/// Gets a [ProcessManager] that will record process invocation activity to the
+/// specified base recording [location].
///
/// Activity will be recorded in a subdirectory of [location] named `"process"`.
/// It is permissible for [location] to represent an existing non-empty
/// directory as long as there is no collision with the `"process"`
/// subdirectory.
-void enableRecordingProcessManager(String location) {
- final ProcessManager originalProcessManager = processManager;
+RecordingProcessManager getRecordingProcessManager(String location) {
final Directory dir = getRecordingSink(location, _kRecordingType);
const ProcessManager delegate = const LocalProcessManager();
final RecordingProcessManager manager = new RecordingProcessManager(delegate, dir);
addShutdownHook(() async {
await manager.flush(finishRunningProcesses: true);
- context.setVariable(ProcessManager, originalProcessManager);
}, ShutdownStage.SERIALIZE_RECORDING);
- context.setVariable(ProcessManager, manager);
+ return manager;
}
-/// Enables process invocation replay mode.
-///
-/// This sets the [active process manager](processManager) to one that replays
-/// process activity from a previously recorded set of invocations.
+/// Gets a [ProcessManager] that replays process activity from a previously
+/// recorded set of invocations.
///
/// [location] must represent a directory to which process activity has been
/// recorded (i.e. the result of having been previously passed to
-/// [enableRecordingProcessManager]), or a [ToolExit] will be thrown.
-Future<Null> enableReplayProcessManager(String location) async {
+/// [getRecordingProcessManager]), or a [ToolExit] will be thrown.
+Future<ReplayProcessManager> getReplayProcessManager(String location) async {
final Directory dir = getReplaySource(location, _kRecordingType);
ProcessManager manager;
@@ -69,5 +58,5 @@
throwToolExit('Invalid replay-from: $error');
}
- context.setVariable(ProcessManager, manager);
+ return manager;
}
diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart
index f980e19..735ab94 100644
--- a/packages/flutter_tools/lib/src/base/utils.dart
+++ b/packages/flutter_tools/lib/src/base/utils.dart
@@ -45,7 +45,7 @@
}
bool get isRunningOnBot {
- final BotDetector botDetector = context?.getVariable(BotDetector) ?? _kBotDetector;
+ final BotDetector botDetector = context[BotDetector] ?? _kBotDetector;
return botDetector.isRunningOnBot;
}
@@ -231,7 +231,7 @@
value.toRadixString(16).padLeft(count, '0');
}
-Clock get clock => context.putIfAbsent(Clock, () => const Clock());
+Clock get clock => context[Clock];
typedef Future<Null> AsyncCallback();
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index cad13b3..4cd9bb2 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -50,21 +50,24 @@
Future<Null> runCommand() {
printStatus('Starting device daemon...');
- final AppContext appContext = new AppContext();
final NotifyingLogger notifyingLogger = new NotifyingLogger();
- appContext.setVariable(Logger, notifyingLogger);
Cache.releaseLockEarly();
- return appContext.runInZone(() async {
- final Daemon daemon = new Daemon(
- stdinCommandStream, stdoutCommandResponse,
- daemonCommand: this, notifyingLogger: notifyingLogger);
+ return context.run<Future<Null>>(
+ body: () async {
+ final Daemon daemon = new Daemon(
+ stdinCommandStream, stdoutCommandResponse,
+ daemonCommand: this, notifyingLogger: notifyingLogger);
- final int code = await daemon.onExit;
- if (code != 0)
- throwToolExit('Daemon exited with non-zero exit code: $code', exitCode: code);
- });
+ final int code = await daemon.onExit;
+ if (code != 0)
+ throwToolExit('Daemon exited with non-zero exit code: $code', exitCode: code);
+ },
+ overrides: <Type, Generator>{
+ Logger: () => notifyingLogger,
+ },
+ );
}
}
@@ -810,10 +813,12 @@
dynamic _runInZone(AppDomain domain, dynamic method()) {
_logger ??= new _AppRunLogger(domain, this, parent: logToStdout ? logger : null);
- final AppContext appContext = new AppContext();
- appContext.setVariable(Logger, _logger);
- appContext.setVariable(Stdio, const Stdio());
- return appContext.runInZone(method);
+ return context.run<dynamic>(
+ body: method,
+ overrides: <Type, Generator>{
+ Logger: () => _logger,
+ },
+ );
}
}
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 5d8bc67..9abaec3 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -4,38 +4,73 @@
import 'dart:async';
-import 'package:process/process.dart';
+import 'package:quiver/time.dart';
+import 'android/android_sdk.dart';
+import 'android/android_studio.dart';
+import 'android/android_workflow.dart';
+import 'artifacts.dart';
+import 'asset.dart';
+import 'base/build.dart';
import 'base/config.dart';
import 'base/context.dart';
-import 'base/file_system.dart';
+import 'base/flags.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/os.dart';
import 'base/platform.dart';
+import 'base/port_scanner.dart';
import 'base/utils.dart';
import 'cache.dart';
-import 'disabled_usage.dart';
+import 'devfs.dart';
+import 'device.dart';
+import 'doctor.dart';
+import 'ios/cocoapods.dart';
+import 'ios/ios_workflow.dart';
+import 'ios/mac.dart';
+import 'ios/simulators.dart';
+import 'ios/xcodeproj.dart';
+import 'run_hot.dart';
import 'usage.dart';
+import 'version.dart';
-typedef Future<Null> Runner(List<String> args);
-
-Future<Null> runInContext(List<String> args, Runner runner) {
- final AppContext executableContext = new AppContext();
- executableContext.setVariable(Logger, new StdoutLogger());
- return executableContext.runInZone(() {
- // Initialize the context with some defaults.
- // This list must be kept in sync with lib/executable.dart.
- context.putIfAbsent(BotDetector, () => const BotDetector());
- context.putIfAbsent(Stdio, () => const Stdio());
- context.putIfAbsent(Platform, () => const LocalPlatform());
- context.putIfAbsent(FileSystem, () => const LocalFileSystem());
- context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
- context.putIfAbsent(Logger, () => new StdoutLogger());
- context.putIfAbsent(Cache, () => new Cache());
- context.putIfAbsent(Config, () => new Config());
- context.putIfAbsent(OperatingSystemUtils, () => new OperatingSystemUtils());
- context.putIfAbsent(Usage, () => new DisabledUsage());
- return runner(args);
- });
+Future<T> runInContext<T>(
+ FutureOr<T> runner(), {
+ Map<Type, dynamic> overrides,
+}) async {
+ return await context.run<Future<T>>(
+ name: 'global fallbacks',
+ body: () async => await runner(),
+ overrides: overrides,
+ fallbacks: <Type, Generator>{
+ AndroidSdk: AndroidSdk.locateAndroidSdk,
+ AndroidStudio: AndroidStudio.latestValid,
+ AndroidWorkflow: () => new AndroidWorkflow(),
+ Artifacts: () => new CachedArtifacts(),
+ AssetBundleFactory: () => AssetBundleFactory.defaultInstance,
+ BotDetector: () => const BotDetector(),
+ Cache: () => new Cache(),
+ Clock: () => const Clock(),
+ CocoaPods: () => const CocoaPods(),
+ Config: () => new Config(),
+ DevFSConfig: () => new DevFSConfig(),
+ DeviceManager: () => new DeviceManager(),
+ Doctor: () => new Doctor(),
+ Flags: () => const EmptyFlags(),
+ FlutterVersion: () => new FlutterVersion(const Clock()),
+ GenSnapshot: () => const GenSnapshot(),
+ HotRunnerConfig: () => new HotRunnerConfig(),
+ IMobileDevice: () => const IMobileDevice(),
+ IOSSimulatorUtils: () => new IOSSimulatorUtils(),
+ IOSWorkflow: () => const IOSWorkflow(),
+ Logger: () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger(),
+ OperatingSystemUtils: () => new OperatingSystemUtils(),
+ PortScanner: () => const HostPortScanner(),
+ SimControl: () => new SimControl(),
+ Stdio: () => const Stdio(),
+ Usage: () => new Usage(),
+ Xcode: () => new Xcode(),
+ XcodeProjectInterpreter: () => new XcodeProjectInterpreter(),
+ },
+ );
}
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index 2e1ca55..1a70543 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -197,7 +197,7 @@
}
abstract class DoctorValidator {
- DoctorValidator(this.title);
+ const DoctorValidator(this.title);
final String title;
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart
index e020c17..1307f00 100644
--- a/packages/flutter_tools/lib/src/ios/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -31,7 +31,7 @@
brew upgrade cocoapods
pod setup''';
-CocoaPods get cocoaPods => context.putIfAbsent(CocoaPods, () => const CocoaPods());
+CocoaPods get cocoaPods => context[CocoaPods];
class CocoaPods {
const CocoaPods();
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index d561ff0..c44ce74 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -13,10 +13,10 @@
import 'cocoapods.dart';
import 'mac.dart';
-IOSWorkflow get iosWorkflow => context.putIfAbsent(IOSWorkflow, () => new IOSWorkflow());
+IOSWorkflow get iosWorkflow => context[IOSWorkflow];
class IOSWorkflow extends DoctorValidator implements Workflow {
- IOSWorkflow() : super('iOS toolchain - develop for iOS devices');
+ const IOSWorkflow() : super('iOS toolchain - develop for iOS devices');
@override
bool get appliesToHostPlatform => platform.isMacOS;
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 89497eb..7356716 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -35,9 +35,9 @@
// Homebrew.
const PythonModule kPythonSix = const PythonModule('six');
-IMobileDevice get iMobileDevice => context.putIfAbsent(IMobileDevice, () => const IMobileDevice());
+IMobileDevice get iMobileDevice => context[IMobileDevice];
-Xcode get xcode => context.putIfAbsent(Xcode, () => new Xcode());
+Xcode get xcode => context[Xcode];
class PythonModule {
const PythonModule(this.name);
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index e35d6aa..0144a2e 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -85,10 +85,7 @@
localsFile.writeAsStringSync(localsBuffer.toString());
}
-XcodeProjectInterpreter get xcodeProjectInterpreter => context.putIfAbsent(
- XcodeProjectInterpreter,
- () => new XcodeProjectInterpreter(),
-);
+XcodeProjectInterpreter get xcodeProjectInterpreter => context[XcodeProjectInterpreter];
/// Interpreter of Xcode projects.
class XcodeProjectInterpreter {
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index c65e9a7..bd9ca25 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -222,46 +222,49 @@
/// and [runCommand] to execute the command
/// so that this method can record and report the overall time to analytics.
@override
- Future<Null> run() async {
+ Future<Null> run() {
final DateTime startTime = clock.now();
- context.setVariable(FlutterCommand, this);
+ return context.run<Future<Null>>(
+ name: 'command',
+ overrides: <Type, Generator>{FlutterCommand: () => this},
+ body: () async {
+ if (flutterUsage.isFirstRun)
+ flutterUsage.printWelcome();
- if (flutterUsage.isFirstRun)
- flutterUsage.printWelcome();
+ FlutterCommandResult commandResult;
+ try {
+ commandResult = await verifyThenRunCommand();
+ } on ToolExit {
+ commandResult = const FlutterCommandResult(ExitStatus.fail);
+ rethrow;
+ } finally {
+ final DateTime endTime = clock.now();
+ printTrace('"flutter $name" took ${getElapsedAsMilliseconds(endTime.difference(startTime))}.');
+ if (usagePath != null) {
+ final List<String> labels = <String>[];
+ if (commandResult?.exitStatus != null)
+ labels.add(getEnumName(commandResult.exitStatus));
+ if (commandResult?.timingLabelParts?.isNotEmpty ?? false)
+ labels.addAll(commandResult.timingLabelParts);
- FlutterCommandResult commandResult;
- try {
- commandResult = await verifyThenRunCommand();
- } on ToolExit {
- commandResult = const FlutterCommandResult(ExitStatus.fail);
- rethrow;
- } finally {
- final DateTime endTime = clock.now();
- printTrace('"flutter $name" took ${getElapsedAsMilliseconds(endTime.difference(startTime))}.');
- if (usagePath != null) {
- final List<String> labels = <String>[];
- if (commandResult?.exitStatus != null)
- labels.add(getEnumName(commandResult.exitStatus));
- if (commandResult?.timingLabelParts?.isNotEmpty ?? false)
- labels.addAll(commandResult.timingLabelParts);
-
- final String label = labels
- .where((String label) => !isBlank(label))
- .join('-');
- flutterUsage.sendTiming(
- 'flutter',
- name,
- // If the command provides its own end time, use it. Otherwise report
- // the duration of the entire execution.
- (commandResult?.endTimeOverride ?? endTime).difference(startTime),
- // Report in the form of `success-[parameter1-parameter2]`, all of which
- // can be null if the command doesn't provide a FlutterCommandResult.
- label: label == '' ? null : label,
- );
- }
- }
-
+ final String label = labels
+ .where((String label) => !isBlank(label))
+ .join('-');
+ flutterUsage.sendTiming(
+ 'flutter',
+ name,
+ // If the command provides its own end time, use it. Otherwise report
+ // the duration of the entire execution.
+ (commandResult?.endTimeOverride ?? endTime).difference(startTime),
+ // Report in the form of `success-[parameter1-parameter2]`, all of which
+ // can be null if the command doesn't provide a FlutterCommandResult.
+ label: label == '' ? null : label,
+ );
+ }
+ }
+ },
+ );
}
/// Perform validation then call [runCommand] to execute the command.
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index 14b3ac4..940309a 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -7,8 +7,10 @@
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
+import 'package:file/file.dart';
+import 'package:platform/platform.dart';
+import 'package:process/process.dart';
-import '../android/android_sdk.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
@@ -168,12 +170,14 @@
@override
Future<Null> runCommand(ArgResults topLevelResults) async {
- context.setVariable(Flags, new Flags(topLevelResults));
+ final Map<Type, dynamic> contextOverrides = <Type, dynamic>{
+ Flags: new Flags(topLevelResults),
+ };
// Check for verbose.
if (topLevelResults['verbose']) {
// Override the logger.
- context.setVariable(Logger, new VerboseLogger(context[Logger]));
+ contextOverrides[Logger] = new VerboseLogger(logger);
}
String recordTo = topLevelResults['record-to'];
@@ -214,9 +218,11 @@
recordTo = recordTo.trim();
if (recordTo.isEmpty)
throwToolExit('record-to location not specified');
- enableRecordingProcessManager(recordTo);
- enableRecordingFileSystem(recordTo);
- await enableRecordingPlatform(recordTo);
+ contextOverrides.addAll(<Type, dynamic>{
+ ProcessManager: getRecordingProcessManager(recordTo),
+ FileSystem: getRecordingFileSystem(recordTo),
+ Platform: await getRecordingPlatform(recordTo),
+ });
VMService.enableRecordingConnection(recordTo);
}
@@ -224,66 +230,74 @@
replayFrom = replayFrom.trim();
if (replayFrom.isEmpty)
throwToolExit('replay-from location not specified');
- await enableReplayProcessManager(replayFrom);
- enableReplayFileSystem(replayFrom);
- await enableReplayPlatform(replayFrom);
+ contextOverrides.addAll(<Type, dynamic>{
+ ProcessManager: await getReplayProcessManager(replayFrom),
+ FileSystem: getReplayFileSystem(replayFrom),
+ Platform: await getReplayPlatform(replayFrom),
+ });
VMService.enableReplayConnection(replayFrom);
}
- logger.quiet = topLevelResults['quiet'];
-
- if (topLevelResults.wasParsed('color'))
- logger.supportsColor = topLevelResults['color'];
-
- // We must set Cache.flutterRoot early because other features use it (e.g.
- // enginePath's initializer uses it).
- final String flutterRoot = topLevelResults['flutter-root'] ?? _defaultFlutterRoot;
- Cache.flutterRoot = fs.path.normalize(fs.path.absolute(flutterRoot));
-
- if (platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true')
- await Cache.lock();
-
- if (topLevelResults['suppress-analytics'])
- flutterUsage.suppressAnalytics = true;
-
- _checkFlutterCopy();
- await FlutterVersion.instance.ensureVersionFile();
- if (topLevelResults.command?.name != 'upgrade') {
- await FlutterVersion.instance.checkFlutterVersionFreshness();
- }
-
- if (topLevelResults.wasParsed('packages'))
- PackageMap.globalPackagesPath = fs.path.normalize(fs.path.absolute(topLevelResults['packages']));
-
- // See if the user specified a specific device.
- deviceManager.specifiedDeviceId = topLevelResults['device-id'];
-
// Set up the tooling configuration.
final String enginePath = _findEnginePath(topLevelResults);
if (enginePath != null) {
- Artifacts.useLocalEngine(enginePath, _findEngineBuildPath(topLevelResults, enginePath));
+ contextOverrides.addAll(<Type, dynamic>{
+ Artifacts: Artifacts.getLocalEngine(enginePath, _findEngineBuildPath(topLevelResults, enginePath)),
+ });
}
- // The Android SDK could already have been set by tests.
- context.putIfAbsent(AndroidSdk, AndroidSdk.locateAndroidSdk);
+ await context.run<Future<Null>>(
+ overrides: contextOverrides.map<Type, Generator>((Type type, dynamic value) {
+ return new MapEntry<Type, Generator>(type, () => value);
+ }),
+ body: () async {
+ logger.quiet = topLevelResults['quiet'];
- if (topLevelResults['version']) {
- flutterUsage.sendCommand('version');
- String status;
- if (topLevelResults['machine']) {
- status = const JsonEncoder.withIndent(' ').convert(FlutterVersion.instance.toJson());
- } else {
- status = FlutterVersion.instance.toString();
- }
- printStatus(status);
- return;
- }
+ if (topLevelResults.wasParsed('color'))
+ logger.supportsColor = topLevelResults['color'];
- if (topLevelResults['machine']) {
- throwToolExit('The --machine flag is only valid with the --version flag.', exitCode: 2);
- }
+ // We must set Cache.flutterRoot early because other features use it (e.g.
+ // enginePath's initializer uses it).
+ final String flutterRoot = topLevelResults['flutter-root'] ?? _defaultFlutterRoot;
+ Cache.flutterRoot = fs.path.normalize(fs.path.absolute(flutterRoot));
- await super.runCommand(topLevelResults);
+ if (platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true')
+ await Cache.lock();
+
+ if (topLevelResults['suppress-analytics'])
+ flutterUsage.suppressAnalytics = true;
+
+ _checkFlutterCopy();
+ await FlutterVersion.instance.ensureVersionFile();
+ if (topLevelResults.command?.name != 'upgrade') {
+ await FlutterVersion.instance.checkFlutterVersionFreshness();
+ }
+
+ if (topLevelResults.wasParsed('packages'))
+ PackageMap.globalPackagesPath = fs.path.normalize(fs.path.absolute(topLevelResults['packages']));
+
+ // See if the user specified a specific device.
+ deviceManager.specifiedDeviceId = topLevelResults['device-id'];
+
+ if (topLevelResults['version']) {
+ flutterUsage.sendCommand('version');
+ String status;
+ if (topLevelResults['machine']) {
+ status = const JsonEncoder.withIndent(' ').convert(FlutterVersion.instance.toJson());
+ } else {
+ status = FlutterVersion.instance.toString();
+ }
+ printStatus(status);
+ return;
+ }
+
+ if (topLevelResults['machine']) {
+ throwToolExit('The --machine flag is only valid with the --version flag.', exitCode: 2);
+ }
+
+ await super.runCommand(topLevelResults);
+ },
+ );
}
String _tryEnginePath(String enginePath) {
diff --git a/packages/flutter_tools/lib/src/usage.dart b/packages/flutter_tools/lib/src/usage.dart
index e1efafb..1202a19 100644
--- a/packages/flutter_tools/lib/src/usage.dart
+++ b/packages/flutter_tools/lib/src/usage.dart
@@ -49,7 +49,7 @@
}
/// Returns [Usage] active in the current app context.
- static Usage get instance => context.putIfAbsent(Usage, () => new Usage());
+ static Usage get instance => context[Usage];
Analytics _analytics;
diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart
index 30b74c0..7e88904 100644
--- a/packages/flutter_tools/lib/src/version.dart
+++ b/packages/flutter_tools/lib/src/version.dart
@@ -167,7 +167,7 @@
await _run(<String>['git', 'remote', 'remove', _kVersionCheckRemote]);
}
- static FlutterVersion get instance => context.putIfAbsent(FlutterVersion, () => new FlutterVersion(const Clock()));
+ static FlutterVersion get instance => context[FlutterVersion];
/// Return a short string for the version (e.g. `master/0.0.59-pre.92`, `scroll_refactor/a76bc8e22b`).
String getVersionString({bool redactUnknownBranches: false}) {