Improve hot reload performance (#28152)
This commit is contained in:
parent
bfac60485d
commit
cd803ac7f2
@ -70,4 +70,5 @@ dev_dependencies:
|
||||
mockito: 4.0.0
|
||||
test_api: 0.2.2
|
||||
|
||||
|
||||
# PUBSPEC CHECKSUM: 422e
|
||||
|
@ -15,7 +15,6 @@ import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/attach.dart';
|
||||
import 'package:flutter_tools/src/commands/doctor.dart';
|
||||
import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
|
||||
import 'package:flutter_tools/src/run_hot.dart';
|
||||
import 'package:flutter_tools/src/runner/flutter_command.dart';
|
||||
|
||||
final ArgParser parser = ArgParser()
|
||||
@ -100,8 +99,7 @@ Future<void> main(List<String> args) async {
|
||||
platformKernelDill: platformKernelDill,
|
||||
flutterPatchedSdk: flutterPatchedSdk,
|
||||
),
|
||||
HotRunnerConfig: () => HotRunnerConfig()..computeDartDependencies = false,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import '../fuchsia/fuchsia_device.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/devices.dart';
|
||||
import '../ios/simulators.dart';
|
||||
import '../project.dart';
|
||||
import '../protocol_discovery.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../run_cold.dart';
|
||||
@ -129,6 +130,7 @@ class AttachCommand extends FlutterCommand {
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
|
||||
final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
|
||||
final FlutterProject flutterProject = await FlutterProject.current();
|
||||
|
||||
Cache.releaseLockEarly();
|
||||
|
||||
@ -228,6 +230,7 @@ class AttachCommand extends FlutterCommand {
|
||||
projectRootPath: argResults['project-root'],
|
||||
dillOutputPath: argResults['output-dill'],
|
||||
ipv6: usesIpv6,
|
||||
flutterProject: flutterProject,
|
||||
)
|
||||
: ColdRunner(
|
||||
flutterDevices,
|
||||
@ -289,6 +292,7 @@ class HotRunnerFactory {
|
||||
String dillOutputPath,
|
||||
bool stayResident = true,
|
||||
bool ipv6 = false,
|
||||
FlutterProject flutterProject,
|
||||
}) => HotRunner(
|
||||
devices,
|
||||
target: target,
|
||||
@ -302,6 +306,7 @@ class HotRunnerFactory {
|
||||
dillOutputPath: dillOutputPath,
|
||||
stayResident: stayResident,
|
||||
ipv6: ipv6,
|
||||
flutterProject: flutterProject,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import '../convert.dart';
|
||||
import '../device.dart';
|
||||
import '../emulator.dart';
|
||||
import '../globals.dart';
|
||||
import '../project.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../run_cold.dart';
|
||||
import '../run_hot.dart';
|
||||
@ -341,6 +342,7 @@ class AppDomain extends Domain {
|
||||
if (await device.isLocalEmulator && !options.buildInfo.supportsEmulator) {
|
||||
throw '${toTitleCase(options.buildInfo.friendlyModeName)} mode is not supported for emulators.';
|
||||
}
|
||||
final FlutterProject flutterProject = await FlutterProject.current();
|
||||
|
||||
// We change the current working directory for the duration of the `start` command.
|
||||
final Directory cwd = fs.currentDirectory;
|
||||
@ -368,6 +370,7 @@ class AppDomain extends Domain {
|
||||
dillOutputPath: dillOutputPath,
|
||||
ipv6: ipv6,
|
||||
hostIsIde: true,
|
||||
flutterProject: flutterProject,
|
||||
);
|
||||
} else {
|
||||
runner = ColdRunner(
|
||||
|
@ -13,6 +13,7 @@ import '../cache.dart';
|
||||
import '../device.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/mac.dart';
|
||||
import '../project.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../run_cold.dart';
|
||||
import '../run_hot.dart';
|
||||
@ -280,6 +281,7 @@ class RunCommand extends RunCommandBase {
|
||||
// Enable hot mode by default if `--no-hot` was not passed and we are in
|
||||
// debug mode.
|
||||
final bool hotMode = shouldUseHotMode();
|
||||
final FlutterProject flutterProject = await FlutterProject.current();
|
||||
|
||||
writePidFile(argResults['pid-file']);
|
||||
|
||||
@ -389,6 +391,7 @@ class RunCommand extends RunCommandBase {
|
||||
saveCompilationTrace: argResults['train'],
|
||||
stayResident: stayResident,
|
||||
ipv6: ipv6,
|
||||
flutterProject: flutterProject,
|
||||
);
|
||||
} else {
|
||||
runner = ColdRunner(
|
||||
|
@ -1093,7 +1093,7 @@ class PubspecDependency extends PubspecLine {
|
||||
|
||||
/// Generates the File object for the pubspec.yaml file of a given Directory.
|
||||
File _pubspecFor(Directory directory) {
|
||||
return fs.file('${directory.path}/pubspec.yaml');
|
||||
return fs.file(fs.path.join(directory.path, 'pubspec.yaml'));
|
||||
}
|
||||
|
||||
/// Generates the source of a fake pubspec.yaml file given a list of
|
||||
|
@ -1,150 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO(dnfield): This will be removed when @jonahwilliams' work on build lands
|
||||
// ignore: deprecated_member_use
|
||||
import 'package:analyzer/analyzer.dart' as analyzer;
|
||||
|
||||
import '../base/file_system.dart';
|
||||
import '../dart/package_map.dart';
|
||||
|
||||
// List of flutter specific environment configurations.
|
||||
// See https://github.com/munificent/dep-interface-libraries/blob/master/Proposal.md
|
||||
// We will populate this list as required. Potentially, all of dart:* libraries
|
||||
// supported by flutter would end up here.
|
||||
final List<String> _configurationConstants = <String>['dart.library.io'];
|
||||
|
||||
String _dottedNameToString(analyzer.DottedName dottedName) {
|
||||
String result = '';
|
||||
for (analyzer.SimpleIdentifier identifier in dottedName.components) {
|
||||
if (result.isEmpty) {
|
||||
result += identifier.token.lexeme;
|
||||
} else {
|
||||
result += '.' + identifier.token.lexeme;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class DartDependencySetBuilder {
|
||||
DartDependencySetBuilder(String mainScriptPath, String packagesFilePath)
|
||||
: _mainScriptPath = canonicalizePath(mainScriptPath),
|
||||
_mainScriptUri = fs.path.toUri(mainScriptPath),
|
||||
_packagesFilePath = canonicalizePath(packagesFilePath);
|
||||
|
||||
final String _mainScriptPath;
|
||||
final String _packagesFilePath;
|
||||
|
||||
final Uri _mainScriptUri;
|
||||
|
||||
Set<String> build() {
|
||||
final List<String> dependencies = <String>[_mainScriptPath, _packagesFilePath];
|
||||
final List<Uri> toProcess = <Uri>[_mainScriptUri];
|
||||
final PackageMap packageMap = PackageMap(_packagesFilePath);
|
||||
|
||||
while (toProcess.isNotEmpty) {
|
||||
final Uri currentUri = toProcess.removeLast();
|
||||
final analyzer.CompilationUnit unit = _parse(currentUri.toFilePath());
|
||||
for (analyzer.Directive directive in unit.directives) {
|
||||
if (!(directive is analyzer.UriBasedDirective))
|
||||
continue;
|
||||
|
||||
String uriAsString;
|
||||
if (directive is analyzer.NamespaceDirective) {
|
||||
final analyzer.NamespaceDirective namespaceDirective = directive;
|
||||
// If the directive is a conditional import directive, we should
|
||||
// select the imported uri based on the condition.
|
||||
for (analyzer.Configuration configuration in namespaceDirective.configurations) {
|
||||
if (_configurationConstants.contains(_dottedNameToString(configuration.name))) {
|
||||
uriAsString = configuration.uri.stringValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uriAsString == null) {
|
||||
final analyzer.UriBasedDirective uriBasedDirective = directive;
|
||||
uriAsString = uriBasedDirective.uri.stringValue;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
try {
|
||||
uri = Uri.parse(uriAsString);
|
||||
} on FormatException {
|
||||
throw DartDependencyException('Unable to parse URI: $uriAsString');
|
||||
}
|
||||
Uri resolvedUri = analyzer.resolveRelativeUri(currentUri, uri);
|
||||
if (resolvedUri.scheme.startsWith('dart'))
|
||||
continue;
|
||||
if (resolvedUri.scheme == 'package') {
|
||||
final Uri newResolvedUri = packageMap.uriForPackage(resolvedUri);
|
||||
if (newResolvedUri == null) {
|
||||
throw DartDependencyException(
|
||||
'The following Dart file:\n'
|
||||
' ${currentUri.toFilePath()}\n'
|
||||
'...refers, in an import, to the following library:\n'
|
||||
' $resolvedUri\n'
|
||||
'That library is in a package that is not known. Maybe you forgot to '
|
||||
'mention it in your pubspec.yaml file?'
|
||||
);
|
||||
}
|
||||
resolvedUri = newResolvedUri;
|
||||
}
|
||||
final String path = canonicalizePath(resolvedUri.toFilePath());
|
||||
if (!dependencies.contains(path)) {
|
||||
if (!fs.isFileSync(path)) {
|
||||
throw DartDependencyException(
|
||||
'The following Dart file:\n'
|
||||
' ${currentUri.toFilePath()}\n'
|
||||
'...refers, in an import, to the following library:\n'
|
||||
' $path\n'
|
||||
'Unfortunately, that library does not appear to exist on your file system.'
|
||||
);
|
||||
}
|
||||
dependencies.add(path);
|
||||
toProcess.add(resolvedUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dependencies.toSet();
|
||||
}
|
||||
|
||||
analyzer.CompilationUnit _parse(String path) {
|
||||
String body;
|
||||
try {
|
||||
body = fs.file(path).readAsStringSync();
|
||||
} on FileSystemException catch (error) {
|
||||
throw DartDependencyException(
|
||||
'Could not read "$path" when determining Dart dependencies.',
|
||||
error,
|
||||
);
|
||||
}
|
||||
try {
|
||||
return analyzer.parseDirectives(body, name: path);
|
||||
} on analyzer.AnalyzerError catch (error) {
|
||||
throw DartDependencyException(
|
||||
'When trying to parse this Dart file to find its dependencies:\n'
|
||||
' $path\n'
|
||||
'...the analyzer failed with the following error:\n'
|
||||
' ${error.toString().trimRight()}',
|
||||
error,
|
||||
);
|
||||
} on analyzer.AnalyzerErrorGroup catch (error) {
|
||||
throw DartDependencyException(
|
||||
'When trying to parse this Dart file to find its dependencies:\n'
|
||||
' $path\n'
|
||||
'...the analyzer failed with the following error:\n'
|
||||
' ${error.toString().trimRight()}',
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DartDependencyException implements Exception {
|
||||
DartDependencyException(this.message, [this.parent]);
|
||||
final String message;
|
||||
final Exception parent;
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'asset.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'dart/dependencies.dart';
|
||||
import 'globals.dart';
|
||||
|
||||
class DependencyChecker {
|
||||
DependencyChecker(this.builder, this.assets);
|
||||
|
||||
final DartDependencySetBuilder builder;
|
||||
final Set<String> _dependencies = <String>{};
|
||||
final AssetBundle assets;
|
||||
|
||||
/// Returns [true] if any components have been modified after [threshold] or
|
||||
/// if it cannot be determined.
|
||||
bool check(DateTime threshold) {
|
||||
_dependencies.clear();
|
||||
// Build the set of Dart dependencies.
|
||||
try {
|
||||
_dependencies.addAll(builder.build());
|
||||
} catch (e, st) {
|
||||
printTrace('DependencyChecker: error determining .dart dependencies:\n$e\n$st');
|
||||
return true;
|
||||
}
|
||||
// TODO(johnmccutchan): Extract dependencies from the AssetBundle too.
|
||||
|
||||
// Check all dependency modification times.
|
||||
for (String path in _dependencies) {
|
||||
final File file = fs.file(path);
|
||||
final FileStat stat = file.statSync();
|
||||
if (stat.type == FileSystemEntityType.notFound) {
|
||||
printTrace('DependencyChecker: Error stating $path.');
|
||||
return true;
|
||||
}
|
||||
if (stat.modified.isAfter(threshold)) {
|
||||
printTrace('DependencyChecker: $path is newer than $threshold');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
printTrace('DependencyChecker: nothing is modified after $threshold.');
|
||||
return false;
|
||||
}
|
||||
}
|
@ -30,8 +30,6 @@ DevFSConfig get devFSConfig => context[DevFSConfig];
|
||||
|
||||
/// Common superclass for content copied to the device.
|
||||
abstract class DevFSContent {
|
||||
bool _exists = true;
|
||||
|
||||
/// Return true if this is the first time this method is called
|
||||
/// or if the entry has been modified since this method was last called.
|
||||
bool get isModified;
|
||||
@ -59,13 +57,6 @@ abstract class DevFSContent {
|
||||
class DevFSFileContent extends DevFSContent {
|
||||
DevFSFileContent(this.file);
|
||||
|
||||
static DevFSFileContent clone(DevFSFileContent fsFileContent) {
|
||||
final DevFSFileContent newFsFileContent = DevFSFileContent(fsFileContent.file);
|
||||
newFsFileContent._linkTarget = fsFileContent._linkTarget;
|
||||
newFsFileContent._fileStat = fsFileContent._fileStat;
|
||||
return newFsFileContent;
|
||||
}
|
||||
|
||||
final FileSystemEntity file;
|
||||
FileSystemEntity _linkTarget;
|
||||
FileStat _fileStat;
|
||||
@ -218,7 +209,6 @@ abstract class DevFSOperations {
|
||||
Future<Uri> create(String fsName);
|
||||
Future<dynamic> destroy(String fsName);
|
||||
Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content);
|
||||
Future<dynamic> deleteFile(String fsName, Uri deviceUri);
|
||||
}
|
||||
|
||||
/// An implementation of [DevFSOperations] that speaks to the
|
||||
@ -261,11 +251,6 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
|
||||
printTrace('DevFS: Failed to write $deviceUri: $error');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> deleteFile(String fsName, Uri deviceUri) async {
|
||||
// TODO(johnmccutchan): Add file deletion to the devFS protocol.
|
||||
}
|
||||
}
|
||||
|
||||
class DevFSException implements Exception {
|
||||
@ -288,16 +273,14 @@ class _DevFSHttpWriter {
|
||||
int _inFlight = 0;
|
||||
Map<Uri, DevFSContent> _outstanding;
|
||||
Completer<void> _completer;
|
||||
HttpClient _client;
|
||||
final HttpClient _client = HttpClient();
|
||||
|
||||
Future<void> write(Map<Uri, DevFSContent> entries) async {
|
||||
_client = HttpClient();
|
||||
_client.maxConnectionsPerHost = kMaxInFlight;
|
||||
_completer = Completer<void>();
|
||||
_outstanding = Map<Uri, DevFSContent>.from(entries);
|
||||
_scheduleWrites();
|
||||
await _completer.future;
|
||||
_client.close();
|
||||
}
|
||||
|
||||
void _scheduleWrites() {
|
||||
@ -405,9 +388,6 @@ class DevFS {
|
||||
final Map<Uri, DevFSContent> _entries = <Uri, DevFSContent>{};
|
||||
final Set<String> assetPathsToEvict = <String>{};
|
||||
|
||||
final List<Future<Map<String, dynamic>>> _pendingOperations =
|
||||
<Future<Map<String, dynamic>>>[];
|
||||
|
||||
Uri _baseUri;
|
||||
Uri get baseUri => _baseUri;
|
||||
|
||||
@ -453,66 +433,33 @@ class DevFS {
|
||||
DateTime firstBuildTime,
|
||||
bool bundleFirstUpload = false,
|
||||
bool bundleDirty = false,
|
||||
Set<String> fileFilter,
|
||||
@required ResidentCompiler generator,
|
||||
String dillOutputPath,
|
||||
@required bool trackWidgetCreation,
|
||||
bool fullRestart = false,
|
||||
String projectRootPath,
|
||||
@required String pathToReload,
|
||||
@required List<String> invalidatedFiles,
|
||||
}) async {
|
||||
assert(trackWidgetCreation != null);
|
||||
assert(generator != null);
|
||||
// Mark all entries as possibly deleted.
|
||||
for (DevFSContent content in _entries.values) {
|
||||
content._exists = false;
|
||||
}
|
||||
|
||||
// Scan workspace, packages, and assets
|
||||
printTrace('DevFS: Starting sync from $rootDirectory');
|
||||
logger.printTrace('Scanning project files');
|
||||
await _scanDirectory(rootDirectory,
|
||||
recursive: true,
|
||||
fileFilter: fileFilter);
|
||||
if (fs.isFileSync(_packagesFilePath)) {
|
||||
printTrace('Scanning package files');
|
||||
await _scanPackages(fileFilter);
|
||||
}
|
||||
if (bundle != null) {
|
||||
printTrace('Scanning asset files');
|
||||
// We write the assets into the AssetBundle working dir so that they
|
||||
// are in the same location in DevFS and the iOS simulator.
|
||||
final String assetDirectory = getAssetBuildDirectory();
|
||||
bundle.entries.forEach((String archivePath, DevFSContent content) {
|
||||
_scanBundleEntry(archivePath, content);
|
||||
final Uri deviceUri = fs.path.toUri(fs.path.join(assetDirectory, archivePath));
|
||||
_entries[deviceUri] = content;
|
||||
});
|
||||
}
|
||||
|
||||
// Handle deletions.
|
||||
printTrace('Scanning for deleted files');
|
||||
final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory());
|
||||
final List<Uri> toRemove = <Uri>[];
|
||||
_entries.forEach((Uri deviceUri, DevFSContent content) {
|
||||
if (!content._exists) {
|
||||
final Future<Map<String, dynamic>> operation =
|
||||
_operations.deleteFile(fsName, deviceUri)
|
||||
.then<Map<String, dynamic>>((dynamic v) => v?.cast<String,dynamic>());
|
||||
if (operation != null)
|
||||
_pendingOperations.add(operation);
|
||||
toRemove.add(deviceUri);
|
||||
if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
|
||||
final String archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
|
||||
assetPathsToEvict.add(archivePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (toRemove.isNotEmpty) {
|
||||
printTrace('Removing deleted files');
|
||||
toRemove.forEach(_entries.remove);
|
||||
await Future.wait<Map<String, dynamic>>(_pendingOperations);
|
||||
_pendingOperations.clear();
|
||||
}
|
||||
|
||||
// Update modified files
|
||||
int syncedBytes = 0;
|
||||
final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory());
|
||||
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
|
||||
|
||||
int syncedBytes = 0;
|
||||
_entries.forEach((Uri deviceUri, DevFSContent content) {
|
||||
String archivePath;
|
||||
if (deviceUri.path.startsWith(assetBuildDirPrefix))
|
||||
@ -527,30 +474,10 @@ class DevFS {
|
||||
assetPathsToEvict.add(archivePath);
|
||||
}
|
||||
});
|
||||
// We run generator even if [dirtyEntries] was empty because we want to
|
||||
// keep logic of accepting/rejecting generator's output simple: we must
|
||||
// accept/reject generator's output after every [update] call. Incremental
|
||||
// run with no changes is supposed to be fast (considering that it is
|
||||
// initiated by user key press).
|
||||
final List<String> invalidatedFiles = <String>[];
|
||||
final Set<Uri> filesUris = <Uri>{};
|
||||
for (Uri uri in dirtyEntries.keys.toList()) {
|
||||
if (!uri.path.startsWith(assetBuildDirPrefix)) {
|
||||
final DevFSContent content = dirtyEntries[uri];
|
||||
if (content is DevFSFileContent) {
|
||||
filesUris.add(uri);
|
||||
invalidatedFiles.add(content.file.uri.toString());
|
||||
syncedBytes -= content.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No need to send source files because all compilation is done on the
|
||||
// host and result of compilation is single kernel file.
|
||||
filesUris.forEach(dirtyEntries.remove);
|
||||
printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
|
||||
if (fullRestart) {
|
||||
generator.reset();
|
||||
}
|
||||
printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
|
||||
final CompilerOutput compilerOutput = await generator.recompile(
|
||||
mainPath,
|
||||
invalidatedFiles,
|
||||
@ -566,16 +493,13 @@ class DevFS {
|
||||
? fs.path.relative(pathToReload, from: projectRootPath)
|
||||
: pathToReload,
|
||||
);
|
||||
if (!dirtyEntries.containsKey(entryUri)) {
|
||||
final DevFSFileContent content = DevFSFileContent(fs.file(compiledBinary));
|
||||
dirtyEntries[entryUri] = content;
|
||||
syncedBytes += content.size;
|
||||
dirtyEntries[entryUri] = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dirtyEntries.isNotEmpty) {
|
||||
printTrace('Updating files');
|
||||
if (_httpWriter != null) {
|
||||
if (dirtyEntries.isNotEmpty) {
|
||||
try {
|
||||
await _httpWriter.write(dirtyEntries);
|
||||
} on SocketException catch (socketException, stackTrace) {
|
||||
@ -585,209 +509,12 @@ class DevFS {
|
||||
printError('Could not update files on device: $exception');
|
||||
throw DevFSException('Sync failed', exception, stackTrace);
|
||||
}
|
||||
} else {
|
||||
// Make service protocol requests for each.
|
||||
dirtyEntries.forEach((Uri deviceUri, DevFSContent content) {
|
||||
final Future<Map<String, dynamic>> operation =
|
||||
_operations.writeFile(fsName, deviceUri, content)
|
||||
.then<Map<String, dynamic>>((dynamic v) => v?.cast<String, dynamic>());
|
||||
if (operation != null)
|
||||
_pendingOperations.add(operation);
|
||||
});
|
||||
await Future.wait<Map<String, dynamic>>(_pendingOperations, eagerError: true);
|
||||
_pendingOperations.clear();
|
||||
}
|
||||
}
|
||||
|
||||
printTrace('DevFS: Sync finished');
|
||||
return UpdateFSReport(success: true, syncedBytes: syncedBytes,
|
||||
invalidatedSourcesCount: invalidatedFiles.length);
|
||||
}
|
||||
|
||||
void _scanFile(Uri deviceUri, FileSystemEntity file) {
|
||||
final DevFSContent content = _entries.putIfAbsent(deviceUri, () => DevFSFileContent(file));
|
||||
content._exists = true;
|
||||
}
|
||||
|
||||
void _scanBundleEntry(String archivePath, DevFSContent content) {
|
||||
// We write the assets into the AssetBundle working dir so that they
|
||||
// are in the same location in DevFS and the iOS simulator.
|
||||
final Uri deviceUri = fs.path.toUri(fs.path.join(getAssetBuildDirectory(), archivePath));
|
||||
|
||||
_entries[deviceUri] = content;
|
||||
content._exists = true;
|
||||
}
|
||||
|
||||
bool _shouldIgnore(Uri deviceUri) {
|
||||
final List<String> ignoredUriPrefixes = <String>['android/',
|
||||
_asUriPath(getBuildDirectory()),
|
||||
'ios/',
|
||||
'.pub/'];
|
||||
for (String ignoredUriPrefix in ignoredUriPrefixes) {
|
||||
if (deviceUri.path.startsWith(ignoredUriPrefix))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _shouldSkip(
|
||||
FileSystemEntity file,
|
||||
String relativePath,
|
||||
Uri directoryUriOnDevice, {
|
||||
bool ignoreDotFiles = true,
|
||||
}) {
|
||||
if (file is Directory) {
|
||||
// Skip non-files.
|
||||
return true;
|
||||
}
|
||||
assert((file is Link) || (file is File));
|
||||
final String basename = fs.path.basename(file.path);
|
||||
if (ignoreDotFiles && basename.startsWith('.')) {
|
||||
// Skip dot files, but not the '.packages' file (even though in dart1
|
||||
// mode devfs['.packages'] will be overwritten with synthesized string content).
|
||||
return basename != '.packages';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Uri _directoryUriOnDevice(Uri directoryUriOnDevice, Directory directory) {
|
||||
if (directoryUriOnDevice == null) {
|
||||
final String relativeRootPath = fs.path.relative(directory.path, from: rootDirectory.path);
|
||||
if (relativeRootPath == '.') {
|
||||
directoryUriOnDevice = Uri();
|
||||
} else {
|
||||
directoryUriOnDevice = fs.path.toUri(relativeRootPath);
|
||||
}
|
||||
}
|
||||
return directoryUriOnDevice;
|
||||
}
|
||||
|
||||
/// Scan all files from the [fileFilter] that are contained in [directory] and
|
||||
/// pass various filters (e.g. ignoreDotFiles).
|
||||
Future<bool> _scanFilteredDirectory(
|
||||
Set<String> fileFilter,
|
||||
Directory directory, {
|
||||
Uri directoryUriOnDevice,
|
||||
bool ignoreDotFiles = true,
|
||||
}) async {
|
||||
directoryUriOnDevice =
|
||||
_directoryUriOnDevice(directoryUriOnDevice, directory);
|
||||
try {
|
||||
final String absoluteDirectoryPath = canonicalizePath(directory.path);
|
||||
// For each file in the file filter.
|
||||
for (String filePath in fileFilter) {
|
||||
if (!filePath.startsWith(absoluteDirectoryPath)) {
|
||||
// File is not in this directory. Skip.
|
||||
continue;
|
||||
}
|
||||
final String relativePath =
|
||||
fs.path.relative(filePath, from: directory.path);
|
||||
final FileSystemEntity file = fs.file(filePath);
|
||||
if (_shouldSkip(file, relativePath, directoryUriOnDevice, ignoreDotFiles: ignoreDotFiles)) {
|
||||
continue;
|
||||
}
|
||||
final Uri deviceUri = directoryUriOnDevice.resolveUri(fs.path.toUri(relativePath));
|
||||
if (!_shouldIgnore(deviceUri))
|
||||
_scanFile(deviceUri, file);
|
||||
}
|
||||
} on FileSystemException catch (e) {
|
||||
_printScanDirectoryError(directory.path, e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Scan all files in [directory] that pass various filters (e.g. ignoreDotFiles).
|
||||
Future<bool> _scanDirectory(
|
||||
Directory directory, {
|
||||
Uri directoryUriOnDevice,
|
||||
bool recursive = false,
|
||||
bool ignoreDotFiles = true,
|
||||
Set<String> fileFilter,
|
||||
}) async {
|
||||
directoryUriOnDevice = _directoryUriOnDevice(directoryUriOnDevice, directory);
|
||||
if ((fileFilter != null) && fileFilter.isNotEmpty) {
|
||||
// When the fileFilter isn't empty, we can skip crawling the directory
|
||||
// tree and instead use the fileFilter as the source of potential files.
|
||||
return _scanFilteredDirectory(fileFilter,
|
||||
directory,
|
||||
directoryUriOnDevice: directoryUriOnDevice,
|
||||
ignoreDotFiles: ignoreDotFiles);
|
||||
}
|
||||
try {
|
||||
final Stream<FileSystemEntity> files =
|
||||
directory.list(recursive: recursive, followLinks: false);
|
||||
await for (FileSystemEntity file in files) {
|
||||
if (!devFSConfig.noDirectorySymlinks && (file is Link)) {
|
||||
// Check if this is a symlink to a directory and skip it.
|
||||
try {
|
||||
final FileSystemEntityType linkType =
|
||||
fs.statSync(file.resolveSymbolicLinksSync()).type;
|
||||
if (linkType == FileSystemEntityType.directory)
|
||||
continue;
|
||||
} on FileSystemException catch (e) {
|
||||
_printScanDirectoryError(file.path, e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
final String relativePath =
|
||||
fs.path.relative(file.path, from: directory.path);
|
||||
if (_shouldSkip(file, relativePath, directoryUriOnDevice, ignoreDotFiles: ignoreDotFiles)) {
|
||||
continue;
|
||||
}
|
||||
final Uri deviceUri = directoryUriOnDevice.resolveUri(fs.path.toUri(relativePath));
|
||||
if (!_shouldIgnore(deviceUri))
|
||||
_scanFile(deviceUri, file);
|
||||
}
|
||||
} on FileSystemException catch (e) {
|
||||
_printScanDirectoryError(directory.path, e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _printScanDirectoryError(String path, Exception e) {
|
||||
printError(
|
||||
'Error while scanning $path.\n'
|
||||
'Hot Reload might not work until the following error is resolved:\n'
|
||||
'$e\n'
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _scanPackages(Set<String> fileFilter) async {
|
||||
StringBuffer sb;
|
||||
final PackageMap packageMap = PackageMap(_packagesFilePath);
|
||||
|
||||
for (String packageName in packageMap.map.keys) {
|
||||
final Uri packageUri = packageMap.map[packageName];
|
||||
final String packagePath = fs.path.fromUri(packageUri);
|
||||
final Directory packageDirectory = fs.directory(packageUri);
|
||||
Uri directoryUriOnDevice = fs.path.toUri(fs.path.join('packages', packageName) + fs.path.separator);
|
||||
bool packageExists = packageDirectory.existsSync();
|
||||
|
||||
if (!packageExists) {
|
||||
// If the package directory doesn't exist at all, we ignore it.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fs.path.isWithin(rootDirectory.path, packagePath)) {
|
||||
// We already scanned everything under the root directory.
|
||||
directoryUriOnDevice = fs.path.toUri(
|
||||
fs.path.relative(packagePath, from: rootDirectory.path) + fs.path.separator
|
||||
);
|
||||
} else {
|
||||
packageExists =
|
||||
await _scanDirectory(packageDirectory,
|
||||
directoryUriOnDevice: directoryUriOnDevice,
|
||||
recursive: true,
|
||||
fileFilter: fileFilter);
|
||||
}
|
||||
if (packageExists) {
|
||||
sb ??= StringBuffer();
|
||||
sb.writeln('$packageName:$directoryUriOnDevice');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Converts a platform-specific file path to a platform-independent Uri path.
|
||||
String _asUriPath(String filePath) => fs.path.toUri(filePath).path + '/';
|
||||
|
@ -18,9 +18,7 @@ import 'base/utils.dart';
|
||||
import 'build_info.dart';
|
||||
import 'codegen.dart';
|
||||
import 'compile.dart';
|
||||
import 'dart/dependencies.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'dependency_checker.dart';
|
||||
import 'devfs.dart';
|
||||
import 'device.dart';
|
||||
import 'globals.dart';
|
||||
@ -444,10 +442,10 @@ class FlutterDevice {
|
||||
DateTime firstBuildTime,
|
||||
bool bundleFirstUpload = false,
|
||||
bool bundleDirty = false,
|
||||
Set<String> fileFilter,
|
||||
bool fullRestart = false,
|
||||
String projectRootPath,
|
||||
String pathToReload,
|
||||
@required List<String> invalidatedFiles,
|
||||
}) async {
|
||||
final Status devFSStatus = logger.startProgress(
|
||||
'Syncing files to device ${device.name}...',
|
||||
@ -462,13 +460,13 @@ class FlutterDevice {
|
||||
firstBuildTime: firstBuildTime,
|
||||
bundleFirstUpload: bundleFirstUpload,
|
||||
bundleDirty: bundleDirty,
|
||||
fileFilter: fileFilter,
|
||||
generator: generator,
|
||||
fullRestart: fullRestart,
|
||||
dillOutputPath: dillOutputPath,
|
||||
trackWidgetCreation: trackWidgetCreation,
|
||||
projectRootPath: projectRootPath,
|
||||
pathToReload: pathToReload,
|
||||
invalidatedFiles: invalidatedFiles,
|
||||
);
|
||||
} on DevFSException {
|
||||
devFSStatus.cancel();
|
||||
@ -945,21 +943,19 @@ abstract class ResidentRunner {
|
||||
}
|
||||
|
||||
bool hasDirtyDependencies(FlutterDevice device) {
|
||||
/// When using the build system, dependency analysis is handled by build
|
||||
/// runner instead.
|
||||
if (experimentalBuildEnabled) {
|
||||
return false;
|
||||
}
|
||||
final DartDependencySetBuilder dartDependencySetBuilder =
|
||||
DartDependencySetBuilder(mainPath, packagesFilePath);
|
||||
final DependencyChecker dependencyChecker =
|
||||
DependencyChecker(dartDependencySetBuilder, assetBundle);
|
||||
if (device.package.packagesFile == null || !device.package.packagesFile.existsSync()) {
|
||||
return true;
|
||||
}
|
||||
final DateTime lastBuildTime = device.package.packagesFile.statSync().modified;
|
||||
|
||||
return dependencyChecker.check(lastBuildTime);
|
||||
// Leave pubspec null to check all dependencies.
|
||||
final ProjectFileInvalidator projectFileInvalidator = ProjectFileInvalidator(device.package.packagesFile.path, null);
|
||||
projectFileInvalidator.findInvalidated();
|
||||
final int lastBuildTime = device.package.packagesFile.statSync().modified.millisecondsSinceEpoch;
|
||||
for (int updateTime in projectFileInvalidator.updateTime.values) {
|
||||
if (updateTime > lastBuildTime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> preStop() async { }
|
||||
|
@ -7,29 +7,28 @@ import 'dart:async';
|
||||
import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
|
||||
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'base/common.dart';
|
||||
import 'base/context.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/logger.dart';
|
||||
import 'base/platform.dart';
|
||||
import 'base/terminal.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'build_info.dart';
|
||||
import 'codegen.dart';
|
||||
import 'compile.dart';
|
||||
import 'convert.dart';
|
||||
import 'dart/dependencies.dart';
|
||||
import 'dart/pub.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'devfs.dart';
|
||||
import 'device.dart';
|
||||
import 'globals.dart';
|
||||
import 'project.dart';
|
||||
import 'resident_runner.dart';
|
||||
import 'usage.dart';
|
||||
import 'vmservice.dart';
|
||||
|
||||
class HotRunnerConfig {
|
||||
/// Should the hot runner compute the minimal Dart dependencies?
|
||||
bool computeDartDependencies = true;
|
||||
/// Should the hot runner assume that the minimal Dart dependencies do not change?
|
||||
bool stableDartDependencies = false;
|
||||
/// A hook for implementations to perform any necessary initialization prior
|
||||
@ -71,6 +70,7 @@ class HotRunner extends ResidentRunner {
|
||||
bool saveCompilationTrace = false,
|
||||
bool stayResident = true,
|
||||
bool ipv6 = false,
|
||||
FlutterProject flutterProject,
|
||||
}) : super(devices,
|
||||
target: target,
|
||||
debuggingOptions: debuggingOptions,
|
||||
@ -79,14 +79,19 @@ class HotRunner extends ResidentRunner {
|
||||
packagesFilePath: packagesFilePath,
|
||||
saveCompilationTrace: saveCompilationTrace,
|
||||
stayResident: stayResident,
|
||||
ipv6: ipv6);
|
||||
ipv6: ipv6) {
|
||||
fileInvalidator = ProjectFileInvalidator(
|
||||
packagesFilePath ?? fs.path.absolute(PackageMap.globalPackagesPath),
|
||||
flutterProject,
|
||||
);
|
||||
}
|
||||
|
||||
final bool benchmarkMode;
|
||||
final File applicationBinary;
|
||||
final bool hostIsIde;
|
||||
bool _didAttach = false;
|
||||
Set<String> _dartDependencies;
|
||||
final String dillOutputPath;
|
||||
ProjectFileInvalidator fileInvalidator;
|
||||
|
||||
final Map<String, List<int>> benchmarkData = <String, List<int>>{};
|
||||
// The initial launch is from a snapshot.
|
||||
@ -98,54 +103,8 @@ class HotRunner extends ResidentRunner {
|
||||
benchmarkData[name].add(value);
|
||||
}
|
||||
|
||||
Future<bool> _refreshDartDependencies() async {
|
||||
if (!hotRunnerConfig.computeDartDependencies) {
|
||||
// Disabled.
|
||||
return true;
|
||||
}
|
||||
if (_dartDependencies != null) {
|
||||
// Already computed.
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Will return immediately if pubspec.yaml is up-to-date.
|
||||
await pubGet(
|
||||
context: PubContext.pubGet,
|
||||
directory: projectRootPath,
|
||||
);
|
||||
} on ToolExit catch (error) {
|
||||
printError(
|
||||
'Unable to reload your application because "flutter packages get" failed to update '
|
||||
'package dependencies.\n'
|
||||
'$error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// When using the build system, dependency analysis is handled by build
|
||||
/// runner instead.
|
||||
if (experimentalBuildEnabled) {
|
||||
return true;
|
||||
}
|
||||
final DartDependencySetBuilder dartDependencySetBuilder = DartDependencySetBuilder(mainPath, packagesFilePath);
|
||||
try {
|
||||
_dartDependencies = Set<String>.from(dartDependencySetBuilder.build());
|
||||
} on DartDependencyException catch (error) {
|
||||
printError(
|
||||
'Your application could not be compiled, because its dependencies could not be established.\n'
|
||||
'$error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _reloadSourcesService(
|
||||
String isolateId, {
|
||||
bool force = false,
|
||||
bool pause = false,
|
||||
}) async {
|
||||
Future<void> _reloadSourcesService(String isolateId,
|
||||
{ bool force = false, bool pause = false }) async {
|
||||
// TODO(cbernaschina): check that isolateId is the id of the UI isolate.
|
||||
final OperationResult result = await restart(pauseAfterRestart: pause);
|
||||
if (!result.isOk) {
|
||||
@ -257,7 +216,6 @@ class HotRunner extends ResidentRunner {
|
||||
// Measure time to perform a hot restart.
|
||||
printStatus('Benchmarking hot restart');
|
||||
await restart(fullRestart: true);
|
||||
// TODO(johnmccutchan): Modify script entry point.
|
||||
printStatus('Benchmarking hot reload');
|
||||
// Measure time to perform a hot reload.
|
||||
await restart(fullRestart: false);
|
||||
@ -296,12 +254,6 @@ class HotRunner extends ResidentRunner {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Determine the Dart dependencies eagerly.
|
||||
if (!await _refreshDartDependencies()) {
|
||||
// Some kind of source level error or missing file in the Dart code.
|
||||
return 1;
|
||||
}
|
||||
|
||||
firstBuildTime = DateTime.now();
|
||||
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
@ -336,8 +288,6 @@ class HotRunner extends ResidentRunner {
|
||||
result = await restart(fullRestart: false);
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else if (lower == 'l') {
|
||||
@ -364,10 +314,6 @@ class HotRunner extends ResidentRunner {
|
||||
}
|
||||
|
||||
Future<UpdateFSReport> _updateDevFS({ bool fullRestart = false }) async {
|
||||
if (!await _refreshDartDependencies()) {
|
||||
// Did not update DevFS because of a Dart source error.
|
||||
return UpdateFSReport(success: false);
|
||||
}
|
||||
final bool isFirstUpload = assetBundle.wasBuiltOnce() == false;
|
||||
final bool rebuildBundle = assetBundle.needsBuild();
|
||||
if (rebuildBundle) {
|
||||
@ -376,7 +322,7 @@ class HotRunner extends ResidentRunner {
|
||||
if (result != 0)
|
||||
return UpdateFSReport(success: false);
|
||||
}
|
||||
|
||||
final List<String> invalidatedFiles = fileInvalidator.findInvalidated();
|
||||
final UpdateFSReport results = UpdateFSReport(success: true);
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
results.incorporateResults(await device.updateDevFS(
|
||||
@ -386,39 +332,15 @@ class HotRunner extends ResidentRunner {
|
||||
firstBuildTime: firstBuildTime,
|
||||
bundleFirstUpload: isFirstUpload,
|
||||
bundleDirty: isFirstUpload == false && rebuildBundle,
|
||||
fileFilter: _dartDependencies,
|
||||
fullRestart: fullRestart,
|
||||
projectRootPath: projectRootPath,
|
||||
pathToReload: getReloadPath(fullRestart: fullRestart),
|
||||
invalidatedFiles: invalidatedFiles,
|
||||
));
|
||||
}
|
||||
if (!results.success) {
|
||||
return results;
|
||||
}
|
||||
|
||||
if (!hotRunnerConfig.stableDartDependencies) {
|
||||
// Clear the set after the sync so they are recomputed next time.
|
||||
_dartDependencies = null;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
Future<void> _evictDirtyAssets() {
|
||||
final List<Future<Map<String, dynamic>>> futures = <Future<Map<String, dynamic>>>[];
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
if (device.devFS.assetPathsToEvict.isEmpty)
|
||||
continue;
|
||||
if (device.views.first.uiIsolate == null) {
|
||||
printError('Application isolate not found for $device');
|
||||
continue;
|
||||
}
|
||||
for (String assetPath in device.devFS.assetPathsToEvict)
|
||||
futures.add(device.views.first.uiIsolate.flutterEvictAsset(assetPath));
|
||||
device.devFS.assetPathsToEvict.clear();
|
||||
}
|
||||
return Future.wait<Map<String, dynamic>>(futures);
|
||||
}
|
||||
|
||||
void _resetDirtyAssets() {
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
device.devFS.assetPathsToEvict.clear();
|
||||
@ -811,9 +733,9 @@ class HotRunner extends ResidentRunner {
|
||||
return OperationResult(OperationResult.ok.code, reloadMessage);
|
||||
}
|
||||
}
|
||||
assert(reassembleViews.isNotEmpty);
|
||||
printTrace('Evicting dirty assets');
|
||||
await _evictDirtyAssets();
|
||||
assert(reassembleViews.isNotEmpty);
|
||||
printTrace('Reassembling application');
|
||||
bool failedReassemble = false;
|
||||
final List<Future<void>> futures = <Future<void>>[];
|
||||
@ -886,7 +808,6 @@ class HotRunner extends ResidentRunner {
|
||||
// Only report timings if we reloaded a single view without any errors.
|
||||
if ((reassembleViews.length == 1) && !failedReassemble && shouldReportReloadTime)
|
||||
flutterUsage.sendTiming('hot', 'reload', reloadDuration);
|
||||
|
||||
return OperationResult(
|
||||
failedReassemble ? 1 : OperationResult.ok.code,
|
||||
reloadMessage,
|
||||
@ -965,6 +886,23 @@ class HotRunner extends ResidentRunner {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _evictDirtyAssets() {
|
||||
final List<Future<Map<String, dynamic>>> futures = <Future<Map<String, dynamic>>>[];
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
if (device.devFS.assetPathsToEvict.isEmpty)
|
||||
continue;
|
||||
if (device.views.first.uiIsolate == null) {
|
||||
printError('Application isolate not found for $device');
|
||||
continue;
|
||||
}
|
||||
for (String assetPath in device.devFS.assetPathsToEvict) {
|
||||
futures.add(device.views.first.uiIsolate.flutterEvictAsset(assetPath));
|
||||
}
|
||||
device.devFS.assetPathsToEvict.clear();
|
||||
}
|
||||
return Future.wait<Map<String, dynamic>>(futures);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> cleanupAfterSignal() async {
|
||||
await stopEchoingDeviceLog();
|
||||
@ -988,3 +926,106 @@ class HotRunner extends ResidentRunner {
|
||||
await stopEchoingDeviceLog();
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectFileInvalidator {
|
||||
ProjectFileInvalidator(this._packagesPath, this._flutterProject) {
|
||||
final File packagesFile = fs.file(_packagesPath);
|
||||
if (packagesFile.existsSync()) {
|
||||
_packagesUpdateTime = packagesFile.statSync().modified.millisecondsSinceEpoch;
|
||||
_packageMap = PackageMap(_packagesPath).map;
|
||||
} else {
|
||||
_packagesUpdateTime = -1;
|
||||
_packageMap = const <String, Uri>{};
|
||||
}
|
||||
_computePackageMap(_packageMap, _flutterProject);
|
||||
}
|
||||
|
||||
// Used to avoid watching pubspec directories. This will not change even with pub upgrade,
|
||||
// because that actually switches the directory and requires a corresponding
|
||||
// update to .packages
|
||||
static const String _pubCachePathLinuxAndWindows = '.pub-cache';
|
||||
static const String _pubCachePathWindows = 'Pub/Cache';
|
||||
|
||||
Map<String, Uri> _packageMap;
|
||||
final String _packagesPath;
|
||||
final FlutterProject _flutterProject;
|
||||
final Map<String, int> _updateTime = <String, int>{};
|
||||
int _packagesUpdateTime;
|
||||
|
||||
Map<String, int> get updateTime => _updateTime;
|
||||
|
||||
@visibleForTesting
|
||||
Map<String, Uri> get packageMap => _packageMap;
|
||||
|
||||
static void _computePackageMap(Map<String, Uri> packageMap, FlutterProject flutterProject) {
|
||||
if (flutterProject != null && flutterProject.pubspecFile.existsSync()) {
|
||||
try {
|
||||
final YamlMap pubspec = loadYamlDocument(flutterProject.pubspecFile.readAsStringSync()).contents;
|
||||
final YamlMap dependencies = pubspec['dependencies'];
|
||||
final Set<String> relevantDependencies = Set<String>.from(dependencies.keys);
|
||||
// Remove any packages which were tagged as dev dependenices,
|
||||
// But don't remove the app itself!
|
||||
for (String packageName in packageMap.keys.toList()) {
|
||||
if (!relevantDependencies.contains(packageName) && packageName != flutterProject.manifest.appName) {
|
||||
packageMap.remove(packageName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// If we detect a pubspec formatting problem, fallback to the packages file.
|
||||
}
|
||||
}
|
||||
// Remove any packages which are derived from the pub cache.
|
||||
for (String packageName in packageMap.keys.toList()) {
|
||||
final String path = packageMap[packageName].path;
|
||||
if ((platform.isWindows && path.contains(_pubCachePathWindows))
|
||||
|| path.contains(_pubCachePathLinuxAndWindows)) {
|
||||
packageMap.remove(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> findInvalidated() {
|
||||
final File packagesFile = fs.file(_packagesPath);
|
||||
if (packagesFile.existsSync()) {
|
||||
final int newPackagesUpdateTime = packagesFile.statSync().modified.millisecondsSinceEpoch;
|
||||
// Hot reloading with an updated package will often times kill a non-trivial
|
||||
// appliction. This _might_ work, given certain application size and package
|
||||
// constraints, so instead of exiting we print a warning so that the user has
|
||||
// some hint on what went wrong.
|
||||
if (newPackagesUpdateTime > _packagesUpdateTime) {
|
||||
printError('Warning: updated dependencies detected. The Flutter application will require a restart to safely use new packages.');
|
||||
}
|
||||
_packagesUpdateTime = newPackagesUpdateTime;
|
||||
}
|
||||
final List<String> invalidatedFiles = <String>[];
|
||||
for (String packageName in _packageMap.keys) {
|
||||
final Uri packageUri =_packageMap[packageName];
|
||||
_scanDirectory(packageUri, invalidatedFiles);
|
||||
}
|
||||
return invalidatedFiles;
|
||||
}
|
||||
|
||||
void _scanDirectory(Uri path, List<String> invalidatedFiles) {
|
||||
final Directory directory = fs.directory(path);
|
||||
if (!directory.existsSync()) {
|
||||
return;
|
||||
}
|
||||
for (FileSystemEntity entity in directory.listSync(recursive: true)) {
|
||||
if (entity.path.endsWith('.dart')) {
|
||||
final int oldUpdatedAt = _updateTime[entity.path];
|
||||
final int updatedAt = fs.statSync(entity.path).modified.millisecondsSinceEpoch;
|
||||
if (oldUpdatedAt == null || updatedAt > oldUpdatedAt) {
|
||||
// On windows convert to file uri in expected format.
|
||||
if (platform.isWindows) {
|
||||
final Uri uri = Uri.file(entity.path, windows: platform.isWindows);
|
||||
invalidatedFiles.add(uri.toString());
|
||||
} else {
|
||||
invalidatedFiles.add(entity.path);
|
||||
}
|
||||
}
|
||||
_updateTime[entity.path] = updatedAt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ environment:
|
||||
|
||||
dependencies:
|
||||
# To update these, use "flutter update-packages --force-upgrade".
|
||||
analyzer: 0.35.3
|
||||
archive: 2.0.8
|
||||
args: 1.5.1
|
||||
bsdiff: 0.1.0
|
||||
@ -53,6 +52,7 @@ dependencies:
|
||||
build_modules: 1.0.9
|
||||
build_daemon: 0.4.2
|
||||
|
||||
analyzer: 0.35.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
async: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
bazel_worker: 0.1.20 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
boolean_selector: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
@ -100,8 +100,8 @@ dev_dependencies:
|
||||
collection: 1.14.11
|
||||
mockito: 4.0.0
|
||||
file_testing: 2.1.0
|
||||
test: 1.5.3
|
||||
vm_service_lib: 3.14.2
|
||||
test: 1.5.3
|
||||
build_runner: 1.2.8
|
||||
build_vm_compilers: 0.1.1+5
|
||||
build_test: 0.10.6
|
||||
|
@ -120,6 +120,7 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
packagesFilePath: anyNamed('packagesFilePath'),
|
||||
usesTerminalUI: anyNamed('usesTerminalUI'),
|
||||
flutterProject: anyNamed('flutterProject'),
|
||||
ipv6: false,
|
||||
),
|
||||
).thenReturn(mockHotRunner);
|
||||
@ -151,6 +152,7 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
packagesFilePath: anyNamed('packagesFilePath'),
|
||||
usesTerminalUI: anyNamed('usesTerminalUI'),
|
||||
flutterProject: anyNamed('flutterProject'),
|
||||
ipv6: false,
|
||||
),
|
||||
)..called(1);
|
||||
@ -225,6 +227,7 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
packagesFilePath: anyNamed('packagesFilePath'),
|
||||
usesTerminalUI: anyNamed('usesTerminalUI'),
|
||||
flutterProject: anyNamed('flutterProject'),
|
||||
ipv6: false,
|
||||
)).thenReturn(mockHotRunner);
|
||||
|
||||
@ -254,6 +257,7 @@ void main() {
|
||||
debuggingOptions: anyNamed('debuggingOptions'),
|
||||
packagesFilePath: anyNamed('packagesFilePath'),
|
||||
usesTerminalUI: anyNamed('usesTerminalUI'),
|
||||
flutterProject: anyNamed('flutterProject'),
|
||||
ipv6: false,
|
||||
)).called(1);
|
||||
}, overrides: <Type, Generator>{
|
||||
|
@ -1,99 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter_tools/src/dart/dependencies.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
|
||||
import 'src/common.dart';
|
||||
import 'src/context.dart';
|
||||
|
||||
void main() {
|
||||
group('DartDependencySetBuilder', () {
|
||||
final String dataPath = fs.path.join(
|
||||
getFlutterRoot(),
|
||||
'packages',
|
||||
'flutter_tools',
|
||||
'test',
|
||||
'data',
|
||||
'dart_dependencies_test',
|
||||
);
|
||||
|
||||
testUsingContext('good', () {
|
||||
final String testPath = fs.path.join(dataPath, 'good');
|
||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||
final DartDependencySetBuilder builder =
|
||||
DartDependencySetBuilder(mainPath, packagesPath);
|
||||
final Set<String> dependencies = builder.build();
|
||||
expect(dependencies.contains(canonicalizePath(mainPath)), isTrue);
|
||||
expect(dependencies.contains(canonicalizePath(fs.path.join(testPath, 'foo.dart'))), isTrue);
|
||||
});
|
||||
|
||||
testUsingContext('syntax_error', () {
|
||||
final String testPath = fs.path.join(dataPath, 'syntax_error');
|
||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||
final DartDependencySetBuilder builder =
|
||||
DartDependencySetBuilder(mainPath, packagesPath);
|
||||
try {
|
||||
builder.build();
|
||||
fail('expect an exception to be thrown.');
|
||||
} on DartDependencyException catch (error) {
|
||||
expect(error.toString(), contains('foo.dart: Expected a string literal'));
|
||||
}
|
||||
});
|
||||
|
||||
testUsingContext('bad_path', () {
|
||||
final String testPath = fs.path.join(dataPath, 'bad_path');
|
||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||
final DartDependencySetBuilder builder =
|
||||
DartDependencySetBuilder(mainPath, packagesPath);
|
||||
try {
|
||||
builder.build();
|
||||
fail('expect an exception to be thrown.');
|
||||
} on DartDependencyException catch (error) {
|
||||
expect(error.toString(), contains('amaze${fs.path.separator}and${fs.path.separator}astonish.dart'));
|
||||
}
|
||||
});
|
||||
|
||||
testUsingContext('bad_package', () {
|
||||
final String testPath = fs.path.join(dataPath, 'bad_package');
|
||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||
final DartDependencySetBuilder builder =
|
||||
DartDependencySetBuilder(mainPath, packagesPath);
|
||||
try {
|
||||
builder.build();
|
||||
fail('expect an exception to be thrown.');
|
||||
} on DartDependencyException catch (error) {
|
||||
expect(error.toString(), contains('rochambeau'));
|
||||
expect(error.toString(), contains('pubspec.yaml'));
|
||||
}
|
||||
});
|
||||
|
||||
testUsingContext('does not change ASCII casing of path', () {
|
||||
final String testPath = fs.path.join(dataPath, 'asci_casing');
|
||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||
final DartDependencySetBuilder builder = DartDependencySetBuilder(mainPath, packagesPath);
|
||||
final Set<String> deps = builder.build();
|
||||
expect(deps, contains(endsWith('This_Import_Has_fuNNy_casING.dart')));
|
||||
});
|
||||
|
||||
testUsingContext('bad_import', () {
|
||||
final String testPath = fs.path.join(dataPath, 'bad_import');
|
||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||
final DartDependencySetBuilder builder =
|
||||
DartDependencySetBuilder(mainPath, packagesPath);
|
||||
try {
|
||||
builder.build();
|
||||
fail('expect an exception to be thrown.');
|
||||
} on DartDependencyException catch (error) {
|
||||
expect(error.toString(), contains('Unable to parse URI'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -1 +0,0 @@
|
||||
self:lib/
|
@ -1,5 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
String dummy = 'Hello';
|
@ -1,3 +0,0 @@
|
||||
analyzer:
|
||||
exclude:
|
||||
- '**'
|
@ -1,9 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'This_Import_Has_fuNNy_casING.dart';
|
||||
|
||||
void main() {
|
||||
print(dummy);
|
||||
}
|
@ -1 +0,0 @@
|
||||
name: self
|
@ -1,3 +0,0 @@
|
||||
analyzer:
|
||||
exclude:
|
||||
- '**'
|
@ -1,5 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'data://object.dart';
|
@ -1 +0,0 @@
|
||||
name: self
|
@ -1 +0,0 @@
|
||||
self:lib/
|
@ -1,3 +0,0 @@
|
||||
analyzer:
|
||||
exclude:
|
||||
- '**'
|
@ -1,6 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:rochambeau/you_have_your_orders_now_go_man_go.dart';
|
||||
|
@ -1 +0,0 @@
|
||||
name: self
|
@ -1 +0,0 @@
|
||||
self:lib/
|
@ -1,3 +0,0 @@
|
||||
analyzer:
|
||||
exclude:
|
||||
- '**'
|
@ -1,5 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'amaze/and/astonish.dart';
|
@ -1 +0,0 @@
|
||||
name: self
|
@ -1,2 +0,0 @@
|
||||
flutter:file:///a/wild/non-existent/directory/has/appeared
|
||||
sdk-move-test:lib/
|
@ -1,5 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// No content
|
@ -1,9 +0,0 @@
|
||||
name: sdk-move-test
|
||||
|
||||
environment:
|
||||
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
@ -1 +0,0 @@
|
||||
self:lib/
|
@ -1,3 +0,0 @@
|
||||
analyzer:
|
||||
exclude:
|
||||
- '**'
|
@ -1,3 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
@ -1,3 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
@ -1,6 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'foo.dart';
|
||||
import 'package:self/bar.dart';
|
@ -1 +0,0 @@
|
||||
name: self
|
@ -1 +0,0 @@
|
||||
self:lib/
|
@ -1,3 +0,0 @@
|
||||
analyzer:
|
||||
exclude:
|
||||
- '**'
|
@ -1,5 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import bad programmer!
|
@ -1,5 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'foo.dart';
|
@ -1 +0,0 @@
|
||||
name: self
|
@ -1,112 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/devices.dart';
|
||||
import 'package:flutter_tools/src/dart/dependencies.dart';
|
||||
import 'package:flutter_tools/src/dependency_checker.dart';
|
||||
|
||||
import 'src/common.dart';
|
||||
import 'src/context.dart';
|
||||
|
||||
void main() {
|
||||
group('DependencyChecker', () {
|
||||
final String dataPath = fs.path.join(
|
||||
getFlutterRoot(),
|
||||
'packages',
|
||||
'flutter_tools',
|
||||
'test',
|
||||
'data',
|
||||
'dart_dependencies_test',
|
||||
);
|
||||
|
||||
FileSystem testFileSystem;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
testFileSystem = MemoryFileSystem();
|
||||
});
|
||||
|
||||
testUsingContext('good', () {
|
||||
final String testPath = fs.path.join(dataPath, 'good');
|
||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||
final String fooPath = fs.path.join(testPath, 'foo.dart');
|
||||
final String barPath = fs.path.join(testPath, 'lib', 'bar.dart');
|
||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||
final DartDependencySetBuilder builder =
|
||||
DartDependencySetBuilder(mainPath, packagesPath);
|
||||
final DependencyChecker dependencyChecker =
|
||||
DependencyChecker(builder, null);
|
||||
|
||||
// Set file modification time on all dependencies to be in the past.
|
||||
final DateTime baseTime = DateTime.now();
|
||||
updateFileModificationTime(packagesPath, baseTime, -10);
|
||||
updateFileModificationTime(mainPath, baseTime, -10);
|
||||
updateFileModificationTime(fooPath, baseTime, -10);
|
||||
updateFileModificationTime(barPath, baseTime, -10);
|
||||
expect(dependencyChecker.check(baseTime), isFalse);
|
||||
|
||||
// Set .packages file modification time to be in the future.
|
||||
updateFileModificationTime(packagesPath, baseTime, 20);
|
||||
expect(dependencyChecker.check(baseTime), isTrue);
|
||||
|
||||
// Reset .packages file modification time.
|
||||
updateFileModificationTime(packagesPath, baseTime, 0);
|
||||
expect(dependencyChecker.check(baseTime), isFalse);
|
||||
|
||||
// Set 'package:self/bar.dart' file modification time to be in the future.
|
||||
updateFileModificationTime(barPath, baseTime, 10);
|
||||
expect(dependencyChecker.check(baseTime), isTrue);
|
||||
});
|
||||
|
||||
testUsingContext('syntax error', () {
|
||||
final String testPath = fs.path.join(dataPath, 'syntax_error');
|
||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||
final String fooPath = fs.path.join(testPath, 'foo.dart');
|
||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||
|
||||
final DartDependencySetBuilder builder =
|
||||
DartDependencySetBuilder(mainPath, packagesPath);
|
||||
final DependencyChecker dependencyChecker =
|
||||
DependencyChecker(builder, null);
|
||||
|
||||
final DateTime baseTime = DateTime.now();
|
||||
|
||||
// Set file modification time on all dependencies to be in the past.
|
||||
updateFileModificationTime(packagesPath, baseTime, -10);
|
||||
updateFileModificationTime(mainPath, baseTime, -10);
|
||||
updateFileModificationTime(fooPath, baseTime, -10);
|
||||
|
||||
// Dependencies are considered dirty because there is a syntax error in
|
||||
// the .dart file.
|
||||
expect(dependencyChecker.check(baseTime), isTrue);
|
||||
});
|
||||
|
||||
/// Test a flutter tool move.
|
||||
///
|
||||
/// Tests that the flutter tool doesn't crash and displays a warning when its own location
|
||||
/// changed since it was last referenced to in a package's .packages file.
|
||||
testUsingContext('moved flutter sdk', () async {
|
||||
final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_dependency_checker_test.');
|
||||
|
||||
// Copy the golden input and let the test run in an isolated temporary in-memory file system.
|
||||
const LocalFileSystem localFileSystem = LocalFileSystem();
|
||||
final Directory sourcePath = localFileSystem.directory(localFileSystem.path.join(dataPath, 'changed_sdk_location'));
|
||||
copyDirectorySync(sourcePath, tempDir);
|
||||
fs.currentDirectory = tempDir;
|
||||
|
||||
// Doesn't matter what commands we run. Arbitrarily list devices here.
|
||||
await createTestCommandRunner(DevicesCommand()).run(<String>['devices']);
|
||||
expect(testLogger.errorText, contains('.packages'));
|
||||
tryToDelete(tempDir);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => testFileSystem,
|
||||
});
|
||||
});
|
||||
}
|
@ -7,10 +7,8 @@ import 'dart:convert';
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/asset.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:flutter_tools/src/vmservice.dart';
|
||||
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
|
||||
@ -22,16 +20,13 @@ import 'src/mocks.dart';
|
||||
void main() {
|
||||
FileSystem fs;
|
||||
String filePath;
|
||||
String filePath2;
|
||||
Directory tempDir;
|
||||
String basePath;
|
||||
DevFS devFS;
|
||||
final AssetBundle assetBundle = AssetBundleFactory.defaultInstance.createBundle();
|
||||
|
||||
setUpAll(() {
|
||||
fs = MemoryFileSystem();
|
||||
filePath = fs.path.join('lib', 'foo.txt');
|
||||
filePath2 = fs.path.join('foo', 'bar.txt');
|
||||
});
|
||||
|
||||
group('DevFSContent', () {
|
||||
@ -94,362 +89,6 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('devfs local', () {
|
||||
final MockDevFSOperations devFSOperations = MockDevFSOperations();
|
||||
final MockResidentCompiler residentCompiler = MockResidentCompiler();
|
||||
|
||||
setUpAll(() {
|
||||
tempDir = _newTempDir(fs);
|
||||
basePath = tempDir.path;
|
||||
});
|
||||
tearDownAll(_cleanupTempDirs);
|
||||
|
||||
testUsingContext('create dev file system', () async {
|
||||
// simulate workspace
|
||||
final File file = fs.file(fs.path.join(basePath, filePath));
|
||||
await file.parent.create(recursive: true);
|
||||
file.writeAsBytesSync(<int>[1, 2, 3]);
|
||||
_packages['my_project'] = fs.path.toUri('lib');
|
||||
|
||||
// simulate package
|
||||
await _createPackage(fs, 'somepkg', 'somefile.txt');
|
||||
|
||||
devFS = DevFS.operations(devFSOperations, 'test', tempDir);
|
||||
await devFS.create();
|
||||
devFSOperations.expectMessages(<String>['create test']);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
|
||||
UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
|
||||
report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: true,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill.track.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('add new file to local file system', () async {
|
||||
final File file = fs.file(fs.path.join(basePath, filePath2));
|
||||
await file.parent.create(recursive: true);
|
||||
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]);
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('modify existing file on local file system', () async {
|
||||
UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
|
||||
final File file = fs.file(fs.path.join(basePath, filePath));
|
||||
// Set the last modified time to 5 seconds in the past.
|
||||
updateFileModificationTime(file.path, DateTime.now(), -5);
|
||||
report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
|
||||
await file.writeAsBytes(<int>[1, 2, 3, 4, 5, 6]);
|
||||
report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
|
||||
// Set the last modified time to 5 seconds in the past.
|
||||
updateFileModificationTime(file.path, DateTime.now(), -5);
|
||||
report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: true,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill.track.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
|
||||
await file.writeAsBytes(<int>[1, 2, 3, 4, 5, 6]);
|
||||
report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: true,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill.track.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('delete a file from the local file system', () async {
|
||||
final File file = fs.file(fs.path.join(basePath, filePath));
|
||||
await file.delete();
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'deleteFile test lib/foo.txt',
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('add new package', () async {
|
||||
await _createPackage(fs, 'newpkg', 'anotherfile.txt');
|
||||
UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
|
||||
report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: true,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill.track.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('add new package with double slashes in URI', () async {
|
||||
const String packageName = 'doubleslashpkg';
|
||||
await _createPackage(fs, packageName, 'somefile.txt', doubleSlash: true);
|
||||
|
||||
final Set<String> fileFilter = <String>{};
|
||||
final List<Uri> pkgUris = <Uri>[fs.path.toUri(basePath)]..addAll(_packages.values);
|
||||
for (Uri pkgUri in pkgUris) {
|
||||
if (!pkgUri.isAbsolute) {
|
||||
pkgUri = fs.path.toUri(fs.path.join(basePath, pkgUri.path));
|
||||
}
|
||||
fileFilter.addAll(fs.directory(pkgUri)
|
||||
.listSync(recursive: true)
|
||||
.whereType<File>()
|
||||
.map<String>((File file) => canonicalizePath(file.path))
|
||||
.toList());
|
||||
}
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
fileFilter: fileFilter,
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('add an asset bundle', () async {
|
||||
assetBundle.entries['a.txt'] = DevFSStringContent('abc');
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
bundle: assetBundle,
|
||||
bundleDirty: true,
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test ${_inAssetBuildDirectory(fs, 'a.txt')}',
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, unorderedMatches(<String>['a.txt']));
|
||||
devFS.assetPathsToEvict.clear();
|
||||
expect(report.syncedBytes, 25);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('add a file to the asset bundle - bundleDirty', () async {
|
||||
assetBundle.entries['b.txt'] = DevFSStringContent('abcd');
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
bundle: assetBundle,
|
||||
bundleDirty: true,
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
// Expect entire asset bundle written because bundleDirty is true
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test ${_inAssetBuildDirectory(fs, 'a.txt')}',
|
||||
'writeFile test ${_inAssetBuildDirectory(fs, 'b.txt')}',
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
|
||||
'a.txt', 'b.txt']));
|
||||
devFS.assetPathsToEvict.clear();
|
||||
expect(report.syncedBytes, 29);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('add a file to the asset bundle', () async {
|
||||
assetBundle.entries['c.txt'] = DevFSStringContent('12');
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
bundle: assetBundle,
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'writeFile test ${_inAssetBuildDirectory(fs, 'c.txt')}',
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
|
||||
'c.txt']));
|
||||
devFS.assetPathsToEvict.clear();
|
||||
expect(report.syncedBytes, 24);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('delete a file from the asset bundle', () async {
|
||||
assetBundle.entries.remove('c.txt');
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
bundle: assetBundle,
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'deleteFile test ${_inAssetBuildDirectory(fs, 'c.txt')}',
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, unorderedMatches(<String>['c.txt']));
|
||||
devFS.assetPathsToEvict.clear();
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('delete all files from the asset bundle', () async {
|
||||
assetBundle.entries.clear();
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainPath: 'lib/foo.txt',
|
||||
bundle: assetBundle,
|
||||
bundleDirty: true,
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
);
|
||||
devFSOperations.expectMessages(<String>[
|
||||
'deleteFile test ${_inAssetBuildDirectory(fs, 'a.txt')}',
|
||||
'deleteFile test ${_inAssetBuildDirectory(fs, 'b.txt')}',
|
||||
'writeFile test lib/foo.txt.dill build/app.dill',
|
||||
]);
|
||||
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
|
||||
'a.txt', 'b.txt',
|
||||
]));
|
||||
devFS.assetPathsToEvict.clear();
|
||||
expect(report.syncedBytes, 22);
|
||||
expect(report.success, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('delete dev file system', () async {
|
||||
await devFS.destroy();
|
||||
devFSOperations.expectMessages(<String>['destroy test']);
|
||||
expect(devFS.assetPathsToEvict, isEmpty);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
});
|
||||
|
||||
group('devfs remote', () {
|
||||
MockVMService vmService;
|
||||
final MockResidentCompiler residentCompiler = MockResidentCompiler();
|
||||
@ -484,6 +123,7 @@ void main() {
|
||||
generator: residentCompiler,
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
invalidatedFiles: <String>[],
|
||||
);
|
||||
vmService.expectMessages(<String>[
|
||||
'writeFile test lib/foo.txt.dill',
|
||||
@ -652,6 +292,3 @@ Future<void> _createPackage(FileSystem fs, String pkgName, String pkgFileName, {
|
||||
fs.file(fs.path.join(_tempDirs[0].path, '.packages')).writeAsStringSync(sb.toString());
|
||||
}
|
||||
|
||||
String _inAssetBuildDirectory(FileSystem fs, String filename) {
|
||||
return '${fs.path.toUri(getAssetBuildDirectory()).path}/$filename';
|
||||
}
|
||||
|
@ -105,13 +105,13 @@ void main() {
|
||||
firstBuildTime: anyNamed('firstBuildTime'),
|
||||
bundleFirstUpload: anyNamed('bundleFirstUpload'),
|
||||
bundleDirty: anyNamed('bundleDirty'),
|
||||
fileFilter: anyNamed('fileFilter'),
|
||||
generator: anyNamed('generator'),
|
||||
fullRestart: anyNamed('fullRestart'),
|
||||
dillOutputPath: anyNamed('dillOutputPath'),
|
||||
trackWidgetCreation: anyNamed('trackWidgetCreation'),
|
||||
projectRootPath: anyNamed('projectRootPath'),
|
||||
pathToReload: anyNamed('pathToReload'),
|
||||
invalidatedFiles: anyNamed('invalidatedFiles'),
|
||||
)).thenAnswer((Invocation _) => Future<UpdateFSReport>.value(
|
||||
UpdateFSReport(success: true, syncedBytes: 1000, invalidatedSourcesCount: 1)));
|
||||
when(mockDevFs.assetPathsToEvict).thenReturn(<String>{});
|
||||
@ -122,18 +122,6 @@ void main() {
|
||||
when(mockArtifacts.getArtifactPath(Artifact.flutterPatchedSdkPath)).thenReturn('some/path');
|
||||
});
|
||||
|
||||
testUsingContext('no setup', () async {
|
||||
final MockDevice mockDevice = MockDevice();
|
||||
when(mockDevice.supportsHotReload).thenReturn(true);
|
||||
when(mockDevice.supportsHotRestart).thenReturn(true);
|
||||
final List<FlutterDevice> devices = <FlutterDevice>[
|
||||
FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false),
|
||||
];
|
||||
expect((await HotRunner(devices).restart(fullRestart: true)).isOk, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => mockArtifacts,
|
||||
});
|
||||
|
||||
testUsingContext('Does not hot restart when device does not support it', () async {
|
||||
// Setup mocks
|
||||
final MockDevice mockDevice = MockDevice();
|
||||
@ -149,7 +137,7 @@ void main() {
|
||||
expect(result.message, 'hotRestart not supported');
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => mockArtifacts,
|
||||
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true, computeDartDependencies: false),
|
||||
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
|
||||
});
|
||||
|
||||
testUsingContext('Does not hot restart when one of many devices does not support it', () async {
|
||||
@ -171,7 +159,7 @@ void main() {
|
||||
expect(result.message, 'hotRestart not supported');
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => mockArtifacts,
|
||||
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true, computeDartDependencies: false),
|
||||
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
|
||||
});
|
||||
|
||||
testUsingContext('Does hot restarts when all devices support it', () async {
|
||||
@ -193,7 +181,7 @@ void main() {
|
||||
expect(result.message, isNot('hotRestart not supported'));
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => mockArtifacts,
|
||||
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true, computeDartDependencies: false),
|
||||
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
|
||||
});
|
||||
|
||||
testUsingContext('setup function fails', () async {
|
||||
@ -226,7 +214,7 @@ void main() {
|
||||
expect(result.message, isNot('setupHotRestart failed'));
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => mockArtifacts,
|
||||
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true, computeDartDependencies: false),
|
||||
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
|
||||
});
|
||||
|
||||
group('shutdown hook tests', () {
|
||||
@ -235,7 +223,6 @@ void main() {
|
||||
setUp(() {
|
||||
shutdownTestingConfig = TestHotRunnerConfig(
|
||||
successfulSetup: true,
|
||||
computeDartDependencies: false,
|
||||
);
|
||||
});
|
||||
|
||||
@ -283,10 +270,7 @@ class MockDevice extends Mock implements Device {
|
||||
}
|
||||
|
||||
class TestHotRunnerConfig extends HotRunnerConfig {
|
||||
TestHotRunnerConfig({@required this.successfulSetup, bool computeDartDependencies = true}) {
|
||||
this.computeDartDependencies = computeDartDependencies;
|
||||
}
|
||||
|
||||
TestHotRunnerConfig({@required this.successfulSetup});
|
||||
bool successfulSetup;
|
||||
bool shutdownHookCalled = false;
|
||||
|
||||
|
234
packages/flutter_tools/test/project_file_invalidator_test.dart
Normal file
234
packages/flutter_tools/test/project_file_invalidator_test.dart
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/run_hot.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'src/common.dart';
|
||||
import 'src/context.dart';
|
||||
|
||||
void main() {
|
||||
final Platform windowsPlatform = MockPlatform();
|
||||
final Platform notWindowsPlatform = MockPlatform();
|
||||
final BufferLogger bufferLogger = BufferLogger();
|
||||
when(windowsPlatform.isWindows).thenReturn(true);
|
||||
when(notWindowsPlatform.isWindows).thenReturn(false);
|
||||
|
||||
group('ProjectFileInvalidator linux/mac', () {
|
||||
final MemoryFileSystem memoryFileSystem = MemoryFileSystem();
|
||||
final File packagesFile = memoryFileSystem.file('.packages')
|
||||
..createSync()
|
||||
..writeAsStringSync(r'''
|
||||
foo:file:///foo/lib/
|
||||
bar:file:///.pub-cache/bar/lib/
|
||||
baz:file:///baz/lib/
|
||||
test_package:file:///lib/
|
||||
''');
|
||||
final File pubspecFile = memoryFileSystem.file('pubspec.yaml')
|
||||
..createSync()
|
||||
..writeAsStringSync(r'''
|
||||
name: test_package
|
||||
|
||||
dependencies:
|
||||
foo: any
|
||||
bar:
|
||||
|
||||
dev_dependencies:
|
||||
baz: any
|
||||
''');
|
||||
final File mainFile = memoryFileSystem.file('lib/main.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(r'''
|
||||
void main() {}
|
||||
''');
|
||||
final File fooFile = memoryFileSystem.file('foo/lib/foo.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('');
|
||||
memoryFileSystem.file('bar/lib/bar.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('');
|
||||
final File bazFile = memoryFileSystem.file('baz/lib/baz.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('');
|
||||
|
||||
testUsingContext('No .packages, no pubspec', () async {
|
||||
// Instead of setting up multiple filesystems, passing a .packages file which does not exist.
|
||||
final ProjectFileInvalidator invalidator = ProjectFileInvalidator('.packages-wrong', null);
|
||||
invalidator.findInvalidated();
|
||||
expect(invalidator.packageMap, isEmpty);
|
||||
expect(invalidator.updateTime, isEmpty);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => memoryFileSystem,
|
||||
Platform: () => notWindowsPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('.packages only', () async {
|
||||
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, null);
|
||||
invalidator.findInvalidated();
|
||||
expect(invalidator.packageMap, <String, Uri>{
|
||||
'foo': Uri.parse('file:///foo/lib/'),
|
||||
// Excluded because it is in pub cache.
|
||||
// 'bar': Uri.parse('file:///.pub-cache/bar/lib/'),
|
||||
'baz': Uri.parse('file:///baz/lib/'),
|
||||
'test_package': Uri.parse('file:///lib/'),
|
||||
});
|
||||
expect(invalidator.updateTime, <String, int>{
|
||||
'/baz/lib/baz.dart': bazFile.statSync().modified.millisecondsSinceEpoch,
|
||||
'/lib/main.dart': mainFile.statSync().modified.millisecondsSinceEpoch,
|
||||
'/foo/lib/foo.dart': fooFile.statSync().modified.millisecondsSinceEpoch,
|
||||
});
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => memoryFileSystem,
|
||||
Platform: () => notWindowsPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('.packages and pubspec', () async {
|
||||
final FlutterProject flutterProject = await FlutterProject.fromDirectory(pubspecFile.parent);
|
||||
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, flutterProject);
|
||||
invalidator.findInvalidated();
|
||||
expect(invalidator.packageMap, <String, Uri>{
|
||||
'foo': Uri.parse('file:///foo/lib/'),
|
||||
// Excluded because it is in pub cache.
|
||||
// 'bar': Uri.parse('file:///.pub-cache/bar/lib/'),
|
||||
// Excluded because it is a dev dependency/
|
||||
// 'baz': Uri.parse('file:///baz/lib/'),
|
||||
'test_package': Uri.parse('file:///lib/'),
|
||||
});
|
||||
expect(invalidator.updateTime, <String, int>{
|
||||
'/foo/lib/foo.dart': fooFile.statSync().modified.millisecondsSinceEpoch,
|
||||
'/lib/main.dart':mainFile.statSync().modified.millisecondsSinceEpoch,
|
||||
});
|
||||
expect(invalidator.findInvalidated(), isEmpty);
|
||||
|
||||
// Invalidate main.dart.
|
||||
mainFile.writeAsStringSync('void main() { }');
|
||||
expect(invalidator.findInvalidated(), <String>['/lib/main.dart']);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => memoryFileSystem,
|
||||
Platform: () => notWindowsPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('update to .packages triggers warning', () async {
|
||||
final FlutterProject flutterProject = await FlutterProject.fromDirectory(pubspecFile.parent);
|
||||
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, flutterProject);
|
||||
invalidator.findInvalidated();
|
||||
packagesFile.writeAsStringSync(r'''
|
||||
foo:file:///foo/lib/
|
||||
bar:file:///.pub-cache/bar/lib/
|
||||
baz:file:///baz/lib/
|
||||
new_dep:file:///new_dep/lib/
|
||||
test_package:file:///lib/
|
||||
''');
|
||||
invalidator.findInvalidated();
|
||||
expect(bufferLogger.errorText, 'Warning: updated dependencies detected. The Flutter application will require a restart to safely use new packages.\n');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => memoryFileSystem,
|
||||
Platform: () => notWindowsPlatform,
|
||||
Logger: () => bufferLogger,
|
||||
});
|
||||
});
|
||||
|
||||
group('ProjectFileInvalidator windows', () {
|
||||
final MemoryFileSystem memoryFileSystem = MemoryFileSystem(style: FileSystemStyle.windows);
|
||||
// On windows .packages still contains file Uris, albeit ones with the Drive prefix.
|
||||
final File packagesFile = memoryFileSystem.file(r'C:\.packages')
|
||||
..createSync()
|
||||
..writeAsStringSync(r'''
|
||||
foo:file:///C:/foo/lib/
|
||||
bar:file:///C:/Pub/Cache/bar/lib/
|
||||
baz:file:///C:/baz/lib/
|
||||
test_package:file:///C:/lib/
|
||||
''');
|
||||
memoryFileSystem.file(r'C:\pubspec.yaml')
|
||||
..createSync()
|
||||
..writeAsStringSync(r'''
|
||||
name: test_package
|
||||
|
||||
dependencies:
|
||||
foo: any
|
||||
bar:
|
||||
|
||||
dev_dependencies:
|
||||
baz: any
|
||||
''');
|
||||
final File mainFile = memoryFileSystem.file(r'C:\lib\main.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(r'''
|
||||
void main() {}
|
||||
''');
|
||||
final File fooFile = memoryFileSystem.file(r'C:\foo\lib\foo.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('');
|
||||
memoryFileSystem.file(r'C:\bar\lib\bar.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('');
|
||||
final File bazFile = memoryFileSystem.file(r'C:\baz\lib\baz.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('');
|
||||
|
||||
testUsingContext('No .packages, no pubspec', () async {
|
||||
// Instead of setting up multiple filesystems, passing a .packages file which does not exist.
|
||||
final ProjectFileInvalidator invalidator = ProjectFileInvalidator('.packages-wrong', null);
|
||||
invalidator.findInvalidated();
|
||||
expect(invalidator.packageMap, isEmpty);
|
||||
expect(invalidator.updateTime, isEmpty);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => memoryFileSystem,
|
||||
Platform: () => windowsPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('.packages only', () async {
|
||||
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, null);
|
||||
invalidator.findInvalidated();
|
||||
expect(invalidator.packageMap, <String, Uri>{
|
||||
'foo': Uri.file(r'C:\foo\lib\', windows: true),
|
||||
// Excluded because it is in pub cache.
|
||||
// 'bar': Uri.parse('file:///Pub/Cache/bar/lib/'),
|
||||
'baz': Uri.file(r'C:\baz\lib\', windows: true),
|
||||
'test_package': Uri.file(r'C:\lib\', windows: true),
|
||||
});
|
||||
expect(invalidator.updateTime, <String, int>{
|
||||
r'C:\baz\lib\baz.dart': bazFile.statSync().modified.millisecondsSinceEpoch,
|
||||
r'C:\lib\main.dart': mainFile.statSync().modified.millisecondsSinceEpoch,
|
||||
r'C:\foo\lib\foo.dart': fooFile.statSync().modified.millisecondsSinceEpoch,
|
||||
});
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => memoryFileSystem,
|
||||
Platform: () => windowsPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('.packages and pubspec', () async {
|
||||
final FlutterProject flutterProject = await FlutterProject.fromDirectory(fs.directory(r'C:\'));
|
||||
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, flutterProject);
|
||||
invalidator.findInvalidated();
|
||||
expect(invalidator.packageMap, <String, Uri>{
|
||||
'foo': Uri.file(r'C:\foo\lib\', windows: true),
|
||||
// Excluded because it is in pub cache.
|
||||
// 'bar': Uri.parse('file:///C:/Pub/Cache/bar/lib/'),
|
||||
// Excluded because it is a dev dependency/
|
||||
// 'baz': Uri.parse('file:///baz/lib/'),
|
||||
'test_package': Uri.file(r'C:\lib\', windows: true),
|
||||
});
|
||||
expect(invalidator.updateTime, <String, int>{
|
||||
r'C:\lib\main.dart': mainFile.statSync().modified.millisecondsSinceEpoch,
|
||||
r'C:\foo\lib\foo.dart': fooFile.statSync().modified.millisecondsSinceEpoch,
|
||||
});
|
||||
expect(invalidator.findInvalidated(), isEmpty);
|
||||
|
||||
// Invalidate main.dart.
|
||||
mainFile.writeAsStringSync('void main() { }');
|
||||
expect(invalidator.findInvalidated(), <String>['file:///C:/lib/main.dart']);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => memoryFileSystem,
|
||||
Platform: () => windowsPlatform,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockPlatform extends Mock implements Platform {}
|
@ -460,12 +460,6 @@ class MockDevFSOperations extends BasicMock implements DevFSOperations {
|
||||
messages.add(message);
|
||||
devicePathToContent[deviceUri] = content;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> deleteFile(String fsName, Uri deviceUri) async {
|
||||
messages.add('deleteFile $fsName $deviceUri');
|
||||
devicePathToContent.remove(deviceUri);
|
||||
}
|
||||
}
|
||||
|
||||
class MockResidentCompiler extends BasicMock implements ResidentCompiler {
|
||||
|
Loading…
x
Reference in New Issue
Block a user