Commit 644e7b13 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Faster hot reload (#8600)

This implements the `DartDependencySetBuilder` completely in Dart instead of calling out to `sky_snapshot` (Linux/Mac) or `gen_snapshot` (Windows) and allows us to use the same code path on all supported host platforms.

It also slightly reduces hot reload times on Linux from ~750ms to ~690ms for the unchanged flutter_gallery app and significantly reduces hot reload times on Windows from almost 1.5s to just slightly slower than on Linux.

This change will also allow us to retire `sky_snapshot` completely in the future.
parent 3b4f6b1a
...@@ -2,89 +2,49 @@ ...@@ -2,89 +2,49 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert'; import 'package:analyzer/analyzer.dart';
import '../artifacts.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/platform.dart'; import '../dart/package_map.dart';
import '../base/process.dart';
class DartDependencySetBuilder { class DartDependencySetBuilder {
DartDependencySetBuilder(String mainScriptPath, String packagesFilePath) :
this._mainScriptPath = fs.path.canonicalize(mainScriptPath),
this._mainScriptUri = fs.path.toUri(mainScriptPath),
this._packagesFilePath = fs.path.canonicalize(packagesFilePath);
factory DartDependencySetBuilder( final String _mainScriptPath;
String mainScriptPath, String projectRootPath, String packagesFilePath) { final String _packagesFilePath;
if (platform.isWindows)
return new _GenSnapshotDartDependencySetBuilder(mainScriptPath, projectRootPath, packagesFilePath);
return new DartDependencySetBuilder._(mainScriptPath, projectRootPath, packagesFilePath);
}
DartDependencySetBuilder._(this.mainScriptPath, this.projectRootPath, this.packagesFilePath);
final String mainScriptPath; final Uri _mainScriptUri;
final String projectRootPath;
final String packagesFilePath;
/// Returns a set of canonicalize paths.
///
/// The paths have been canonicalize with `fs.path.canonicalize`.
Set<String> build() { Set<String> build() {
final String skySnapshotPath = final List<String> dependencies = <String>[_mainScriptPath, _packagesFilePath];
Artifacts.instance.getArtifactPath(Artifact.skySnapshot); final List<Uri> toProcess = <Uri>[_mainScriptUri];
final PackageMap packageMap = new PackageMap(_packagesFilePath);
final List<String> args = <String>[
skySnapshotPath, while (toProcess.isNotEmpty) {
'--packages=$packagesFilePath', final Uri currentUri = toProcess.removeLast();
'--print-deps', final CompilationUnit unit = _parse(currentUri.toFilePath());
mainScriptPath for (Directive directive in unit.directives) {
]; if (!(directive is UriBasedDirective))
continue;
final String output = runSyncAndThrowStdErrOnError(args); final UriBasedDirective uriBasedDirective = directive;
final String uriAsString = uriBasedDirective.uri.stringValue;
return new Set<String>.from(LineSplitter.split(output).map( Uri resolvedUri = resolveRelativeUri(currentUri, Uri.parse(uriAsString));
(String path) => fs.path.canonicalize(path)) if (resolvedUri.scheme.startsWith('dart'))
); continue;
if (resolvedUri.scheme == 'package')
resolvedUri = packageMap.uriForPackage(resolvedUri);
final String path = fs.path.canonicalize(resolvedUri.toFilePath());
if (!dependencies.contains(path)) {
dependencies.add(path);
toProcess.add(resolvedUri);
}
}
}
return dependencies.toSet();
} }
}
/// A [DartDependencySetBuilder] that is backed by gen_snapshot.
class _GenSnapshotDartDependencySetBuilder implements DartDependencySetBuilder {
_GenSnapshotDartDependencySetBuilder(this.mainScriptPath,
this.projectRootPath,
this.packagesFilePath);
@override CompilationUnit _parse(String path) => parseDirectives(fs.file(path).readAsStringSync(), name: path);
final String mainScriptPath;
@override
final String projectRootPath;
@override
final String packagesFilePath;
@override
Set<String> build() {
final String snapshotterPath =
Artifacts.instance.getArtifactPath(Artifact.genSnapshot);
final String vmSnapshotData =
Artifacts.instance.getArtifactPath(Artifact.vmSnapshotData);
final String isolateSnapshotData =
Artifacts.instance.getArtifactPath(Artifact.isolateSnapshotData);
assert(fs.path.isAbsolute(this.projectRootPath));
final List<String> args = <String>[
snapshotterPath,
'--snapshot_kind=script',
'--dependencies-only',
'--vm_snapshot_data=$vmSnapshotData',
'--isolate_snapshot_data=$isolateSnapshotData',
'--packages=$packagesFilePath',
'--print-dependencies',
'--script_snapshot=snapshot_blob.bin',
mainScriptPath
];
final String output = runSyncAndThrowStdErrOnError(args);
return new Set<String>.from(LineSplitter.split(output).map(
(String path) => fs.path.canonicalize(path))
);
}
} }
...@@ -40,13 +40,16 @@ class PackageMap { ...@@ -40,13 +40,16 @@ class PackageMap {
Map<String, Uri> _map; Map<String, Uri> _map;
/// Returns the path to [packageUri]. /// Returns the path to [packageUri].
String pathForPackage(Uri packageUri) { String pathForPackage(Uri packageUri) => uriForPackage(packageUri).path;
/// Returns the path to [packageUri] as Uri.
Uri uriForPackage(Uri packageUri) {
assert(packageUri.scheme == 'package'); assert(packageUri.scheme == 'package');
final List<String> pathSegments = packageUri.pathSegments.toList(); final List<String> pathSegments = packageUri.pathSegments.toList();
final String packageName = pathSegments.removeAt(0); final String packageName = pathSegments.removeAt(0);
final Uri packageBase = map[packageName]; final Uri packageBase = map[packageName];
final String packageRelativePath = fs.path.joinAll(pathSegments); final String packageRelativePath = fs.path.joinAll(pathSegments);
return packageBase.resolve(packageRelativePath).path; return packageBase.resolveUri(fs.path.toUri(packageRelativePath));
} }
String checkValid() { String checkValid() {
......
...@@ -17,12 +17,6 @@ class DependencyChecker { ...@@ -17,12 +17,6 @@ class DependencyChecker {
/// if it cannot be determined. /// if it cannot be determined.
bool check(DateTime threshold) { bool check(DateTime threshold) {
_dependencies.clear(); _dependencies.clear();
try {
_dependencies.add(builder.packagesFilePath);
} catch (e, st) {
printTrace('DependencyChecker: could not parse .packages file:\n$e\n$st');
return true;
}
// Build the set of Dart dependencies. // Build the set of Dart dependencies.
try { try {
_dependencies.addAll(builder.build()); _dependencies.addAll(builder.build());
......
...@@ -330,8 +330,7 @@ abstract class ResidentRunner { ...@@ -330,8 +330,7 @@ abstract class ResidentRunner {
bool hasDirtyDependencies() { bool hasDirtyDependencies() {
final DartDependencySetBuilder dartDependencySetBuilder = final DartDependencySetBuilder dartDependencySetBuilder =
new DartDependencySetBuilder( new DartDependencySetBuilder(mainPath, packagesFilePath);
mainPath, projectRootPath, packagesFilePath);
final DependencyChecker dependencyChecker = final DependencyChecker dependencyChecker =
new DependencyChecker(dartDependencySetBuilder, assetBundle); new DependencyChecker(dartDependencySetBuilder, assetBundle);
final String path = package.packagePath; final String path = package.packagePath;
......
...@@ -99,8 +99,7 @@ class HotRunner extends ResidentRunner { ...@@ -99,8 +99,7 @@ class HotRunner extends ResidentRunner {
return true; return true;
} }
final DartDependencySetBuilder dartDependencySetBuilder = final DartDependencySetBuilder dartDependencySetBuilder =
new DartDependencySetBuilder( new DartDependencySetBuilder(mainPath, packagesFilePath);
mainPath, projectRootPath, packagesFilePath);
try { try {
_dartDependencies = new Set<String>.from(dartDependencySetBuilder.build()); _dartDependencies = new Set<String>.from(dartDependencySetBuilder.build());
} catch (error) { } catch (error) {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:analyzer/analyzer.dart';
import 'package:flutter_tools/src/dart/dependencies.dart'; import 'package:flutter_tools/src/dart/dependencies.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
...@@ -17,7 +18,7 @@ void main() { ...@@ -17,7 +18,7 @@ void main() {
final String mainPath = fs.path.join(testPath, 'main.dart'); final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages'); final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder = final DartDependencySetBuilder builder =
new DartDependencySetBuilder(mainPath, testPath, packagesPath); new DartDependencySetBuilder(mainPath, packagesPath);
final Set<String> dependencies = builder.build(); final Set<String> dependencies = builder.build();
expect(dependencies.contains(fs.path.canonicalize(mainPath)), isTrue); expect(dependencies.contains(fs.path.canonicalize(mainPath)), isTrue);
expect(dependencies.contains(fs.path.canonicalize(fs.path.join(testPath, 'foo.dart'))), isTrue); expect(dependencies.contains(fs.path.canonicalize(fs.path.join(testPath, 'foo.dart'))), isTrue);
...@@ -27,13 +28,12 @@ void main() { ...@@ -27,13 +28,12 @@ void main() {
final String mainPath = fs.path.join(testPath, 'main.dart'); final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages'); final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder = final DartDependencySetBuilder builder =
new DartDependencySetBuilder(mainPath, testPath, packagesPath); new DartDependencySetBuilder(mainPath, packagesPath);
try { try {
builder.build(); builder.build();
fail('expect an assertion to be thrown.'); fail('expect an assertion to be thrown.');
} catch (e) { } on AnalyzerErrorGroup catch (e) {
expect(e, const isInstanceOf<String>()); expect(e.toString(), contains('foo.dart: Expected a string literal'));
expect(e.contains('unexpected token \'bad\''), isTrue);
} }
}); });
}); });
......
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
bad programmer! import bad programmer!
...@@ -31,7 +31,7 @@ void main() { ...@@ -31,7 +31,7 @@ void main() {
final String barPath = fs.path.join(testPath, 'lib', 'bar.dart'); final String barPath = fs.path.join(testPath, 'lib', 'bar.dart');
final String packagesPath = fs.path.join(testPath, '.packages'); final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder = final DartDependencySetBuilder builder =
new DartDependencySetBuilder(mainPath, testPath, packagesPath); new DartDependencySetBuilder(mainPath, packagesPath);
final DependencyChecker dependencyChecker = final DependencyChecker dependencyChecker =
new DependencyChecker(builder, null); new DependencyChecker(builder, null);
...@@ -63,7 +63,7 @@ void main() { ...@@ -63,7 +63,7 @@ void main() {
final String packagesPath = fs.path.join(testPath, '.packages'); final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder = final DartDependencySetBuilder builder =
new DartDependencySetBuilder(mainPath, testPath, packagesPath); new DartDependencySetBuilder(mainPath, packagesPath);
final DependencyChecker dependencyChecker = final DependencyChecker dependencyChecker =
new DependencyChecker(builder, null); new DependencyChecker(builder, null);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment