add --dart-defines option (#44083)

diff --git a/packages/flutter_tools/lib/src/aot.dart b/packages/flutter_tools/lib/src/aot.dart
index 71e1558..50be745 100644
--- a/packages/flutter_tools/lib/src/aot.dart
+++ b/packages/flutter_tools/lib/src/aot.dart
@@ -34,6 +34,7 @@
     Iterable<DarwinArch> iosBuildArchs = defaultIOSArchs,
     List<String> extraFrontEndOptions,
     List<String> extraGenSnapshotOptions,
+    @required List<String> dartDefines,
   }) async {
     if (platform == null) {
       throwToolExit('No AOT build platform specified');
@@ -78,6 +79,7 @@
         trackWidgetCreation: false,
         outputPath: outputPath,
         extraFrontEndOptions: extraFrontEndOptions,
+        dartDefines: dartDefines,
       );
       if (kernelOut == null) {
         throwToolExit('Compiler terminated unexpectedly.');
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index d0f1642..9a70a94 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -285,6 +285,7 @@
     @required String packagesPath,
     @required String outputPath,
     @required bool trackWidgetCreation,
+    @required List<String> dartDefines,
     List<String> extraFrontEndOptions = const <String>[],
   }) async {
     final FlutterProject flutterProject = FlutterProject.current();
@@ -315,6 +316,7 @@
       aot: true,
       buildMode: buildMode,
       trackWidgetCreation: trackWidgetCreation,
+      dartDefines: dartDefines,
     ));
 
     // Write path to frontend_server, since things need to be re-generated when that changes.
diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
index 36537c3..3613dad 100644
--- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
@@ -46,6 +46,7 @@
     @required FlutterProject flutterProject,
     @required bool ipv6,
     @required DebuggingOptions debuggingOptions,
+    @required List<String> dartDefines,
   }) {
     if (featureFlags.isWebIncrementalCompilerEnabled) {
       return _ExperimentalResidentWebRunner(
@@ -55,6 +56,7 @@
         debuggingOptions: debuggingOptions,
         ipv6: ipv6,
         stayResident: stayResident,
+        dartDefines: dartDefines,
       );
     }
     return _DwdsResidentWebRunner(
@@ -64,6 +66,7 @@
       debuggingOptions: debuggingOptions,
       ipv6: ipv6,
       stayResident: stayResident,
+      dartDefines: dartDefines,
     );
   }
 }
@@ -77,6 +80,7 @@
     @required bool ipv6,
     @required DebuggingOptions debuggingOptions,
     bool stayResident = true,
+    @required this.dartDefines,
   }) : super(
           <FlutterDevice>[],
           target: target ?? fs.path.join('lib', 'main.dart'),
@@ -87,6 +91,7 @@
 
   final FlutterDevice device;
   final FlutterProject flutterProject;
+  final List<String> dartDefines;
   DateTime firstBuildTime;
 
   // Only the debug builds of the web support the service protocol.
@@ -348,6 +353,7 @@
     @required bool ipv6,
     @required DebuggingOptions debuggingOptions,
     bool stayResident = true,
+    @required List<String> dartDefines,
   }) : super(
           device,
           flutterProject: flutterProject,
@@ -355,6 +361,7 @@
           debuggingOptions: debuggingOptions,
           ipv6: ipv6,
           stayResident: stayResident,
+          dartDefines: dartDefines,
         );
 
   @override
@@ -540,6 +547,7 @@
     @required bool ipv6,
     @required DebuggingOptions debuggingOptions,
     bool stayResident = true,
+    @required List<String> dartDefines,
   }) : super(
           device,
           flutterProject: flutterProject,
@@ -547,6 +555,7 @@
           debuggingOptions: debuggingOptions,
           ipv6: ipv6,
           stayResident: stayResident,
+          dartDefines: dartDefines,
         );
 
   @override
@@ -594,6 +603,7 @@
           hostname: debuggingOptions.hostname,
           port: debuggingOptions.port,
           skipDwds: device is WebServerDevice || !debuggingOptions.buildInfo.isDebug,
+          dartDefines: dartDefines,
         );
         // When connecting to a browser, update the message with a seemsSlow notification
         // to handle the case where we fail to connect.
diff --git a/packages/flutter_tools/lib/src/build_runner/web_fs.dart b/packages/flutter_tools/lib/src/build_runner/web_fs.dart
index da25e08..688d2d3 100644
--- a/packages/flutter_tools/lib/src/build_runner/web_fs.dart
+++ b/packages/flutter_tools/lib/src/build_runner/web_fs.dart
@@ -81,6 +81,7 @@
   @required bool initializePlatform,
   @required String hostname,
   @required String port,
+  @required List<String> dartDefines,
 });
 
 /// The dev filesystem responsible for building and serving  web applications.
@@ -97,6 +98,7 @@
     this._target,
     this._buildInfo,
     this._initializePlatform,
+    this._dartDefines,
   );
 
   /// The server uri.
@@ -111,6 +113,7 @@
   final String _target;
   final BuildInfo _buildInfo;
   final bool _initializePlatform;
+  final List<String> _dartDefines;
   StreamSubscription<void> _connectedApps;
 
   static const String _kHostName = 'localhost';
