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 =