Only synchronize used Dart sources to DevFS. (#6668)

- [x] Remove the second full-sync on startup.
- [x] Always invoke the snapshotter to determine the minimal set of Dart sources used.
- [x] Only synchronize the *used* Dart sources to DevFS.
- [x] Detect syntax / file missing errors on the host and gate reloads / restarts.
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 5a2d102..104db6c 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -164,6 +164,16 @@
   );
 }
 
+/// Run cmd and return stdout on success.
+///
+/// Throws the standard error output if cmd exits with a non-zero value.
+String runSyncAndThrowStdErrOnError(List<String> cmd) {
+  return _runWithLoggingSync(cmd,
+                             checked: true,
+                             throwStandardErrorOnError: true,
+                             hideStdout: true);
+}
+
 /// Run cmd and return stdout.
 String runSync(List<String> cmd, {
   String workingDirectory,
@@ -187,6 +197,7 @@
 String _runWithLoggingSync(List<String> cmd, {
   bool checked: false,
   bool noisyErrors: false,
+  bool throwStandardErrorOnError: false,
   String workingDirectory,
   bool allowReentrantFlutter: false,
   bool hideStdout: false,
@@ -216,6 +227,9 @@
         printTrace(results.stderr.trim());
     }
 
+    if (throwStandardErrorOnError)
+      throw results.stderr.trim();
+
     if (checked)
       throw 'Exit code ${results.exitCode} from: ${cmd.join(' ')}';
   }
diff --git a/packages/flutter_tools/lib/src/hot.dart b/packages/flutter_tools/lib/src/hot.dart
index ac1320c..bfbf177 100644
--- a/packages/flutter_tools/lib/src/hot.dart
+++ b/packages/flutter_tools/lib/src/hot.dart
@@ -38,9 +38,9 @@
                                  'loader_app.dart'));
 }
 
-class StartupDependencySetBuilder {
-  StartupDependencySetBuilder(this.mainScriptPath,
-                              this.projectRootPath);
+class DartDependencySetBuilder {
+  DartDependencySetBuilder(this.mainScriptPath,
+                           this.projectRootPath);
 
   final String mainScriptPath;
   final String projectRootPath;
@@ -56,12 +56,7 @@
       mainScriptPath
     ];
 
-    String output;
-    try {
-      output = runCheckedSync(args, hideStdout: true);
-    } catch (e) {
-      return null;
-    }
+    String output = runSyncAndThrowStdErrOnError(args);
 
     final List<String> lines = LineSplitter.split(output).toList();
     final Set<String> minimalDependencies = new Set<String>();
@@ -135,7 +130,7 @@
   ApplicationPackage _package;
   String _mainPath;
   String _projectRootPath;
-  Set<String> _startupDependencies;
+  Set<String> _dartDependencies;
   int _observatoryPort;
   final AssetBundle bundle = new AssetBundle();
   final bool benchmarkMode;
@@ -159,6 +154,23 @@
     });
   }
 