@@ -146,7 +149,7 @@
   /// Recompile the web application and return whether this was successful.
   Future<bool> recompile() async {
     if (!_useBuildRunner) {
-      await buildWeb(_flutterProject, _target, _buildInfo, _initializePlatform);
+      await buildWeb(_flutterProject, _target, _buildInfo, _initializePlatform, _dartDefines);
       return true;
     }
     _client.startBuild();
@@ -173,6 +176,7 @@
     @required bool initializePlatform,
     @required String hostname,
     @required String port,
+    @required List<String> dartDefines,
   }) async {
     // workaround for https://github.com/flutter/flutter/issues/38290
     if (!flutterProject.dartTool.existsSync()) {
@@ -302,7 +306,7 @@
         handler = pipeline.addHandler(proxyHandler('http://localhost:$daemonAssetPort/web/'));
       }
     } else {
-      await buildWeb(flutterProject, target, buildInfo, initializePlatform);
+      await buildWeb(flutterProject, target, buildInfo, initializePlatform, dartDefines);
       firstBuildCompleter.complete(true);
     }
 
@@ -325,6 +329,7 @@
       target,
       buildInfo,
       initializePlatform,
+      dartDefines,
     );
     if (!await firstBuildCompleter.future) {
       throw const BuildException();
diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
index 8f00e82..392cc34 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
@@ -7,6 +7,7 @@
 import '../../base/file_system.dart';
 import '../../build_info.dart';
 import '../../compile.dart';
+import '../../convert.dart';
 import '../../globals.dart';
 import '../../project.dart';
 import '../build_system.dart';
@@ -50,6 +51,9 @@
 /// If provided, must be used along with [kFileSystemScheme].
 const String kFileSystemRoots = 'FileSystemRoots';
 
+/// Defines specified via the `--dart-define` command-line option.
+const String kDartDefines = 'DartDefines';
+
 /// The define to control what iOS architectures are built for.
 ///
 /// This is expected to be a comma-separated list of architectures. If not
@@ -208,6 +212,7 @@
       extraFrontEndOptions: extraFrontEndOptions,
       fileSystemRoots: fileSystemRoots,
       fileSystemScheme: fileSystemScheme,
+      dartDefines: parseDartDefines(environment),
     );
     if (output == null || output.errorCount != 0) {
       throw Exception('Errors during snapshot creation: $output');
@@ -357,3 +362,22 @@
     AotElfRelease(),
   ];
 }
+
+/// Dart defines are encoded inside [Environment] as a JSON array.
+List<String> parseDartDefines(Environment environment) {
+  if (!environment.defines.containsKey(kDartDefines)) {
+    return const <String>[];
+  }
+
+  final String dartDefinesJson = environment.defines[kDartDefines];
+  try {
+    final List<Object> parsedDefines = jsonDecode(dartDefinesJson);
+    return parsedDefines.cast<String>();
+  } on FormatException catch (_) {
+    throw Exception(
+      'The value of -D$kDartDefines is not formatted correctly.\n'
+      'The value must be a JSON-encoded list of strings but was:\n'
+      '$dartDefinesJson'
+    );
+  }
+}
diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart
index 4c4946f..11b01ec 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/web.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart
@@ -130,6 +130,7 @@
       ? PackageMap.globalGeneratedPackagesPath
       : PackageMap.globalPackagesPath;
     final File outputFile = environment.buildDir.childFile('main.dart.js');
+
     final ProcessResult result = await processManager.run(<String>[
       artifacts.getArtifactPath(Artifact.engineDartBinary),
       artifacts.getArtifactPath(Artifact.dart2jsSnapshot),
@@ -147,6 +148,8 @@
         '-Ddart.vm.profile=true'
       else
         '-Ddart.vm.product=true',
+      for (String dartDefine in parseDartDefines(environment))
+        '-D$dartDefine',
       environment.buildDir.childFile('main.dart').path,
     ]);
     if (result.exitCode != 0) {
diff --git a/packages/flutter_tools/lib/src/codegen.dart b/packages/flutter_tools/lib/src/codegen.dart
index bd66869..a85bd86 100644
--- a/packages/flutter_tools/lib/src/codegen.dart
+++ b/packages/flutter_tools/lib/src/codegen.dart
@@ -109,6 +109,7 @@
     TargetModel targetModel = TargetModel.flutter,
     String initializeFromDill,
     String platformDill,
+    List<String> dartDefines,
   }) async {
     if (fileSystemRoots != null || fileSystemScheme != null || depFilePath != null || targetModel != null || sdkRoot != null || packagesPath != null) {
       printTrace('fileSystemRoots, fileSystemScheme, depFilePath, targetModel,'
@@ -147,6 +148,7 @@
       depFilePath: depFilePath,
       targetModel: targetModel,
       initializeFromDill: initializeFromDill,
+      dartDefines: dartDefines,
     );
   }
 }
@@ -172,6 +174,7 @@
     String initializeFromDill,
     bool runCold = false,
     TargetPlatform targetPlatform,
