diff --git a/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs b/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs index b9329dad3ba..00dc1bb6f61 100644 --- a/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs +++ b/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs @@ -471,6 +471,14 @@ public void ContextDisambiguatesSameRelativeGlobsPointingOutsideDifferentProject [MemberData(nameof(ContextDisambiguatesRelativeGlobsData))] public void ContextDisambiguatesAFullyQualifiedGlobPointingInAnotherRelativeGlobsCone(EvaluationContext.SharingPolicy policy, string[][] expectedGlobExpansions) { + if (policy == EvaluationContext.SharingPolicy.Shared) + { + // This test case has a dependency on our glob expansion caching policy. If the evaluation context is reused + // between evaluations and files are added to the filesystem between evaluations, the cache may be returning + // stale results. Run only the Isolated variant. + return; + } + var project1Directory = _env.DefaultTestDirectory.CreateDirectory("Project1"); var project1GlobDirectory = project1Directory.CreateDirectory("Glob").CreateDirectory("1").Path; diff --git a/src/Shared/FileMatcher.cs b/src/Shared/FileMatcher.cs index 36e87bf5a35..ac674478108 100644 --- a/src/Shared/FileMatcher.cs +++ b/src/Shared/FileMatcher.cs @@ -110,21 +110,51 @@ internal FileMatcher(IFileSystem fileSystem, GetFileSystemEntries getFileSystemE _getFileSystemEntries = getFileSystemDirectoryEntriesCache == null ? getFileSystemEntries - : (type, path, pattern, directory, projectDirectory) => + : (type, path, pattern, directory, stripProjectDirectory) => { - // Cache only directories, for files we won't hit the cache because the file name patterns tend to be unique - if (type == FileSystemEntity.Directories) + if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_10)) { - return getFileSystemDirectoryEntriesCache.GetOrAdd( - $"{path};{pattern ?? "*"}", - s => getFileSystemEntries( - type, - path, - pattern, - directory, - projectDirectory).ToArray()); + // New behavior: + // Always hit the filesystem with "*" pattern, cache the results, and do the filtering here. + string cacheKey = type switch + { + FileSystemEntity.Files => "F", + FileSystemEntity.Directories => "D", + FileSystemEntity.FilesAndDirectories => "A", + _ => throw new NotImplementedException() + } + ";" + path; + IReadOnlyList allEntriesForPath = getFileSystemDirectoryEntriesCache.GetOrAdd( + cacheKey, + s => getFileSystemEntries( + type, + path, + "*", + directory, + false).ToArray()); + IEnumerable filteredEntriesForPath = (pattern != null && pattern != "*") + ? allEntriesForPath.Where(o => IsMatch(Path.GetFileName(o), pattern)) + : allEntriesForPath; + return stripProjectDirectory + ? RemoveProjectDirectory(filteredEntriesForPath, directory) + : filteredEntriesForPath; + } + else + { + // Legacy behavior: + // Cache only directories, for files we won't hit the cache because the file name patterns tend to be unique + if (type == FileSystemEntity.Directories) + { + return getFileSystemDirectoryEntriesCache.GetOrAdd( + $"D;{path};{pattern ?? "*"}", + s => getFileSystemEntries( + type, + path, + pattern, + directory, + stripProjectDirectory).ToArray()); + } } - return getFileSystemEntries(type, path, pattern, directory, projectDirectory); + return getFileSystemEntries(type, path, pattern, directory, stripProjectDirectory); }; }