@@ -515,15 +515,16 @@ class PluginManager {
515515
516516 /// Compiles [entrypoint] to an AOT snapshot and records timing to the
517517 /// instrumentation log.
518- ProcessResult _compileAotSnapshot (String entrypoint) {
518+ ProcessResult _compileAotSnapshot (File entrypoint) {
519519 instrumentationService.logInfo (
520520 'Running "dart compile aot-snapshot $entrypoint ".' ,
521521 );
522522
523523 var stopwatch = Stopwatch ()..start ();
524+ var depfile = entrypoint.parent.getChildAssumingFile ('depfile.txt' );
524525 var result = _processRunner.runSync (
525526 sdk.dart,
526- ['compile' , 'aot-snapshot' , entrypoint],
527+ ['compile' , 'aot-snapshot' , '--depfile' , depfile.path, entrypoint.path ],
527528 stderrEncoding: utf8,
528529 stdoutEncoding: utf8,
529530 );
@@ -539,9 +540,30 @@ class PluginManager {
539540 /// Compiles [pluginFile] , in [pluginFolder] , to an AOT snapshot, and returns
540541 /// the [File] for the snapshot.
541542 File _compileAsAot ({required File pluginFile, required Folder pluginFolder}) {
543+ try {
544+ // Potentially use existing snapshot.
545+ var aotSnapshotFile = _existingAotSnapshot (
546+ resourceProvider: _resourceProvider,
547+ pluginFile: pluginFile,
548+ pluginFolder: pluginFolder,
549+ );
550+ if (aotSnapshotFile != null ) {
551+ instrumentationService.logInfo (
552+ 'Using existing plugin AOT snapshot at '
553+ "'${aotSnapshotFile .path }'" ,
554+ );
555+ return aotSnapshotFile;
556+ }
557+ } catch (error, stackTrace) {
558+ instrumentationService.logException (
559+ 'Exception while checking an existing plugin AOT snapshot: '
560+ '"$error "\n $stackTrace ' ,
561+ );
562+ }
563+
542564 // When the Dart Analysis Server is built as AOT, then all spawned
543565 // Isolates must also be built as AOT.
544- var aotResult = _compileAotSnapshot (pluginFile.path );
566+ var aotResult = _compileAotSnapshot (pluginFile);
545567 if (aotResult.exitCode != 0 ) {
546568 var buffer = StringBuffer ();
547569 buffer.writeln (
@@ -564,7 +586,7 @@ class PluginManager {
564586 /// Computes the plugin files, given that the plugin should exist in
565587 /// [pluginFolder] .
566588 ///
567- /// Runs `pub` if [pubCommand] is not `null` .
589+ /// Runs `pub <pubCommand>` in [pluginFolder] if [pubCommand] is not `null` .
568590 PluginFiles _computeFiles (
569591 Folder pluginFolder, {
570592 required bool builtAsAot,
@@ -582,7 +604,10 @@ class PluginManager {
582604 .getChildAssumingFile (file_paths.packageConfigJson);
583605
584606 if (pubCommand != null ) {
585- var pubResult = _runPubCommand (pubCommand, pluginFolder);
607+ var pubResult = _runPubCommand (
608+ pubCommand,
609+ workingDirectory: pluginFolder,
610+ );
586611 String ? exceptionReason;
587612 if (pubResult.exitCode != 0 ) {
588613 var buffer = StringBuffer ();
@@ -722,6 +747,56 @@ class PluginManager {
722747 return packageConfigFile;
723748 }
724749
750+ /// Returns a viable existing plugin AOT snapshot, if it exists and its
751+ /// modification timestamp is newer than all of its dependencies, and `null`
752+ /// otherwise.
753+ ///
754+ /// The dependencies of an AOT snapshot are the pubspec file, the
755+ /// entrypoint file, and all of the files referenced in the depfile which
756+ /// is generated by the `dart compile` command.
757+ File ? _existingAotSnapshot ({
758+ required ResourceProvider resourceProvider,
759+ required File pluginFile,
760+ required Folder pluginFolder,
761+ }) {
762+ var aotSnapshotFile = pluginFolder
763+ .getChildAssumingFolder ('bin' )
764+ .getChildAssumingFile ('plugin.aot' );
765+ if (! aotSnapshotFile.exists) return null ;
766+ var snapshotModificationStamp = aotSnapshotFile.modificationStamp;
767+
768+ if (pluginFile.modificationStamp > snapshotModificationStamp) return null ;
769+ var pubspecFile = pluginFolder.getChildAssumingFile (file_paths.pubspecYaml);
770+ if (pubspecFile.modificationStamp > snapshotModificationStamp) return null ;
771+
772+ var depfile = pluginFolder
773+ .getChildAssumingFolder ('bin' )
774+ .getChildAssumingFile ('depfile.txt' );
775+ if (! depfile.exists) return null ;
776+
777+ var content = depfile.readAsStringSync ();
778+ var dependencies = parseDepfile (content);
779+ if (dependencies == null ) {
780+ // Malformed depfile content.
781+ return null ;
782+ }
783+
784+ for (var dependencyPath in dependencies) {
785+ var file = _resourceProvider.getFile (dependencyPath);
786+ if (! file.exists) {
787+ // Something has certainly changed on disk; do not use the cached
788+ // snapshot.
789+ return null ;
790+ }
791+ if (file.modificationStamp > snapshotModificationStamp) {
792+ // Snapshot is stale.
793+ return null ;
794+ }
795+ }
796+
797+ return aotSnapshotFile;
798+ }
799+
725800 void _notifyPluginsChanged () => _pluginsChanged.add (null );
726801
727802 /// Return the names of packages that are listed as dependencies in the given
@@ -742,17 +817,20 @@ class PluginManager {
742817 }
743818
744819 /// Runs (and records timing to the instrumentation log) a Pub command
745- /// [pubCommand] in [folder] .
746- ProcessResult _runPubCommand (String pubCommand, Folder folder) {
820+ /// [pubCommand] in [workingDirectory] .
821+ ProcessResult _runPubCommand (
822+ String pubCommand, {
823+ required Folder workingDirectory,
824+ }) {
747825 instrumentationService.logInfo (
748- 'Running "pub $pubCommand " in "${folder .path }".' ,
826+ 'Running "pub $pubCommand " in "${workingDirectory .path }".' ,
749827 );
750828
751829 var stopwatch = Stopwatch ()..start ();
752830 var result = _processRunner.runSync (
753831 sdk.dart,
754832 ['pub' , pubCommand],
755- workingDirectory: folder .path,
833+ workingDirectory: workingDirectory .path,
756834 environment: {_pubEnvironmentKey: _getPubEnvironmentValue ()},
757835 stderrEncoding: utf8,
758836 stdoutEncoding: utf8,
@@ -772,6 +850,50 @@ class PluginManager {
772850 return hex.encode (bytes);
773851 }
774852
853+ /// Parses Ninja-style depfile content, returning a list of dependency paths.
854+ ///
855+ /// Returns `null` if the text is not valid depfile content.
856+ ///
857+ /// The format is:
858+ ///
859+ /// target: dependency1 dependency2 ...
860+ ///
861+ /// See https://ninja-build.org/manual.html#_depfile.
862+ @visibleForTesting
863+ static List <String >? parseDepfile (String content) {
864+ var colonIndex = content.indexOf (': ' );
865+ if (colonIndex < 0 ) {
866+ // Not a valid depfile.
867+ return null ;
868+ }
869+ var dependenciesString = content
870+ .substring (colonIndex + 1 )
871+ .trimLeft ()
872+ .replaceAll (RegExp (r'[\r\n]' ), '' );
873+ var dependencies = < String > [];
874+ var start = 0 ;
875+ while (start < dependenciesString.length) {
876+ var index = start;
877+ while (index < dependenciesString.length) {
878+ var char = dependenciesString[index];
879+ if (char == ' ' ) {
880+ break ;
881+ } else if (char == r'\' ) {
882+ index++ ;
883+ }
884+ index++ ;
885+ }
886+ dependencies.add (
887+ dependenciesString
888+ .substring (start, index)
889+ .replaceAll (r'\\' , r'\' )
890+ .replaceAll (r'\ ' , ' ' ),
891+ );
892+ start = index + 1 ;
893+ }
894+ return dependencies.where ((p) => p.isNotEmpty).toList ();
895+ }
896+
775897 /// Record the fact that the given [pluginIsolate] responded to a request with
776898 /// the given [method] in the given [time] .
777899 static void recordResponseTime (
0 commit comments