+    @required List<String> dartDefines,
   }) async {
     codeGenerator.updatePackages(flutterProject);
     final ResidentCompiler residentCompiler = ResidentCompiler(
@@ -191,6 +194,7 @@
       targetModel: TargetModel.flutter,
       unsafePackageSerialization: unsafePackageSerialization,
       initializeFromDill: initializeFromDill,
+      dartDefines: dartDefines,
     );
     if (runCold) {
       return residentCompiler;
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index d6aec17..b9f35b5 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -61,6 +61,7 @@
     usesIpv6Flag();
     usesFilesystemOptions(hide: !verboseHelp);
     usesFuchsiaOptions(hide: !verboseHelp);
+    usesDartDefines();
     argParser
       ..addOption(
         'debug-port',
@@ -274,6 +275,7 @@
         target: argResults['target'],
         targetModel: TargetModel(argResults['target-model']),
         buildMode: getBuildMode(),
+        dartDefines: dartDefines,
       );
       flutterDevice.observatoryUris = <Uri>[ observatoryUri ];
       final List<FlutterDevice> flutterDevices =  <FlutterDevice>[flutterDevice];
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index b69b1bc..92a8f86 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -18,6 +18,7 @@
     usesTargetOption();
     addBuildModeFlags();
     usesPubOption();
+    usesDartDefines();
     argParser
       ..addOption('output-dir', defaultsTo: getAotBuildDirectory())
       ..addOption('target-platform',
@@ -86,6 +87,7 @@
       iosBuildArchs: argResults['ios-arch'].map<DarwinArch>(getIOSArchForName),
       extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
       extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
+      dartDefines: dartDefines,
     );
     return null;
   }
diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart
index 12d9f36..07e8199 100644
--- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart
+++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart
@@ -35,6 +35,7 @@
     usesTargetOption();
     usesFlavorOption();
     usesPubOption();
+    usesDartDefines();
     argParser
       ..addFlag('debug',
         negatable: true,
@@ -294,6 +295,7 @@
       quiet: true,
       reportTimings: false,
       iosBuildArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64],
+      dartDefines: dartDefines,
     );
 
     const String appFrameworkName = 'App.framework';
diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart
index e731150..60b5863 100644
--- a/packages/flutter_tools/lib/src/commands/build_web.dart
+++ b/packages/flutter_tools/lib/src/commands/build_web.dart
@@ -18,6 +18,7 @@
     usesTargetOption();
     usesPubOption();
     addBuildModeFlags(excludeDebug: true);
+    usesDartDefines();
     argParser.addFlag('web-initialize-platform',
         defaultsTo: true,
         negatable: true,
@@ -53,7 +54,13 @@
     if (buildInfo.isDebug) {
       throwToolExit('debug builds cannot be built directly for the web. Try using "flutter run"');
     }
-    await buildWeb(flutterProject, target, buildInfo, argResults['web-initialize-platform']);
+    await buildWeb(
+      flutterProject,
+      target,
+      buildInfo,
+      argResults['web-initialize-platform'],
+      dartDefines,
+    );
     return null;
   }
 }
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index d37bfe0..fed2bd0 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -424,6 +424,7 @@
       viewFilter: isolateFilter,
       target: target,
       buildMode: options.buildInfo.mode,
+      dartDefines: command?.dartDefines,
     );
 
     ResidentRunner runner;
@@ -436,6 +437,7 @@
         debuggingOptions: options,
         ipv6: ipv6,
         stayResident: true,
+        dartDefines: command?.dartDefines,
       );
     } else if (enableHotReload) {
       runner = HotRunner(
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index b410da2..6f10357 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -31,6 +31,7 @@
   // Used by run and drive commands.
   RunCommandBase({ bool verboseHelp = false }) {
     addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp);
+    usesDartDefines();
     usesFlavorOption();
     argParser
       ..addFlag('trace-startup',
@@ -426,6 +427,7 @@
           experimentalFlags: expFlags,
           target: argResults['target'],
           buildMode: getBuildMode(),
+          dartDefines: dartDefines,
         ),
     ];
     // Only support "web mode" with a single web device due to resident runner
@@ -459,6 +461,7 @@
         ipv6: ipv6,
         debuggingOptions: _createDebuggingOptions(),
         stayResident: stayResident,
+        dartDefines: dartDefines,
       );
     } else {
       runner = ColdRunner(
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 4dfeba8..d31d1d0 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -288,6 +288,7 @@
     String fileSystemScheme,
     String initializeFromDill,
     String platformDill,
+    @required List<String> dartDefines,
   }) async {
     final String frontendServer = artifacts.getArtifactPath(
       Artifact.frontendServerSnapshotForEngineDartSdk
@@ -316,6 +317,8 @@
       sdkRoot,
       '--target=$targetModel',
       '-Ddart.developer.causal_async_stacks=$causalAsyncStacks',
+      for (Object dartDefine in dartDefines)
+        '-D$dartDefine',
       ..._buildModeOptions(buildMode),
       if (trackWidgetCreation) '--track-widget-creation',
       if (!linkPlatformKernelIn) '--no-link-platform',
@@ -464,6 +467,7 @@
     bool unsafePackageSerialization,
     List<String> experimentalFlags,
     String platformDill,
+    List<String> dartDefines,
   }) = DefaultResidentCompiler;
 
 
@@ -524,8 +528,10 @@
     this.unsafePackageSerialization,
     this.experimentalFlags,
     this.platformDill,