+  bool _refreshDartDependencies() {
+    if (_dartDependencies != null) {
+      // Already computed.
+      return true;
+    }
+    DartDependencySetBuilder dartDependencySetBuilder =
+        new DartDependencySetBuilder(_mainPath, _projectRootPath);
+    try {
+      _dartDependencies = dartDependencySetBuilder.build();
+    } catch (error) {
+      printStatus('Error detected in application source code:', emphasis: true);
+      printError(error);
+      return false;
+    }
+    return true;
+  }
+
   Future<int> _run({
     Completer<DebugConnectionInfo> connectionInfoCompleter,
     String route,
@@ -184,6 +196,12 @@
       return 1;
     }
 
+    // Determine the Dart dependencies eagerly.
+    if (!_refreshDartDependencies()) {
+      // Some kind of source level error or missing file in the Dart code.
+      return 1;
+    }
+
     // TODO(devoncarew): We shouldn't have to do type checks here.
     if (shouldBuild && device is AndroidDevice) {
       printTrace('Running build command.');
@@ -231,15 +249,6 @@
       route: route
     );
 
-    // In parallel, compute the minimal dependency set.
-    StartupDependencySetBuilder startupDependencySetBuilder =
-        new StartupDependencySetBuilder(_mainPath, _projectRootPath);
-    _startupDependencies = startupDependencySetBuilder.build();
-    if (_startupDependencies == null) {
-      printError('Error determining the set of Dart sources necessary to start '
-                 'the application. Initial file upload may take a long time.');
-    }
-
     LaunchResult result = await futureResult;
 
     if (!result.started) {
@@ -295,10 +304,6 @@
 
     registerSignalHandlers();
 
-    printTrace('Finishing file synchronization');
-    // Finish the file sync now.
-    await _updateDevFS();
-
     if (benchmarkMode) {
       // We are running in benchmark mode.
       printStatus('Running in benchmark mode.');
@@ -325,13 +330,19 @@
   Future<Null> handleTerminalCommand(String code) async {
     final String lower = code.toLowerCase();
     if ((lower == 'r') || (code == AnsiTerminal.KEY_F5)) {
+      OperationResult result = OperationResult.ok;
       // F5, restart
       if ((code == 'r') || (code == AnsiTerminal.KEY_F5)) {
         // lower-case 'r'
-        await _reloadSources();
+        result = await _reloadSources();
       } else {
         // upper-case 'R'.
-        await _restartFromSources();
+        result = await _restartFromSources();
+      }
+      if (!result.isOk) {
+        // TODO(johnmccutchan): Attempt to determine the number of errors that
+        // occurred and tighten this message.
+        printStatus('Try again after fixing the above error(s).', emphasis: true);
       }
     }
   }
@@ -362,6 +373,10 @@
   }
 
   Future<bool> _updateDevFS({ DevFSProgressReporter progressReporter }) async {
+    if (!_refreshDartDependencies()) {
+      // Did not update DevFS because of a Dart source error.
+      return false;
+    }
     Status devFSStatus = logger.startProgress('Syncing files to device...');
     final bool rebuildBundle = bundle.needsBuild();
     if (rebuildBundle) {
@@ -371,10 +386,10 @@
     await _devFS.update(progressReporter: progressReporter,
                         bundle: bundle,
                         bundleDirty: rebuildBundle,
-                        fileFilter: _startupDependencies);
+                        fileFilter: _dartDependencies);
     devFSStatus.stop(showElapsedTime: true);
-    // Clear the minimal set after the first sync.
-    _startupDependencies = null;
+    // Clear the set after the sync.
+    _dartDependencies = null;
     printTrace('Synced ${getSizeAsMB(_devFS.bytes)}.');
     return true;
   }
@@ -422,10 +437,12 @@
                         deviceAssetsDirectoryPath);
   }
 
-  Future<Null> _restartFromSources() async {
+  Future<OperationResult> _restartFromSources() async {
     FirstFrameTimer firstFrameTimer = new FirstFrameTimer(vmService);
     firstFrameTimer.start();
-    await _updateDevFS();
+    bool updatedDevFS = await _updateDevFS();
+    if (!updatedDevFS)
+      return new OperationResult(1, 'Dart Source Error');
     await _launchFromDevFS(_package, _mainPath);
     bool waitForFrame =
         await currentView.uiIsolate.flutterFrameworkPresent();
@@ -446,6 +463,7 @@
       flutterUsage.sendTiming('hot', 'restart', firstFrameTimer.elapsed);
     }
     flutterUsage.sendEvent('hot', 'restart');
+    return OperationResult.ok;
   }
 
   /// Returns [true] if the reload was successful.
@@ -465,8 +483,7 @@
   @override
   Future<OperationResult> restart({ bool fullRestart: false, bool pauseAfterRestart: false }) async {
     if (fullRestart) {
-      await _restartFromSources();
-      return OperationResult.ok;
+      return _restartFromSources();
     } else {
       return _reloadSources(pause: pauseAfterRestart);
     }
@@ -477,8 +494,11 @@
       throw 'Application isolate not found';
     FirstFrameTimer firstFrameTimer = new FirstFrameTimer(vmService);
     firstFrameTimer.start();
-    if (_devFS != null)
-      await _updateDevFS();
+    if (_devFS != null) {
+      bool updatedDevFS = await _updateDevFS();
+      if (!updatedDevFS)
+        return new OperationResult(1, 'Dart Source Error');
+    }
     Status reloadStatus = logger.startProgress('Performing hot reload...');
     try {
       Map<String, dynamic> reloadReport =