Check for workspaces consistency after analysis contexts creation.
Bug: https://github.com/dart-lang/linter/issues/2694
Change-Id: I13cae115e48ae794e0751b6099f864ca9d202cdc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/203821
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index 047e8b1b..721f37c 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -175,6 +175,10 @@
final Map<Folder, AnalysisDriver> driverMap =
HashMap<Folder, AnalysisDriver>();
+ /// The timer that is started after creating analysis contexts, to check
+ /// for in-between changes to configuration files.
+ Timer? _collectionConsistencyCheckTimer;
+
/// Stream subscription we are using to watch each analysis root directory for
/// changes.
final Map<Folder, StreamSubscription<WatchEvent>> changeSubscriptions =
@@ -461,6 +465,7 @@
}
callbacks.afterContextsCreated();
+ _scheduleCollectionConsistencyCheck(collection);
}
/// Clean up and destroy the context associated with the given folder.
@@ -482,6 +487,7 @@
void _destroyAnalysisContexts() {
var collection = _collection;
if (collection != null) {
+ _collectionConsistencyCheckTimer?.cancel();
for (var analysisContext in collection.contexts) {
_destroyAnalysisContext(analysisContext);
}
@@ -632,6 +638,28 @@
existingExcludedSet.containsAll(excludedPaths);
}
+ /// We create analysis contexts, and then start watching the file system
+ /// for modifications to Dart files, and to configuration files, e.g.
+ /// `pubspec.yaml` file Pub workspaces.
+ ///
+ /// So, it is possible that one of these files will be changed between the
+ /// moment when we read it, and the moment when we started watching for
+ /// changes. Using `package:watcher` before creating analysis contexts
+ /// was still not reliable enough.
+ ///
+ /// To work around this we check after a short timeout, and hope that
+ /// any subsequent changes will be noticed by `package:watcher`.
+ void _scheduleCollectionConsistencyCheck(
+ AnalysisContextCollectionImpl collection,
+ ) {
+ _collectionConsistencyCheckTimer = Timer(Duration(seconds: 1), () {
+ _collectionConsistencyCheckTimer = null;
+ if (!collection.areWorkspacesConsistent) {
+ _createAnalysisContexts();
+ }
+ });
+ }
+
/// Starts watching for the `bazel-bin` and `blaze-bin` symlinks.
///
/// This is important since these symlinks might not be present when the
diff --git a/pkg/analyzer/lib/src/dart/analysis/analysis_context_collection.dart b/pkg/analyzer/lib/src/dart/analysis/analysis_context_collection.dart
index 2335ded..103c1a6 100644
--- a/pkg/analyzer/lib/src/dart/analysis/analysis_context_collection.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/analysis_context_collection.dart
@@ -79,6 +79,20 @@
}
}
+ /// Return `true` if the read state of configuration files is consistent
+ /// with their current state on the file system. We use this as a work
+ /// around an issue with watching for file system changes.
+ bool get areWorkspacesConsistent {
+ for (var analysisContext in contexts) {
+ var contextRoot = analysisContext.contextRoot;
+ var workspace = contextRoot.workspace;
+ if (!workspace.isConsistentWithFileSystem) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@override
DriverBasedAnalysisContext contextFor(String path) {
_throwIfNotAbsoluteNormalizedPath(path);
diff --git a/pkg/analyzer/lib/src/workspace/package_build.dart b/pkg/analyzer/lib/src/workspace/package_build.dart
index 5d2ee1e..7e0c179 100644
--- a/pkg/analyzer/lib/src/workspace/package_build.dart
+++ b/pkg/analyzer/lib/src/workspace/package_build.dart
@@ -145,6 +145,9 @@
/// package:build does it.
static const String _pubspecName = 'pubspec.yaml';
+ /// The associated pubspec file.
+ final File _pubspecFile;
+
/// The content of the `pubspec.yaml` file.
/// We read it once, so that all usages return consistent results.
final String? _pubspecContent;
@@ -185,11 +188,17 @@
this.generatedRootPath,
this.generatedThisPath,
File pubspecFile,
- ) : _pubspecContent = _fileContentOrNull(pubspecFile) {
+ ) : _pubspecFile = pubspecFile,
+ _pubspecContent = _fileContentOrNull(pubspecFile) {
_theOnlyPackage = PackageBuildWorkspacePackage(root, this);
}
@override
+ bool get isConsistentWithFileSystem {
+ return _fileContentOrNull(_pubspecFile) == _pubspecContent;
+ }
+
+ @override
UriResolver get packageUriResolver => PackageBuildPackageUriResolver(
this, PackageMapUriResolver(provider, packageMap));
diff --git a/pkg/analyzer/lib/src/workspace/pub.dart b/pkg/analyzer/lib/src/workspace/pub.dart
index ef69e23..e5059d7 100644
--- a/pkg/analyzer/lib/src/workspace/pub.dart
+++ b/pkg/analyzer/lib/src/workspace/pub.dart
@@ -21,6 +21,9 @@
/// Each Pub workspace is itself one package.
late final PubWorkspacePackage _theOnlyPackage;
+ /// The associated pubspec file.
+ final File _pubspecFile;
+
/// The content of the `pubspec.yaml` file.
/// We read it once, so that all usages return consistent results.
final String? _pubspecContent;
@@ -30,11 +33,17 @@
Map<String, List<Folder>> packageMap,
String root,
File pubspecFile,
- ) : _pubspecContent = _fileContentOrNull(pubspecFile),
+ ) : _pubspecFile = pubspecFile,
+ _pubspecContent = _fileContentOrNull(pubspecFile),
super(provider, packageMap, root) {
_theOnlyPackage = PubWorkspacePackage(root, this);
}
+ @override
+ bool get isConsistentWithFileSystem {
+ return _fileContentOrNull(_pubspecFile) == _pubspecContent;
+ }
+
@internal
@override
void contributeToResolutionSalt(ApiSignature buffer) {
diff --git a/pkg/analyzer/lib/src/workspace/workspace.dart b/pkg/analyzer/lib/src/workspace/workspace.dart
index c4cb125..34ab9c1 100644
--- a/pkg/analyzer/lib/src/workspace/workspace.dart
+++ b/pkg/analyzer/lib/src/workspace/workspace.dart
@@ -17,6 +17,11 @@
/// Return true iff this [Workspace] is a [BazelWorkspace].
bool get isBazel => false;
+ /// Return `true` if the read state of configuration files is consistent
+ /// with their current state on the file system.
+ @internal
+ bool get isConsistentWithFileSystem => true;
+
/// The [UriResolver] that can resolve `package` URIs.
UriResolver get packageUriResolver;
diff --git a/pkg/analyzer/test/src/workspace/package_build_test.dart b/pkg/analyzer/test/src/workspace/package_build_test.dart
index 8ce842f..b7a9650 100644
--- a/pkg/analyzer/test/src/workspace/package_build_test.dart
+++ b/pkg/analyzer/test/src/workspace/package_build_test.dart
@@ -640,6 +640,17 @@
workspace.findFile(convertPath('/workspace/web/file.dart')), webFile);
}
+ void test_isConsistentWithFileSystem() {
+ newFolder('/workspace/.dart_tool/build/generated/project/bin');
+ newPubspecYamlFile('/workspace', 'name: project');
+ PackageBuildWorkspace workspace =
+ _createWorkspace('/workspace', ['project']);
+ expect(workspace.isConsistentWithFileSystem, isTrue);
+
+ newPubspecYamlFile('/workspace', 'name: my2');
+ expect(workspace.isConsistentWithFileSystem, isFalse);
+ }
+
PackageBuildWorkspace _createWorkspace(
String root, List<String> packageNames) {
return PackageBuildWorkspace.find(
diff --git a/pkg/analyzer/test/src/workspace/pub_test.dart b/pkg/analyzer/test/src/workspace/pub_test.dart
index f69f1c2..5f68d58 100644
--- a/pkg/analyzer/test/src/workspace/pub_test.dart
+++ b/pkg/analyzer/test/src/workspace/pub_test.dart
@@ -111,4 +111,14 @@
resourceProvider, {}, convertPath('/workspace/lib/lib1.dart'));
expect(workspace, isNull);
}
+
+ void test_isConsistentWithFileSystem() {
+ newPubspecYamlFile('/workspace', 'name: my');
+ var workspace =
+ PubWorkspace.find(resourceProvider, {}, convertPath('/workspace'))!;
+ expect(workspace.isConsistentWithFileSystem, isTrue);
+
+ newPubspecYamlFile('/workspace', 'name: my2');
+ expect(workspace.isConsistentWithFileSystem, isFalse);
+ }
}