+    List<String> dartDefines,
   }) : assert(sdkRoot != null),
        _stdoutHandler = StdoutHandler(consumer: compilerMessageConsumer),
+       dartDefines = dartDefines ?? const <String>[],
        // This is a URI, not a file path, so the forward slash is correct even on Windows.
        sdkRoot = sdkRoot.endsWith('/') ? sdkRoot : '$sdkRoot/';
 
@@ -539,6 +545,7 @@
   final String initializeFromDill;
   final bool unsafePackageSerialization;
   final List<String> experimentalFlags;
+  final List<String> dartDefines;
 
   /// The path to the root of the Dart SDK used to compile.
   ///
@@ -594,9 +601,9 @@
 
     if (_server == null) {
       return _compile(
-          _mapFilename(request.mainPath, packageUriMapper),
-          request.outputPath,
-          _mapFilename(request.packagesFilePath ?? packagesPath, /* packageUriMapper= */ null),
+        _mapFilename(request.mainPath, packageUriMapper),
+        request.outputPath,
+        _mapFilename(request.packagesFilePath ?? packagesPath, /* packageUriMapper= */ null),
       );
     }
 
@@ -648,6 +655,8 @@
       '--incremental',
       '--target=$targetModel',
       '-Ddart.developer.causal_async_stacks=$causalAsyncStacks',
+      for (Object dartDefine in dartDefines)
+        '-D$dartDefine',
       if (outputPath != null) ...<String>[
         '--output-dill',
         outputPath,
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 0e465b7..9b3ebb6 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -41,6 +41,7 @@
     List<String> experimentalFlags,
     ResidentCompiler generator,
     @required BuildMode buildMode,
+    List<String> dartDefines,
   }) : assert(trackWidgetCreation != null),
        generator = generator ?? ResidentCompiler(
          artifacts.getArtifactPath(
@@ -54,6 +55,7 @@
          fileSystemScheme: fileSystemScheme,
          targetModel: targetModel,
          experimentalFlags: experimentalFlags,
+         dartDefines: dartDefines,
        );
 
   /// Create a [FlutterDevice] with optional code generation enabled.
@@ -69,6 +71,7 @@
     TargetModel targetModel = TargetModel.flutter,
     List<String> experimentalFlags,
     ResidentCompiler generator,
+    List<String> dartDefines,
   }) async {
     ResidentCompiler generator;
     final TargetPlatform targetPlatform = await device.targetPlatform;
@@ -86,12 +89,14 @@
         targetModel: TargetModel.dartdevc,
         experimentalFlags: experimentalFlags,
         platformDill: artifacts.getArtifactPath(Artifact.webPlatformKernelDill, mode: buildMode),
+        dartDefines: dartDefines,
       );
     } else if (flutterProject.hasBuilders) {
       generator = await CodeGeneratingResidentCompiler.create(
         targetPlatform: targetPlatform,
         buildMode: buildMode,
         flutterProject: flutterProject,
+        dartDefines: dartDefines,
       );
     } else {
       generator = ResidentCompiler(
@@ -106,6 +111,7 @@
         fileSystemScheme: fileSystemScheme,
         targetModel: targetModel,
         experimentalFlags: experimentalFlags,
+        dartDefines: dartDefines,
       );
     }
     return FlutterDevice(
@@ -119,6 +125,7 @@
       targetPlatform: targetPlatform,
       generator: generator,
       buildMode: buildMode,
+      dartDefines: dartDefines,
     );
   }
 
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index cdbad2d..81ec070 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -269,6 +269,20 @@
         valueHelp: 'x.y.z');
   }
 
+  void usesDartDefines() {
+    argParser.addMultiOption(
+      'dart-define',
+      help: 'Passed to the Dart compiler building this application as a -D flag.\n'
+            'Values supported by this option are compiler implementation specific.\n'
+            'Multiple defines can be passed by repeating --dart-define multiple times.',
+      valueHelp: 'FOO=bar',
+      hide: true,
+    );
+  }
+
+  /// The values passed via the `--dart-define` option.
+  List<String> get dartDefines => argResults['dart-define'];
+
   void usesIsolateFilterOption({ @required bool hide }) {
     argParser.addOption('isolate-filter',
       defaultsTo: null,
diff --git a/packages/flutter_tools/lib/src/test/test_compiler.dart b/packages/flutter_tools/lib/src/test/test_compiler.dart
index 8bb3a65..dd37a5f 100644
--- a/packages/flutter_tools/lib/src/test/test_compiler.dart
+++ b/packages/flutter_tools/lib/src/test/test_compiler.dart
@@ -103,6 +103,7 @@
         // We already ran codegen once at the start, we only need to
         // configure builders.
         runCold: true,
+        dartDefines: const <String>[],
       );
     }
     return ResidentCompiler(
@@ -113,6 +114,7 @@
       compilerMessageConsumer: _reportCompilerMessage,
       initializeFromDill: testFilePath,
       unsafePackageSerialization: false,
+      dartDefines: const <String>[],
     );
   }
 
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index 51413ad..f0d9524 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -12,6 +12,7 @@
 import '../build_system/build_system.dart';
 import '../build_system/targets/dart.dart';
 import '../build_system/targets/web.dart';
