Unverified Commit 12c4e050 authored by Alexander Aprelev's avatar Alexander Aprelev Committed by GitHub

Use source list from the compiler to track invalidated files for hot reload. (#29693)

* Use source list from the compiler to track invalidated files.

* Revert accidental change

* Fix first-time-seen-the-file logic

* Fix/simplify invalidate logic now that we can rely on compiler to let us know what is the cut-off point for invalidation.

* Update devfs mock to accommodate for new fields

* Fix deleted files case

* Analyzer found missing final
parent 553a0478
......@@ -352,7 +352,6 @@ class AndroidDevice extends Device {
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) async {
......
......@@ -214,7 +214,7 @@ class CodeGeneratingResidentCompiler implements ResidentCompiler {
}
@override
Future<CompilerOutput> recompile(String mainPath, List<String> invalidatedFiles, {String outputPath, String packagesFilePath}) async {
Future<CompilerOutput> recompile(String mainPath, List<Uri> invalidatedFiles, {String outputPath, String packagesFilePath}) async {
if (_codegenDaemon.lastStatus != CodegenStatus.Succeeded && _codegenDaemon.lastStatus != CodegenStatus.Failed) {
await _codegenDaemon.buildResults.firstWhere((CodegenStatus status) {
return status == CodegenStatus.Succeeded || status == CodegenStatus.Failed;
......
......@@ -306,7 +306,6 @@ class HotRunnerFactory {
dillOutputPath: dillOutputPath,
stayResident: stayResident,
ipv6: ipv6,
flutterProject: flutterProject,
);
}
......
......@@ -19,7 +19,6 @@ 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';
......@@ -342,8 +341,6 @@ 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;
fs.currentDirectory = fs.directory(projectDirectory);
......@@ -370,7 +367,6 @@ class AppDomain extends Domain {
dillOutputPath: dillOutputPath,
ipv6: ipv6,
hostIsIde: true,
flutterProject: flutterProject,
);
} else {
runner = ColdRunner(
......
......@@ -13,7 +13,6 @@ 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';
......@@ -281,7 +280,6 @@ 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']);
......@@ -391,7 +389,6 @@ class RunCommand extends RunCommandBase {
saveCompilationTrace: argResults['train'],
stayResident: stayResident,
ipv6: ipv6,
flutterProject: flutterProject,
);
} else {
runner = ColdRunner(
......
......@@ -354,7 +354,7 @@ class _RecompileRequest extends _CompilationRequest {
) : super(completer);
String mainPath;
List<String> invalidatedFiles;
List<Uri> invalidatedFiles;
String outputPath;
String packagesFilePath;
......@@ -450,7 +450,7 @@ class ResidentCompiler {
/// null is returned.
Future<CompilerOutput> recompile(
String mainPath,
List<String> invalidatedFiles, {
List<Uri> invalidatedFiles, {
@required String outputPath,
String packagesFilePath,
}) async {
......@@ -497,9 +497,9 @@ class ResidentCompiler {
: '';
_server.stdin.writeln('recompile $mainUri$inputKey');
printTrace('<- recompile $mainUri$inputKey');
for (String fileUri in request.invalidatedFiles) {
_server.stdin.writeln(_mapFileUri(fileUri, packageUriMapper));
printTrace('<- ${_mapFileUri(fileUri, packageUriMapper)}');
for (Uri fileUri in request.invalidatedFiles) {
_server.stdin.writeln(_mapFileUri(fileUri.toString(), packageUriMapper));
printTrace('<- ${_mapFileUri(fileUri.toString(), packageUriMapper)}');
}
_server.stdin.writeln(inputKey);
printTrace('<- $inputKey');
......
......@@ -387,6 +387,8 @@ class DevFS {
String _packagesFilePath;
final Map<Uri, DevFSContent> _entries = <Uri, DevFSContent>{};
final Set<String> assetPathsToEvict = <String>{};
List<Uri> sources = <Uri>[];
DateTime lastCompiled;
Uri _baseUri;
Uri get baseUri => _baseUri;
......@@ -439,7 +441,7 @@ class DevFS {
bool fullRestart = false,
String projectRootPath,
@required String pathToReload,
@required List<String> invalidatedFiles,
@required List<Uri> invalidatedFiles,
}) async {
assert(trackWidgetCreation != null);
assert(generator != null);
......@@ -478,6 +480,7 @@ class DevFS {
generator.reset();
}
printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
lastCompiled = DateTime.now();
final CompilerOutput compilerOutput = await generator.recompile(
mainPath,
invalidatedFiles,
......@@ -485,6 +488,7 @@ class DevFS {
packagesFilePath : _packagesFilePath,
);
// list of sources that needs to be monitored are in [compilerOutput.sources]
sources = compilerOutput.sources;
//
// Don't send full kernel file that would overwrite what VM already
// started loading from.
......
......@@ -286,7 +286,6 @@ abstract class Device {
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
});
......
......@@ -188,7 +188,6 @@ class FuchsiaDevice extends Device {
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) => Future<void>.error('unimplemented');
......
......@@ -235,7 +235,6 @@ class IOSDevice extends Device {
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) async {
......
......@@ -297,7 +297,6 @@ class IOSSimulator extends Device {
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) async {
......
......@@ -57,7 +57,6 @@ class LinuxDevice extends Device {
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) {
......
......@@ -63,7 +63,6 @@ class MacOSDevice extends Device {
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) async {
......
......@@ -345,7 +345,6 @@ class FlutterDevice {
startEchoingDeviceLog();
// Start the application.
final bool hasDirtyDependencies = hotRunner.hasDirtyDependencies(this);
final Future<LaunchResult> futureResult = device.startApp(
package,
mainPath: hotRunner.mainPath,
......@@ -353,7 +352,6 @@ class FlutterDevice {
platformArgs: platformArgs,
route: route,
prebuiltApplication: prebuiltMode,
applicationNeedsRebuild: shouldBuild || hasDirtyDependencies,
usesTerminalUi: hotRunner.usesTerminalUI,
ipv6: hotRunner.ipv6,
);
......@@ -409,7 +407,6 @@ class FlutterDevice {
startEchoingDeviceLog();
final bool hasDirtyDependencies = coldRunner.hasDirtyDependencies(this);
final LaunchResult result = await device.startApp(
package,
mainPath: coldRunner.mainPath,
......@@ -417,7 +414,6 @@ class FlutterDevice {
platformArgs: platformArgs,
route: route,
prebuiltApplication: prebuiltMode,
applicationNeedsRebuild: shouldBuild || hasDirtyDependencies,
usesTerminalUi: coldRunner.usesTerminalUI,
ipv6: coldRunner.ipv6,
);
......@@ -445,7 +441,7 @@ class FlutterDevice {
bool fullRestart = false,
String projectRootPath,
String pathToReload,
@required List<String> invalidatedFiles,
@required List<Uri> invalidatedFiles,
}) async {
final Status devFSStatus = logger.startProgress(
'Syncing files to device ${device.name}...',
......@@ -942,26 +938,6 @@ abstract class ResidentRunner {
return exitCode;
}
bool hasDirtyDependencies(FlutterDevice device) {
if (device.package.packagesFile == null || !device.package.packagesFile.existsSync()) {
return true;
}
// why is this sometimes an APK.
if (!device.package.packagesFile.path.contains('.packages')) {
return true;
}
// 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 { }
Future<void> stopApp() async {
......
......@@ -7,7 +7,6 @@ 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';
......@@ -19,11 +18,9 @@ import 'base/utils.dart';
import 'build_info.dart';
import 'compile.dart';
import 'convert.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';
......@@ -70,7 +67,6 @@ class HotRunner extends ResidentRunner {
bool saveCompilationTrace = false,
bool stayResident = true,
bool ipv6 = false,
FlutterProject flutterProject,
}) : super(devices,
target: target,
debuggingOptions: debuggingOptions,
......@@ -79,19 +75,13 @@ class HotRunner extends ResidentRunner {
packagesFilePath: packagesFilePath,
saveCompilationTrace: saveCompilationTrace,
stayResident: stayResident,
ipv6: ipv6) {
fileInvalidator = ProjectFileInvalidator(
packagesFilePath ?? fs.path.absolute(PackageMap.globalPackagesPath),
flutterProject,
);
}
ipv6: ipv6);
final bool benchmarkMode;
final File applicationBinary;
final bool hostIsIde;
bool _didAttach = false;
final String dillOutputPath;
ProjectFileInvalidator fileInvalidator;
final Map<String, List<int>> benchmarkData = <String, List<int>>{};
// The initial launch is from a snapshot.
......@@ -322,7 +312,12 @@ class HotRunner extends ResidentRunner {
if (result != 0)
return UpdateFSReport(success: false);
}
final List<String> invalidatedFiles = fileInvalidator.findInvalidated();
// Picking up first device's compiler as a source of truth - compilers
// for all devices should be in sync.
final List<Uri> invalidatedFiles = ProjectFileInvalidator.findInvalidated(
lastCompiled: flutterDevices[0].devFS.lastCompiled,
urisToMonitor: flutterDevices[0].devFS.sources);
final UpdateFSReport results = UpdateFSReport(success: true);
for (FlutterDevice device in flutterDevices) {
results.incorporateResults(await device.updateDevFS(
......@@ -941,104 +936,31 @@ class HotRunner extends ResidentRunner {
}
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.
static List<Uri> findInvalidated({@required DateTime lastCompiled,
@required List<Uri> urisToMonitor}) {
final List<Uri> invalidatedFiles = <Uri>[];
int scanned = 0;
final Stopwatch stopwatch = Stopwatch()..start();
for (Uri uri in urisToMonitor) {
if ((platform.isWindows && uri.path.contains(_pubCachePathWindows))
|| uri.path.contains(_pubCachePathLinuxAndWindows)) {
// Don't watch pub cache directories to speed things up a little.
continue;
}
}
// 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);
final DateTime updatedAt = fs.statSync(
uri.toFilePath(windows: platform.isWindows)).modified;
scanned++;
if (updatedAt == null) {
continue;
}
}
}
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.');
if (updatedAt.millisecondsSinceEpoch > lastCompiled.millisecondsSinceEpoch) {
invalidatedFiles.add(uri);
}
_packagesUpdateTime = newPackagesUpdateTime;
}
final List<String> invalidatedFiles = <String>[];
for (String packageName in _packageMap.keys) {
final Uri packageUri =_packageMap[packageName];
_scanDirectory(packageUri, invalidatedFiles);
}
printTrace('Scanned through $scanned files in ${stopwatch.elapsedMilliseconds}ms');
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;
}
}
}
}
}
\ No newline at end of file
......@@ -311,7 +311,7 @@ class _Compiler {
suppressOutput = false;
final CompilerOutput compilerOutput = await compiler.recompile(
request.path,
<String>[request.path],
<Uri>[Uri.parse(request.path)],
outputPath: outputDill.path,
);
final String outputPath = compilerOutput?.outputFilename;
......
......@@ -96,7 +96,6 @@ class FlutterTesterDevice extends Device {
@required DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) async {
......
......@@ -100,7 +100,6 @@ class WebDevice extends Device {
DebuggingOptions debuggingOptions,
Map<String, Object> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) async {
......
......@@ -57,7 +57,6 @@ class WindowsDevice extends Device {
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool applicationNeedsRebuild = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) {
......
......@@ -517,7 +517,7 @@ Future<void> _recompile(
});
final CompilerOutput output = await generator.recompile(
null /* mainPath */,
<String>['/path/to/main.dart'],
<Uri>[Uri.parse('/path/to/main.dart')],
outputPath: '/build/',
);
expect(output.outputFilename, equals('/path/to/main.dart.dill'));
......
......@@ -123,7 +123,7 @@ void main() {
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <String>[],
invalidatedFiles: <Uri>[],
);
vmService.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
......
......@@ -116,6 +116,8 @@ void main() {
UpdateFSReport(success: true, syncedBytes: 1000, invalidatedSourcesCount: 1)));
when(mockDevFs.assetPathsToEvict).thenReturn(<String>{});
when(mockDevFs.baseUri).thenReturn(Uri.file('test'));
when(mockDevFs.sources).thenReturn(<Uri>[Uri.file('test')]);
when(mockDevFs.lastCompiled).thenReturn(DateTime.now());
setUp(() {
mockArtifacts = MockLocalEngineArtifacts();
......
......@@ -4,231 +4,30 @@
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', () {
group('ProjectFileInvalidator', () {
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,
});
testUsingContext('Empty project', () async {
expect(
ProjectFileInvalidator.findInvalidated(lastCompiled: DateTime.now(), urisToMonitor: <Uri>[]),
isEmpty);
}, 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');
testUsingContext('Non-existent files are ignored', () async {
expect(
ProjectFileInvalidator.findInvalidated(
lastCompiled: DateTime.now(),
urisToMonitor: <Uri>[Uri.parse('/not-there-anymore')]),
isEmpty);
}, 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 {}
......@@ -487,7 +487,7 @@ class MockResidentCompiler extends BasicMock implements ResidentCompiler {
return null;
}
@override
Future<CompilerOutput> recompile(String mainPath, List<String> invalidatedFiles, { String outputPath, String packagesFilePath }) async {
Future<CompilerOutput> recompile(String mainPath, List<Uri> invalidatedFiles, { String outputPath, String packagesFilePath }) async {
fs.file(outputPath).createSync(recursive: true);
fs.file(outputPath).writeAsStringSync('compiled_kernel_output');
return CompilerOutput(outputPath, 0, <Uri>[]);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment