Fix dartDefines in daemon mode (#45317)

* de-null dartDefines in daemon mode
* remove daemonCommand; pipe through dartDefines into Daemon
* pass dartDefiles in attach; add test for --machine mode
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index 88b5677..04e86d8 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -198,8 +198,13 @@
     final int devicePort = await getDevicePort();
 
     final Daemon daemon = boolArg('machine')
-      ? Daemon(stdinCommandStream, stdoutCommandResponse,
-            notifyingLogger: NotifyingLogger(), logToStdout: true)
+      ? Daemon(
+          stdinCommandStream,
+          stdoutCommandResponse,
+          notifyingLogger: NotifyingLogger(),
+          logToStdout: true,
+          dartDefines: dartDefines,
+        )
       : null;
 
     Stream<Uri> observatoryUri;
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 8c5d6e4..42c413d 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -35,7 +35,9 @@
 /// It can be shutdown with a `daemon.shutdown` command (or by killing the
 /// process).
 class DaemonCommand extends FlutterCommand {
-  DaemonCommand({ this.hidden = false });
+  DaemonCommand({ this.hidden = false }) {
+    usesDartDefines();
+  }
 
   @override
   final String name = 'daemon';
@@ -58,8 +60,10 @@
     await context.run<void>(
       body: () async {
         final Daemon daemon = Daemon(
-            stdinCommandStream, stdoutCommandResponse,
-            daemonCommand: this, notifyingLogger: notifyingLogger);
+          stdinCommandStream, stdoutCommandResponse,
+          notifyingLogger: notifyingLogger,
+          dartDefines: dartDefines,
+        );
 
         final int code = await daemon.onExit;
         if (code != 0) {
@@ -82,10 +86,17 @@
   Daemon(
     Stream<Map<String, dynamic>> commandStream,
     this.sendCommand, {
-    this.daemonCommand,
     this.notifyingLogger,
     this.logToStdout = false,
+    @required this.dartDefines,
   }) {
+    if (dartDefines == null) {
+      throw Exception(
+        'dartDefines must not be null. This is a bug in Flutter.\n'
+        'Please file an issue at https://github.com/flutter/flutter/issues/new/choose',
+      );
+    }
+
     // Set up domains.
     _registerDomain(daemonDomain = DaemonDomain(this));
     _registerDomain(appDomain = AppDomain(this));
@@ -110,9 +121,9 @@
   StreamSubscription<Map<String, dynamic>> _commandSubscription;
 
   final DispatchCommand sendCommand;
-  final DaemonCommand daemonCommand;
   final NotifyingLogger notifyingLogger;
   final bool logToStdout;
+  final List<String> dartDefines;
 
   final Completer<int> _onExitCompleter = Completer<int>();
   final Map<String, Domain> _domainMap = <String, Domain>{};
@@ -184,8 +195,6 @@
     _handlers[name] = handler;
   }
 
-  FlutterCommand get command => daemon.daemonCommand;
-
   @override
   String toString() => name;
 
@@ -424,7 +433,7 @@
       viewFilter: isolateFilter,
       target: target,
       buildMode: options.buildInfo.mode,
-      dartDefines: command?.dartDefines,
+      dartDefines: daemon.dartDefines,
     );
 
     ResidentRunner runner;
@@ -437,7 +446,7 @@
         debuggingOptions: options,
         ipv6: ipv6,
         stayResident: true,
-        dartDefines: command?.dartDefines,
+        dartDefines: daemon.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 c41709a..60e1eac 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -340,8 +340,13 @@
       if (devices.length > 1) {
         throwToolExit('--machine does not support -d all.');
       }
-      final Daemon daemon = Daemon(stdinCommandStream, stdoutCommandResponse,
-          notifyingLogger: NotifyingLogger(), logToStdout: true);
+      final Daemon daemon = Daemon(
+        stdinCommandStream,
+        stdoutCommandResponse,
+        notifyingLogger: NotifyingLogger(),
+        logToStdout: true,
+        dartDefines: dartDefines,
+      );
       AppInstance app;
       try {
         final String applicationBinaryPath = stringArg('use-application-binary');
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart
index c9eadf1..8d59836 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart
@@ -39,6 +39,7 @@
         commands.stream,
         responses.add,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
       commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.version'});
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
@@ -56,6 +57,7 @@
         commands.stream,
         responses.add,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
       printError('daemon.logMessage test');
       final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
@@ -83,6 +85,7 @@
           responses.add,
           notifyingLogger: notifyingLogger,
           logToStdout: true,
+          dartDefines: const <String>[],
         );
         printStatus('daemon.logMessage test');
         // Service the event loop.
@@ -103,6 +106,7 @@
         commands.stream,
         responses.add,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
       commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.shutdown'});
       return daemon.onExit.then<void>((int code) async {
@@ -112,16 +116,13 @@
     });
 
     testUsingContext('app.restart without an appId should report an error', () async {
-      final DaemonCommand command = DaemonCommand();
-      applyMocksToCommand(command);
-
       final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
       final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
       daemon = Daemon(
         commands.stream,
         responses.add,
-        daemonCommand: command,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
 
       commands.add(<String, dynamic>{'id': 0, 'method': 'app.restart'});
@@ -133,16 +134,13 @@
     });
 
     testUsingContext('ext.flutter.debugPaint via service extension without an appId should report an error', () async {
-      final DaemonCommand command = DaemonCommand();
-      applyMocksToCommand(command);
-
       final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
       final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
       daemon = Daemon(
         commands.stream,
         responses.add,
-        daemonCommand: command,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
 
       commands.add(<String, dynamic>{
@@ -160,16 +158,13 @@
     });
 
     testUsingContext('app.stop without appId should report an error', () async {
-      final DaemonCommand command = DaemonCommand();
-      applyMocksToCommand(command);
-
       final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
       final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
       daemon = Daemon(
         commands.stream,
         responses.add,
-        daemonCommand: command,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
 
       commands.add(<String, dynamic>{'id': 0, 'method': 'app.stop'});
@@ -187,6 +182,7 @@
         commands.stream,
         responses.add,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
       commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
@@ -203,6 +199,7 @@
         commands.stream,
         responses.add,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
       final MockPollingDeviceDiscovery discoverer = MockPollingDeviceDiscovery();
       daemon.deviceDomain.addDeviceDiscoverer(discoverer);
@@ -221,9 +218,10 @@
       final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
       final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
       daemon = Daemon(
-          commands.stream,
-          responses.add,
-          notifyingLogger: notifyingLogger,
+        commands.stream,
+        responses.add,
+        notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
 
       final MockPollingDeviceDiscovery discoverer = MockPollingDeviceDiscovery();
@@ -247,16 +245,13 @@
     });
 
     testUsingContext('emulator.launch without an emulatorId should report an error', () async {
-      final DaemonCommand command = DaemonCommand();
-      applyMocksToCommand(command);
-
       final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
       final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
       daemon = Daemon(
         commands.stream,
         responses.add,
-        daemonCommand: command,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
 
       commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.launch'});
@@ -274,6 +269,7 @@
         commands.stream,
         responses.add,
         notifyingLogger: notifyingLogger,
+        dartDefines: const <String>[],
       );
       commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.getEmulators'});
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
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 cb4f01e..fb92f5c 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -244,6 +244,37 @@
         FlutterVersion: () => mockStableFlutterVersion,
         WebRunnerFactory: () => mockWebRunnerFactory,
       });
+
+      testUsingContext('populates dartDefines in --machine mode', () 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>');
+
+        when(mockDeviceManager.deviceDiscoverers).thenReturn(<DeviceDiscovery>[]);
+
+        args.add('--machine');
+        await createTestCommandRunner(command).run(args);
+        expect(mockWebRunnerFactory._dartDefines, <String>['FOO=bar']);
+      }, overrides: <Type, Generator>{
+        DeviceManager: () => mockDeviceManager,
+        FeatureFlags: () => TestFeatureFlags(
+          isWebEnabled: true,
+        ),
+        FileSystem: () => fs,
+        ProcessManager: () => mockProcessManager,
+        DeviceManager: () => mockDeviceManager,
+        FlutterVersion: () => mockStableFlutterVersion,
+        WebRunnerFactory: () => mockWebRunnerFactory,
+      });
     });
   });
 }
@@ -276,6 +307,9 @@
   static const int kFailure = -1;
   TargetPlatform _targetPlatform = TargetPlatform.ios;
 
+  @override
+  String get id => 'fake_device';
+
   void _throwToolExit(int code) => throwToolExit(null, exitCode: code);
 
   @override
@@ -351,6 +385,9 @@
 
 class MockWebRunner extends Mock implements ResidentRunner {
   @override
+  bool get debuggingEnabled => false;
+
+  @override
   Future<int> run({
     Completer<DebugConnectionInfo> connectionInfoCompleter,
     Completer<void> appStartedCompleter,
@@ -358,4 +395,7 @@
   }) async {
     return 0;
   }
+
+  @override
+  Future<int> waitForAppToFinish() async => 0;
 }