+import '../convert.dart';
 import '../globals.dart';
 import '../platform_plugins.dart';
 import '../plugins.dart';
@@ -21,7 +22,13 @@
 /// The [WebCompilationProxy] instance.
 WebCompilationProxy get webCompilationProxy => context.get<WebCompilationProxy>();
 
-Future<void> buildWeb(FlutterProject flutterProject, String target, BuildInfo buildInfo, bool initializePlatform) async {
+Future<void> buildWeb(
+  FlutterProject flutterProject,
+  String target,
+  BuildInfo buildInfo,
+  bool initializePlatform,
+  List<String> dartDefines,
+) async {
   if (!flutterProject.web.existsSync()) {
     throwToolExit('Missing index.html.');
   }
@@ -42,6 +49,7 @@
         kTargetFile: target,
         kInitializePlatform: initializePlatform.toString(),
         kHasWebPlugins: hasWebPlugins.toString(),
+        kDartDefines: jsonEncode(dartDefines),
       },
     ));
     if (!result.success) {
diff --git a/packages/flutter_tools/lib/src/web/web_runner.dart b/packages/flutter_tools/lib/src/web/web_runner.dart
index 09402a3..ad41c5e 100644
--- a/packages/flutter_tools/lib/src/web/web_runner.dart
+++ b/packages/flutter_tools/lib/src/web/web_runner.dart
@@ -23,5 +23,6 @@
     @required FlutterProject flutterProject,
     @required bool ipv6,
     @required DebuggingOptions debuggingOptions,
+    @required List<String> dartDefines,
   });
 }
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart
index b11f78e..3709ccf 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart
@@ -57,6 +57,7 @@
       fs.path.join('lib', 'main.dart'),
       BuildInfo.debug,
       false,
+      const <String>[],
     ), throwsA(isInstanceOf<ToolExit>()));
   }));
 
@@ -69,6 +70,7 @@
       ipv6: false,
       debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
       stayResident: true,
+      dartDefines: const <String>[],
     );
     expect(await runner.run(), 1);
   }));
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
index b13a303..cb4f01e 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -2,20 +2,30 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
 import 'package:args/command_runner.dart';
 import 'package:flutter_tools/src/application_package.dart';
 import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/build_info.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/run.dart';
 import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/features.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
 import 'package:flutter_tools/src/runner/flutter_command.dart';
 import 'package:flutter_tools/src/version.dart';
+import 'package:flutter_tools/src/web/web_runner.dart';
 import 'package:mockito/mockito.dart';
 
 import '../../src/common.dart';
 import '../../src/context.dart';
 import '../../src/mocks.dart';
+import '../../src/testbed.dart';
 
 void main() {
   group('run', () {
@@ -178,6 +188,63 @@
     }, overrides: <Type, Generator>{
       DeviceManager: () => mockDeviceManager,
     });
+
+    group('--dart-define option', () {
+      MemoryFileSystem fs;
+      MockProcessManager mockProcessManager;
+      MockWebRunnerFactory mockWebRunnerFactory;
+
+      setUpAll(() {
+        when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
+          return Stream<Device>.fromIterable(<Device>[
+            FakeDevice().._targetPlatform = TargetPlatform.web_javascript,
+          ]);
+        });
+      });
+
+      RunCommand command;
+      List<String> args;
+      setUp(() {
+        command = TestRunCommand();
+        args = <String> [
+          'run',
+          '--dart-define=FOO=bar',
+          '--no-hot',
+          '--no-pub',
+        ];
+        applyMocksToCommand(command);
+        fs = MemoryFileSystem();
+        mockProcessManager = MockProcessManager();
+        mockWebRunnerFactory = MockWebRunnerFactory();
+      });
+
+      testUsingContext('populates the environment', () async {
+        final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_run_test.');
+        fs.currentDirectory = tempDir;
+
+        final Directory libDir = tempDir.childDirectory('lib');
+        libDir.createSync();
+        final File mainFile = libDir.childFile('main.dart');
+        mainFile.writeAsStringSync('void main() {}');
+
+        final Directory webDir = tempDir.childDirectory('web');
+        webDir.createSync();
+        final File indexFile = libDir.childFile('index.html');
+        indexFile.writeAsStringSync('<h1>Hello</h1>');
+
+        await createTestCommandRunner(command).run(args);
+        expect(mockWebRunnerFactory._dartDefines, <String>['FOO=bar']);
+      }, overrides: <Type, Generator>{
+        FeatureFlags: () => TestFeatureFlags(
+          isWebEnabled: true,
+        ),
+        FileSystem: () => fs,
+        ProcessManager: () => mockProcessManager,
+        DeviceManager: () => mockDeviceManager,
+        FlutterVersion: () => mockStableFlutterVersion,
+        WebRunnerFactory: () => mockWebRunnerFactory,
+      });
+    });
   });
 }
 
@@ -207,7 +274,7 @@
 class FakeDevice extends Fake implements Device {
   static const int kSuccess = 1;
   static const int kFailure = -1;
-  final TargetPlatform _targetPlatform = TargetPlatform.ios;
+  TargetPlatform _targetPlatform = TargetPlatform.ios;
 
   void _throwToolExit(int code) => throwToolExit(null, exitCode: code);
 
@@ -263,3 +330,32 @@
     return null;
   }
 }
+
+class MockWebRunnerFactory extends Mock implements WebRunnerFactory {
+  List<String> _dartDefines;
+
+  @override
+  ResidentRunner createWebRunner(
+    FlutterDevice device, {
+    String target,
+    bool stayResident,
+    FlutterProject flutterProject,
+    bool ipv6,
+    DebuggingOptions debuggingOptions,
+    List<String> dartDefines,
+  }) {
+    _dartDefines = dartDefines;
+    return MockWebRunner();
+  }
+}
+
+class MockWebRunner extends Mock implements ResidentRunner {
+  @override
+  Future<int> run({
+    Completer<DebugConnectionInfo> connectionInfoCompleter,
+    Completer<void> appStartedCompleter,
+    String route,
+  }) async {
+    return 0;
+  }
+}
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart
index 5e136fb..600e490 100644
--- a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart
@@ -136,6 +136,7 @@
       fileSystemScheme: anyNamed('fileSystemScheme'),
       platformDill: anyNamed('platformDill'),
       initializeFromDill: anyNamed('initializeFromDill'),
+      dartDefines: anyNamed('dartDefines'),
     )).thenAnswer((Invocation invocation) async {
       return null;
     });
@@ -163,6 +164,7 @@
       fileSystemRoots: anyNamed('fileSystemRoots'),
       fileSystemScheme: anyNamed('fileSystemScheme'),
       linkPlatformKernelIn: anyNamed('linkPlatformKernelIn'),
+      dartDefines: anyNamed('dartDefines'),
     )).thenAnswer((Invocation _) async {
       return const CompilerOutput('example', 0, <Uri>[]);
     });
@@ -191,6 +193,7 @@
       fileSystemRoots: anyNamed('fileSystemRoots'),
       fileSystemScheme: anyNamed('fileSystemScheme'),
       linkPlatformKernelIn: false,
+      dartDefines: anyNamed('dartDefines'),
     )).thenAnswer((Invocation _) async {
       return const CompilerOutput('example', 0, <Uri>[]);
     });
@@ -221,6 +224,7 @@
       fileSystemRoots: anyNamed('fileSystemRoots'),
       fileSystemScheme: anyNamed('fileSystemScheme'),
       linkPlatformKernelIn: false,
+      dartDefines: anyNamed('dartDefines'),
     )).thenAnswer((Invocation _) async {
       return const CompilerOutput('example', 0, <Uri>[]);
     });
@@ -431,6 +435,7 @@
     String fileSystemScheme,
     String platformDill,
     String initializeFromDill,
+    List<String> dartDefines,
   }) async {
     fs.file(outputFilePath).createSync(recursive: true);
     return CompilerOutput(outputFilePath, 0, null);
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
index 816ff55..7c4b2b4 100644
--- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
@@ -236,6 +236,80 @@
   }, overrides: <Type, Generator>{
     ProcessManager: () => MockProcessManager(),
   }));
+
+  test('Dart2JSTarget calls dart2js with Dart defines in release mode', () => testbed.run(() async {
+    environment.defines[kBuildMode] = 'release';
+    environment.defines[kDartDefines] = '["FOO=bar","BAZ=qux"]';
+    when(processManager.run(any)).thenAnswer((Invocation invocation) async {
+      return FakeProcessResult(exitCode: 0);
+    });
+    await const Dart2JSTarget().build(environment);
+
+    final List<String> expected = <String>[
+      fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
+      fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
+      '--libraries-spec=' + fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'),
+      '-O4',
+      '-o',
+      environment.buildDir.childFile('main.dart.js').absolute.path,
+      '--packages=.packages',
+      '-Ddart.vm.product=true',
+      '-DFOO=bar',
+      '-DBAZ=qux',
+      environment.buildDir.childFile('main.dart').absolute.path,
+    ];
+    verify(processManager.run(expected)).called(1);
+  }, overrides: <Type, Generator>{
+    ProcessManager: () => MockProcessManager(),
+  }));
+
+  test('Dart2JSTarget calls dart2js with Dart defines in profile mode', () => testbed.run(() async {
+    environment.defines[kBuildMode] = 'profile';
+    environment.defines[kDartDefines] = '["FOO=bar","BAZ=qux"]';
+    when(processManager.run(any)).thenAnswer((Invocation invocation) async {
+      return FakeProcessResult(exitCode: 0);
+    });
+    await const Dart2JSTarget().build(environment);
+
+    final List<String> expected = <String>[
+      fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
+      fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
+      '--libraries-spec=' + fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'),
+      '-O4',
+      '--no-minify',
+      '-o',
+      environment.buildDir.childFile('main.dart.js').absolute.path,
+      '--packages=.packages',
+      '-Ddart.vm.profile=true',
+      '-DFOO=bar',
+      '-DBAZ=qux',
+      environment.buildDir.childFile('main.dart').absolute.path,
+    ];
+    verify(processManager.run(expected)).called(1);
+  }, overrides: <Type, Generator>{
+    ProcessManager: () => MockProcessManager(),
+  }));
+
+  test('Dart2JSTarget throws developer-friendly exception on misformatted DartDefines', () => testbed.run(() async {
+    environment.defines[kBuildMode] = 'profile';
+    environment.defines[kDartDefines] = '[misformatted json';
+    try {
+      await const Dart2JSTarget().build(environment);
+      fail('Call to build() must not have succeeded.');
+    } on Exception catch(exception) {
+      expect(
+        '$exception',
+        'Exception: The value of -D$kDartDefines is not formatted correctly.\n'
+        'The value must be a JSON-encoded list of strings but was:\n'
+        '[misformatted json',
+      );
+    }
+
+    // Should not attempt to run any processes.
+    verifyNever(processManager.run(any));
+  }, overrides: <Type, Generator>{
+    ProcessManager: () => MockProcessManager(),
+  }));
 }
 
 class MockProcessManager extends Mock implements ProcessManager {}
