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
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index a0ba839..c0d1efd 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -352,7 +352,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) async {
diff --git a/packages/flutter_tools/lib/src/codegen.dart b/packages/flutter_tools/lib/src/codegen.dart
index 114918e..b843a5b 100644
--- a/packages/flutter_tools/lib/src/codegen.dart
+++ b/packages/flutter_tools/lib/src/codegen.dart
@@ -214,7 +214,7 @@
   }
 
   @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;
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index a485772..0031a4c 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -306,7 +306,6 @@
     dillOutputPath: dillOutputPath,
     stayResident: stayResident,
     ipv6: ipv6,
-    flutterProject: flutterProject,
   );
 }
 
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index e7b650a..e884b4b 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -19,7 +19,6 @@
 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 @@
     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 @@
         dillOutputPath: dillOutputPath,
         ipv6: ipv6,
         hostIsIde: true,
-        flutterProject: flutterProject,
       );
     } else {
       runner = ColdRunner(
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 4edaf21..507ea45 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -13,7 +13,6 @@
 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 @@
     // 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 @@
         saveCompilationTrace: argResults['train'],
         stayResident: stayResident,
         ipv6: ipv6,
-        flutterProject: flutterProject,
       );
     } else {
       runner = ColdRunner(
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 630e323..01ee9d4 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -354,7 +354,7 @@
   ) : super(completer);
 
   String mainPath;
-  List<String> invalidatedFiles;
+  List<Uri> invalidatedFiles;
   String outputPath;
   String packagesFilePath;
 
@@ -450,7 +450,7 @@
   /// null is returned.
   Future<CompilerOutput> recompile(
     String mainPath,
-    List<String> invalidatedFiles, {
+    List<Uri> invalidatedFiles, {
     @required String outputPath,
     String packagesFilePath,
   }) async {
@@ -497,9 +497,9 @@
         : '';
     _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');
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index ea63e73..b4c8293 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -387,6 +387,8 @@
   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 @@
     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 @@
       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 @@
       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.
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 608a20f..15ab883 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -286,7 +286,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   });
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index 9a13ea6..e5ed42e 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -188,7 +188,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) => Future<void>.error('unimplemented');
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 2959a2e..8903b5a 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -235,7 +235,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) async {
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index f521871..82f2ecf 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -297,7 +297,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) async {
diff --git a/packages/flutter_tools/lib/src/linux/linux_device.dart b/packages/flutter_tools/lib/src/linux/linux_device.dart
index 06c5301..fb88fbf 100644
--- a/packages/flutter_tools/lib/src/linux/linux_device.dart
+++ b/packages/flutter_tools/lib/src/linux/linux_device.dart
@@ -57,7 +57,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) {
diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart
index 8e82cb7..74c11f8 100644
--- a/packages/flutter_tools/lib/src/macos/macos_device.dart
+++ b/packages/flutter_tools/lib/src/macos/macos_device.dart
@@ -63,7 +63,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) async {
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 726562d..4cb6e43 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -345,7 +345,6 @@
     startEchoingDeviceLog();
 
     // Start the application.
-    final bool hasDirtyDependencies = hotRunner.hasDirtyDependencies(this);
     final Future<LaunchResult> futureResult = device.startApp(
       package,
       mainPath: hotRunner.mainPath,
@@ -353,7 +352,6 @@
       platformArgs: platformArgs,
       route: route,
       prebuiltApplication: prebuiltMode,
-      applicationNeedsRebuild: shouldBuild || hasDirtyDependencies,
       usesTerminalUi: hotRunner.usesTerminalUI,
       ipv6: hotRunner.ipv6,
     );
@@ -409,7 +407,6 @@
 
     startEchoingDeviceLog();
 
-    final bool hasDirtyDependencies = coldRunner.hasDirtyDependencies(this);
     final LaunchResult result = await device.startApp(
       package,
       mainPath: coldRunner.mainPath,
@@ -417,7 +414,6 @@
       platformArgs: platformArgs,
       route: route,
       prebuiltApplication: prebuiltMode,
-      applicationNeedsRebuild: shouldBuild || hasDirtyDependencies,
       usesTerminalUi: coldRunner.usesTerminalUI,
       ipv6: coldRunner.ipv6,
     );
@@ -445,7 +441,7 @@
     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 @@
     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 {
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 31de4bd..485ede2 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -7,7 +7,6 @@
 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 '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 @@
     bool saveCompilationTrace = false,
     bool stayResident = true,
     bool ipv6 = false,
-    FlutterProject flutterProject,
   }) : super(devices,
              target: target,
              debuggingOptions: debuggingOptions,
@@ -79,19 +75,13 @@
              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 @@
       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 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;
+      }
+      final DateTime updatedAt = fs.statSync(
+          uri.toFilePath(windows: platform.isWindows)).modified;
+      scanned++;
+      if (updatedAt == null) {
+        continue;
+      }
+      if (updatedAt.millisecondsSinceEpoch > lastCompiled.millisecondsSinceEpoch) {
+        invalidatedFiles.add(uri);
       }
     }
-    // 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);
-    }
+    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
diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart
index d265aac..b50aa652 100644
--- a/packages/flutter_tools/lib/src/test/flutter_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart
@@ -311,7 +311,7 @@
           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;
diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
index bfa2aeb..d5f5c22 100644
--- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart
+++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
@@ -96,7 +96,6 @@
     @required DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) async {
diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart
index 7c23bf4..4b34393 100644
--- a/packages/flutter_tools/lib/src/web/web_device.dart
+++ b/packages/flutter_tools/lib/src/web/web_device.dart
@@ -100,7 +100,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, Object> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) async {
diff --git a/packages/flutter_tools/lib/src/windows/windows_device.dart b/packages/flutter_tools/lib/src/windows/windows_device.dart
index efe7783..c019581 100644
--- a/packages/flutter_tools/lib/src/windows/windows_device.dart
+++ b/packages/flutter_tools/lib/src/windows/windows_device.dart
@@ -57,7 +57,6 @@
     DebuggingOptions debuggingOptions,
     Map<String, dynamic> platformArgs,
     bool prebuiltApplication = false,
-    bool applicationNeedsRebuild = false,
     bool usesTerminalUi = true,
     bool ipv6 = false,
   }) {