mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Hot reload: Only sync Dart sources necessary for startup on first launch (#5333)
- [x] Update engine to bring in new snapshotter. - [x] Use the new snapshotter to quickly determine the minimal set of files necessary to run. - [x] On first DevFS sync, only sync files necessary to run the application. - [x] Fix a DevFS unit test failure. - [x] Include DevFS tests in all.dart.
This commit is contained in:
parent
996a59aaa0
commit
ec0f880032
2
bin/cache/engine.version
vendored
2
bin/cache/engine.version
vendored
@ -1 +1 @@
|
|||||||
9f6294c4f389633b9bfb9bea324337ab5ac49558
|
607d379c23379ab29948d116ed0e431ef6b7d86a
|
||||||
|
@ -286,13 +286,16 @@ class DevFS {
|
|||||||
|
|
||||||
Future<dynamic> update({ DevFSProgressReporter progressReporter,
|
Future<dynamic> update({ DevFSProgressReporter progressReporter,
|
||||||
AssetBundle bundle,
|
AssetBundle bundle,
|
||||||
bool bundleDirty: false }) async {
|
bool bundleDirty: false,
|
||||||
|
Set<String> fileFilter}) async {
|
||||||
_reset();
|
_reset();
|
||||||
printTrace('DevFS: Starting sync from $rootDirectory');
|
printTrace('DevFS: Starting sync from $rootDirectory');
|
||||||
Status status;
|
Status status;
|
||||||
status = logger.startProgress('Scanning project files...');
|
status = logger.startProgress('Scanning project files...');
|
||||||
Directory directory = rootDirectory;
|
Directory directory = rootDirectory;
|
||||||
await _scanDirectory(directory, recursive: true);
|
await _scanDirectory(directory,
|
||||||
|
recursive: true,
|
||||||
|
fileFilter: fileFilter);
|
||||||
status.stop(showElapsedTime: true);
|
status.stop(showElapsedTime: true);
|
||||||
|
|
||||||
status = logger.startProgress('Scanning package files...');
|
status = logger.startProgress('Scanning package files...');
|
||||||
@ -310,7 +313,8 @@ class DevFS {
|
|||||||
bool packageExists =
|
bool packageExists =
|
||||||
await _scanDirectory(directory,
|
await _scanDirectory(directory,
|
||||||
directoryName: 'packages/$packageName',
|
directoryName: 'packages/$packageName',
|
||||||
recursive: true);
|
recursive: true,
|
||||||
|
fileFilter: fileFilter);
|
||||||
if (packageExists) {
|
if (packageExists) {
|
||||||
sb ??= new StringBuffer();
|
sb ??= new StringBuffer();
|
||||||
sb.writeln('$packageName:packages/$packageName');
|
sb.writeln('$packageName:packages/$packageName');
|
||||||
@ -440,7 +444,7 @@ class DevFS {
|
|||||||
List<String> ignoredPrefixes = <String>['android/',
|
List<String> ignoredPrefixes = <String>['android/',
|
||||||
'build/',
|
'build/',
|
||||||
'ios/',
|
'ios/',
|
||||||
'packages/analyzer'];
|
'.pub/'];
|
||||||
for (String ignoredPrefix in ignoredPrefixes) {
|
for (String ignoredPrefix in ignoredPrefixes) {
|
||||||
if (devicePath.startsWith(ignoredPrefix))
|
if (devicePath.startsWith(ignoredPrefix))
|
||||||
return true;
|
return true;
|
||||||
@ -451,7 +455,8 @@ class DevFS {
|
|||||||
Future<bool> _scanDirectory(Directory directory,
|
Future<bool> _scanDirectory(Directory directory,
|
||||||
{String directoryName,
|
{String directoryName,
|
||||||
bool recursive: false,
|
bool recursive: false,
|
||||||
bool ignoreDotFiles: true}) async {
|
bool ignoreDotFiles: true,
|
||||||
|
Set<String> fileFilter}) async {
|
||||||
String prefix = directoryName;
|
String prefix = directoryName;
|
||||||
if (prefix == null) {
|
if (prefix == null) {
|
||||||
prefix = path.relative(directory.path, from: rootDirectory.path);
|
prefix = path.relative(directory.path, from: rootDirectory.path);
|
||||||
@ -472,6 +477,15 @@ class DevFS {
|
|||||||
}
|
}
|
||||||
final String devicePath =
|
final String devicePath =
|
||||||
path.join(prefix, path.relative(file.path, from: directory.path));
|
path.join(prefix, path.relative(file.path, from: directory.path));
|
||||||
|
if ((fileFilter != null) &&
|
||||||
|
!fileFilter.contains(devicePath)) {
|
||||||
|
// Skip files that are not included in the filter.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ignoreDotFiles && devicePath.startsWith('.')) {
|
||||||
|
// Skip directories that start with a dot.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!_shouldIgnore(devicePath))
|
if (!_shouldIgnore(devicePath))
|
||||||
_scanFile(devicePath, file);
|
_scanFile(devicePath, file);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
@ -10,15 +11,18 @@ import 'package:path/path.dart' as path;
|
|||||||
import 'application_package.dart';
|
import 'application_package.dart';
|
||||||
import 'asset.dart';
|
import 'asset.dart';
|
||||||
import 'base/logger.dart';
|
import 'base/logger.dart';
|
||||||
|
import 'base/process.dart';
|
||||||
import 'base/utils.dart';
|
import 'base/utils.dart';
|
||||||
import 'cache.dart';
|
import 'cache.dart';
|
||||||
import 'commands/build_apk.dart';
|
import 'commands/build_apk.dart';
|
||||||
import 'commands/install.dart';
|
import 'commands/install.dart';
|
||||||
|
import 'dart/package_map.dart';
|
||||||
import 'device.dart';
|
import 'device.dart';
|
||||||
import 'globals.dart';
|
import 'globals.dart';
|
||||||
import 'devfs.dart';
|
import 'devfs.dart';
|
||||||
import 'observatory.dart';
|
import 'observatory.dart';
|
||||||
import 'resident_runner.dart';
|
import 'resident_runner.dart';
|
||||||
|
import 'toolchain.dart';
|
||||||
|
|
||||||
String getDevFSLoaderScript() {
|
String getDevFSLoaderScript() {
|
||||||
return path.absolute(path.join(Cache.flutterRoot,
|
return path.absolute(path.join(Cache.flutterRoot,
|
||||||
@ -29,6 +33,51 @@ String getDevFSLoaderScript() {
|
|||||||
'loader_app.dart'));
|
'loader_app.dart'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StartupDependencySetBuilder {
|
||||||
|
StartupDependencySetBuilder(this.mainScriptPath,
|
||||||
|
this.projectRootPath);
|
||||||
|
|
||||||
|
final String mainScriptPath;
|
||||||
|
final String projectRootPath;
|
||||||
|
|
||||||
|
Set<String> build() {
|
||||||
|
final String skySnapshotPath =
|
||||||
|
ToolConfiguration.instance.getHostToolPath(HostTool.SkySnapshot);
|
||||||
|
|
||||||
|
final List<String> args = <String>[
|
||||||
|
skySnapshotPath,
|
||||||
|
'--packages=${path.absolute(PackageMap.globalPackagesPath)}',
|
||||||
|
'--print-deps',
|
||||||
|
mainScriptPath
|
||||||
|
];
|
||||||
|
|
||||||
|
String output;
|
||||||
|
try {
|
||||||
|
output = runCheckedSync(args);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> lines = LineSplitter.split(output).toList();
|
||||||
|
final Set<String> minimalDependencies = new Set<String>();
|
||||||
|
for (String line in lines) {
|
||||||
|
// We need to convert the uris so that they are relative to the project
|
||||||
|
// root and tweak package: uris so that they reflect their devFS location.
|
||||||
|
if (line.startsWith('package:')) {
|
||||||
|
// Swap out package: for packages/ because we place all package sources
|
||||||
|
// under packages/.
|
||||||
|
line = line.replaceFirst('package:', 'packages/');
|
||||||
|
} else {
|
||||||
|
// Ensure paths are relative to the project root.
|
||||||
|
line = path.relative(line, from: projectRootPath);
|
||||||
|
}
|
||||||
|
minimalDependencies.add(line);
|
||||||
|
}
|
||||||
|
return minimalDependencies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class FirstFrameTimer {
|
class FirstFrameTimer {
|
||||||
FirstFrameTimer(this.serviceProtocol);
|
FirstFrameTimer(this.serviceProtocol);
|
||||||
|
|
||||||
@ -81,6 +130,7 @@ class HotRunner extends ResidentRunner {
|
|||||||
ApplicationPackage _package;
|
ApplicationPackage _package;
|
||||||
String _mainPath;
|
String _mainPath;
|
||||||
String _projectRootPath;
|
String _projectRootPath;
|
||||||
|
Set<String> _startupDependencies;
|
||||||
final AssetBundle bundle = new AssetBundle();
|
final AssetBundle bundle = new AssetBundle();
|
||||||
final File pipe;
|
final File pipe;
|
||||||
|
|
||||||
@ -194,7 +244,8 @@ class HotRunner extends ResidentRunner {
|
|||||||
printStatus('Launching ${getDisplayPath(_mainPath)} on ${device.name}...');
|
printStatus('Launching ${getDisplayPath(_mainPath)} on ${device.name}...');
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchResult result = await device.startApp(
|
// Start the loader.
|
||||||
|
Future<LaunchResult> futureResult = device.startApp(
|
||||||
_package,
|
_package,
|
||||||
debuggingOptions.buildMode,
|
debuggingOptions.buildMode,
|
||||||
mainPath: device.needsDevFS ? getDevFSLoaderScript() : _mainPath,
|
mainPath: device.needsDevFS ? getDevFSLoaderScript() : _mainPath,
|
||||||
@ -203,6 +254,17 @@ class HotRunner extends ResidentRunner {
|
|||||||
route: route
|
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) {
|
if (!result.started) {
|
||||||
if (device.needsDevFS) {
|
if (device.needsDevFS) {
|
||||||
printError('Error launching DevFS loader on ${device.name}.');
|
printError('Error launching DevFS loader on ${device.name}.');
|
||||||
@ -243,6 +305,10 @@ class HotRunner extends ResidentRunner {
|
|||||||
|
|
||||||
registerSignalHandlers();
|
registerSignalHandlers();
|
||||||
|
|
||||||
|
printStatus('Finishing file synchronization...');
|
||||||
|
// Finish the file sync now.
|
||||||
|
await _updateDevFS();
|
||||||
|
|
||||||
return await waitForAppToFinish();
|
return await waitForAppToFinish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,8 +364,11 @@ class HotRunner extends ResidentRunner {
|
|||||||
Status devFSStatus = logger.startProgress('Syncing files to device...');
|
Status devFSStatus = logger.startProgress('Syncing files to device...');
|
||||||
await _devFS.update(progressReporter: progressReporter,
|
await _devFS.update(progressReporter: progressReporter,
|
||||||
bundle: bundle,
|
bundle: bundle,
|
||||||
bundleDirty: rebuildBundle);
|
bundleDirty: rebuildBundle,
|
||||||
|
fileFilter: _startupDependencies);
|
||||||
devFSStatus.stop(showElapsedTime: true);
|
devFSStatus.stop(showElapsedTime: true);
|
||||||
|
// Clear the minimal set after the first sync.
|
||||||
|
_startupDependencies = null;
|
||||||
if (progressReporter != null)
|
if (progressReporter != null)
|
||||||
printStatus('Synced ${getSizeAsMB(_devFS.bytes)}.');
|
printStatus('Synced ${getSizeAsMB(_devFS.bytes)}.');
|
||||||
else
|
else
|
||||||
|
@ -21,6 +21,7 @@ import 'config_test.dart' as config_test;
|
|||||||
import 'context_test.dart' as context_test;
|
import 'context_test.dart' as context_test;
|
||||||
import 'create_test.dart' as create_test;
|
import 'create_test.dart' as create_test;
|
||||||
import 'daemon_test.dart' as daemon_test;
|
import 'daemon_test.dart' as daemon_test;
|
||||||
|
import 'devfs_test.dart' as devfs_test;
|
||||||
import 'device_test.dart' as device_test;
|
import 'device_test.dart' as device_test;
|
||||||
// import 'devices_test.dart' as devices_test;
|
// import 'devices_test.dart' as devices_test;
|
||||||
import 'drive_test.dart' as drive_test;
|
import 'drive_test.dart' as drive_test;
|
||||||
@ -51,6 +52,7 @@ void main() {
|
|||||||
context_test.main();
|
context_test.main();
|
||||||
create_test.main();
|
create_test.main();
|
||||||
daemon_test.main();
|
daemon_test.main();
|
||||||
|
devfs_test.main();
|
||||||
device_test.main();
|
device_test.main();
|
||||||
// devices_test.main(); // https://github.com/flutter/flutter/issues/4480
|
// devices_test.main(); // https://github.com/flutter/flutter/issues/4480
|
||||||
drive_test.main();
|
drive_test.main();
|
||||||
|
@ -58,17 +58,17 @@ void main() {
|
|||||||
expect(devFSOperations.contains('deleteFile test bar/foo.txt'), isTrue);
|
expect(devFSOperations.contains('deleteFile test bar/foo.txt'), isTrue);
|
||||||
});
|
});
|
||||||
testUsingContext('add file in an asset bundle', () async {
|
testUsingContext('add file in an asset bundle', () async {
|
||||||
await devFS.update(bundle: assetBundle);
|
await devFS.update(bundle: assetBundle, bundleDirty: true);
|
||||||
expect(devFSOperations.contains('writeFile test build/flx/a.txt'), isTrue);
|
expect(devFSOperations.contains('writeFile test build/flx/a.txt'), isTrue);
|
||||||
});
|
});
|
||||||
testUsingContext('add a file to the asset bundle', () async {
|
testUsingContext('add a file to the asset bundle', () async {
|
||||||
assetBundle.entries.add(new AssetBundleEntry.fromString('b.txt', ''));
|
assetBundle.entries.add(new AssetBundleEntry.fromString('b.txt', ''));
|
||||||
await devFS.update(bundle: assetBundle);
|
await devFS.update(bundle: assetBundle, bundleDirty: true);
|
||||||
expect(devFSOperations.contains('writeFile test build/flx/b.txt'), isTrue);
|
expect(devFSOperations.contains('writeFile test build/flx/b.txt'), isTrue);
|
||||||
});
|
});
|
||||||
testUsingContext('delete a file from the asset bundle', () async {
|
testUsingContext('delete a file from the asset bundle', () async {
|
||||||
assetBundle.entries.clear();
|
assetBundle.entries.clear();
|
||||||
await devFS.update(bundle: assetBundle);
|
await devFS.update(bundle: assetBundle, bundleDirty: true);
|
||||||
expect(devFSOperations.contains('deleteFile test build/flx/b.txt'), isTrue);
|
expect(devFSOperations.contains('deleteFile test build/flx/b.txt'), isTrue);
|
||||||
});
|
});
|
||||||
testUsingContext('delete dev file system', () async {
|
testUsingContext('delete dev file system', () async {
|
||||||
|
Loading…
Reference in New Issue
Block a user