diff --git a/packages/flutter_tools/test/general.shard/compile_batch_test.dart b/packages/flutter_tools/test/general.shard/compile_batch_test.dart
index fabc29f..18ecd0b 100644
--- a/packages/flutter_tools/test/general.shard/compile_batch_test.dart
+++ b/packages/flutter_tools/test/general.shard/compile_batch_test.dart
@@ -25,6 +25,8 @@
   MockStdIn mockFrontendServerStdIn;
   MockStream mockFrontendServerStdErr;
 
+  List<String> latestCommand;
+
   setUp(() {
     mockProcessManager = MockProcessManager();
     mockFrontendServer = MockProcess();
@@ -38,7 +40,10 @@
     when(mockFrontendServer.stdin).thenReturn(mockFrontendServerStdIn);
     when(mockProcessManager.canRun(any)).thenReturn(true);
     when(mockProcessManager.start(any)).thenAnswer(
-        (Invocation invocation) => Future<Process>.value(mockFrontendServer));
+        (Invocation invocation) {
+          latestCommand = invocation.positionalArguments.first;
+          return Future<Process>.value(mockFrontendServer);
+        });
     when(mockFrontendServer.exitCode).thenAnswer((_) async => 0);
   });
 
@@ -55,6 +60,7 @@
       mainPath: '/path/to/main.dart',
       buildMode: BuildMode.debug,
       trackWidgetCreation: false,
+      dartDefines: const <String>[],
     );
 
     expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
@@ -79,6 +85,7 @@
       buildMode: BuildMode.release,
       trackWidgetCreation: false,
       aot: true,
+      dartDefines: const <String>[],
     );
 
     expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
@@ -103,6 +110,7 @@
       mainPath: '/path/to/main.dart',
       buildMode: BuildMode.debug,
       trackWidgetCreation: false,
+      dartDefines: const <String>[],
     );
 
     expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
@@ -130,6 +138,7 @@
       mainPath: '/path/to/main.dart',
       buildMode: BuildMode.debug,
       trackWidgetCreation: false,
+      dartDefines: const <String>[],
     );
     expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
     expect(bufferLogger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
@@ -139,6 +148,27 @@
     OutputPreferences: () => OutputPreferences(showColor: false),
     Platform: kNoColorTerminalPlatform,
   });
+
+  testUsingContext('passes dartDefines to the kernel compiler', () async {
+    // Use unsuccessful result because it's easier to setup in test. We only care about arguments passed to the compiler.
+    when(mockFrontendServer.exitCode).thenAnswer((_) async => 255);
+    when(mockFrontendServer.stdout).thenAnswer((Invocation invocation) => Stream<List<int>>.fromFuture(
+      Future<List<int>>.value(<int>[])
+    ));
+    final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(null);
+    await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot',
+      mainPath: '/path/to/main.dart',
+      buildMode: BuildMode.debug,
+      trackWidgetCreation: false,
+      dartDefines: const <String>['FOO=bar', 'BAZ=qux'],
+    );
+
+    expect(latestCommand, containsAllInOrder(<String>['-DFOO=bar', '-DBAZ=qux']));
+  }, overrides: <Type, Generator>{
+    ProcessManager: () => mockProcessManager,
+    OutputPreferences: () => OutputPreferences(showColor: false),
+    Platform: kNoColorTerminalPlatform,
+  });
 }
 
 class MockProcess extends Mock implements Process {}
diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart
index 603fee8..a793459 100644
--- a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart
@@ -41,6 +41,7 @@
           debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
           ipv6: true,
           stayResident: true,
+          dartDefines: const <String>[],
         );
       },
       overrides: <Type, Generator>{
@@ -52,6 +53,7 @@
           @required bool initializePlatform,
           @required String hostname,
           @required String port,
+          @required List<String> dartDefines,
         }) async {
           return mockWebFs;
         },
diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
index 05753d3..c30b020 100644
--- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
@@ -73,6 +73,7 @@
           debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
           ipv6: true,
           stayResident: true,
+          dartDefines: const <String>[],
         );
       },
       overrides: <Type, Generator>{
@@ -84,6 +85,7 @@
           @required bool initializePlatform,
           @required String hostname,
           @required String port,
+          @required List<String> dartDefines,
         }) async {
           return mockWebFs;
         },
@@ -133,6 +135,7 @@
       debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
       ipv6: true,
       stayResident: true,
+      dartDefines: const <String>[],
     );
 
     expect(profileResidentWebRunner.debuggingEnabled, false);
@@ -150,6 +153,7 @@
       debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
       ipv6: true,
       stayResident: true,
+      dartDefines: const <String>[],
     );
 
     expect(profileResidentWebRunner.supportsServiceProtocol, false);
@@ -204,6 +208,7 @@
       debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
       ipv6: true,
       stayResident: false,
+      dartDefines: const <String>[],
     );
 
     expect(await residentWebRunner.run(), 0);
@@ -240,6 +245,7 @@
       debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
       ipv6: true,
       stayResident: true,
+      dartDefines: const <String>[],
     );
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
diff --git a/packages/flutter_tools/test/general.shard/web/web_fs_test.dart b/packages/flutter_tools/test/general.shard/web/web_fs_test.dart
index f44ce27..2482140 100644
--- a/packages/flutter_tools/test/general.shard/web/web_fs_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/web_fs_test.dart
@@ -126,6 +126,7 @@
       initializePlatform: true,
       hostname: null,
       port: null,
+      dartDefines: const <String>[],
     );
     // Since the .packages file is missing in the memory filesystem, this should
     // be called.
@@ -155,6 +156,7 @@
       initializePlatform: false,
       hostname: null,
       port: null,
+      dartDefines: const <String>[],
     );
 
     // The build daemon is told to build once.
@@ -175,6 +177,7 @@
       initializePlatform: false,
       hostname: 'foo',
       port: '1234',
+      dartDefines: const <String>[],
     );
 
     expect(webFs.uri, contains('foo:1234'));
@@ -207,6 +210,7 @@
       initializePlatform: false,
       hostname: 'foo',
       port: '1234',
+      dartDefines: const <String>[],
     ), throwsA(isInstanceOf<Exception>()));
   }));
 }
diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart
index eabae35..9905c61 100644
--- a/packages/flutter_tools/test/integration.shard/test_driver.dart
+++ b/packages/flutter_tools/test/integration.shard/test_driver.dart
@@ -486,29 +486,50 @@
       pidFile: pidFile,
     );
 
-    // Stash the PID so that we can terminate the VM more reliably than using
-    // _process.kill() (`flutter` is a shell script so _process itself is a
-    // shell, not the flutter tool's Dart process).
-    final Map<String, dynamic> connected = await _waitFor(event: 'daemon.connected');
-    _processPid = connected['params']['pid'];
+    final Completer<void> prematureExitGuard = Completer<void>();
 
-    // Set this up now, but we don't wait it yet. We want to make sure we don't
-    // miss it while waiting for debugPort below.
-    final Future<Map<String, dynamic>> started = _waitFor(event: 'app.started', timeout: appStartTimeout);
-
-    if (withDebugger) {
-      final Map<String, dynamic> debugPort = await _waitFor(event: 'app.debugPort', timeout: appStartTimeout);
-      final String wsUriString = debugPort['params']['wsUri'];
-      _vmServiceWsUri = Uri.parse(wsUriString);
-      await connectToVmService(pauseOnExceptions: pauseOnExceptions);
-      if (!startPaused) {
-        await resume(waitForNextPause: false);
+    // If the process exits before all of the `await`s below are done, then it
+    // exited prematurely. This causes the currently suspended `await` to
+    // deadlock until the test times out. Instead, this causes the test to fail
+    // fast.
+    unawaited(_process.exitCode.then((_) {
+      if (!prematureExitGuard.isCompleted) {
+        prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}');
       }
-    }
+    }));
 
-    // Now await the started event; if it had already happened the future will
-    // have already completed.
-    _currentRunningAppId = (await started)['params']['appId'];
+    unawaited(() async {
+      try {
+        // Stash the PID so that we can terminate the VM more reliably than using
+        // _process.kill() (`flutter` is a shell script so _process itself is a
+        // shell, not the flutter tool's Dart process).
+        final Map<String, dynamic> connected = await _waitFor(event: 'daemon.connected');
+        _processPid = connected['params']['pid'];
+
+        // Set this up now, but we don't wait it yet. We want to make sure we don't
+        // miss it while waiting for debugPort below.
+        final Future<Map<String, dynamic>> started = _waitFor(event: 'app.started', timeout: appStartTimeout);
+
+        if (withDebugger) {
+          final Map<String, dynamic> debugPort = await _waitFor(event: 'app.debugPort', timeout: appStartTimeout);
+          final String wsUriString = debugPort['params']['wsUri'];
+          _vmServiceWsUri = Uri.parse(wsUriString);
+          await connectToVmService(pauseOnExceptions: pauseOnExceptions);
+          if (!startPaused) {
+            await resume(waitForNextPause: false);
+          }
+        }
+
+        // Now await the started event; if it had already happened the future will
+        // have already completed.
+        _currentRunningAppId = (await started)['params']['appId'];
+        prematureExitGuard.complete();
+      } catch(error, stackTrace) {
+        prematureExitGuard.completeError(error, stackTrace);
+      }
+    }());
+
+    return prematureExitGuard.future;
   }
 
   Future<void> hotRestart({ bool pause = false }) => _restart(fullRestart: true, pause: pause);