diff --git a/src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs b/src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs deleted file mode 100644 index cb6a455050e..00000000000 --- a/src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.Build.Framework; -using Microsoft.Build.UnitTests; -using Microsoft.Build.Utilities; -using Shouldly; -using System; -using System.Collections.Generic; -using System.IO; -using Xunit; - -namespace Microsoft.Build.Tasks.UnitTests -{ - public class RARPrecomputedCache_Tests - { - [Fact] - public void TestPrecomputedCacheOutput() - { - using (TestEnvironment env = TestEnvironment.Create()) - { - TransientTestFile standardCache = env.CreateFile(".cache"); - ResolveAssemblyReference t = new ResolveAssemblyReference() - { - _cache = new SystemState() - }; - t._cache.instanceLocalFileStateCache = new Dictionary() { - { Path.Combine(standardCache.Path, "assembly1"), new SystemState.FileState(DateTime.Now) }, - { Path.Combine(standardCache.Path, "assembly2"), new SystemState.FileState(DateTime.Now) { Assembly = new Shared.AssemblyNameExtension("hi") } } }; - t._cache.IsDirty = true; - t.StateFile = standardCache.Path; - t.WriteStateFile(); - int standardLen = File.ReadAllText(standardCache.Path).Length; - File.Delete(standardCache.Path); - standardLen.ShouldBeGreaterThan(0); - - string precomputedPath = standardCache.Path + ".cache"; - t._cache.IsDirty = true; - t.AssemblyInformationCacheOutputPath = precomputedPath; - t.WriteStateFile(); - File.Exists(standardCache.Path).ShouldBeFalse(); - int preLen = File.ReadAllText(precomputedPath).Length; - preLen.ShouldBeGreaterThan(0); - preLen.ShouldNotBe(standardLen); - } - } - - [Fact] - public void TestPreComputedCacheInputAndOutput() - { - using (TestEnvironment env = TestEnvironment.Create()) { - TransientTestFile standardCache = env.CreateFile(".cache"); - ResolveAssemblyReference rarWriterTask = new ResolveAssemblyReference() - { - _cache = new SystemState() - }; - rarWriterTask._cache.instanceLocalFileStateCache = new Dictionary() { - { Path.Combine(standardCache.Path, "assembly1"), new SystemState.FileState(DateTime.Now) }, - { Path.Combine(standardCache.Path, "assembly2"), new SystemState.FileState(DateTime.Now) { Assembly = new Shared.AssemblyNameExtension("hi") } } }; - rarWriterTask.StateFile = standardCache.Path; - rarWriterTask._cache.IsDirty = true; - rarWriterTask.WriteStateFile(); - - string dllName = Path.Combine(Path.GetDirectoryName(standardCache.Path), "randomFolder", "dll.dll"); - rarWriterTask._cache.instanceLocalFileStateCache.Add(dllName, - new SystemState.FileState(DateTime.Now) { - Assembly = new Shared.AssemblyNameExtension("notDll.dll", false), - RuntimeVersion = "v4.0.30319", - FrameworkNameAttribute = new System.Runtime.Versioning.FrameworkName(".NETFramework", Version.Parse("4.7.2"), "Profile"), - scatterFiles = new string[] { "first", "second" } }); - rarWriterTask._cache.instanceLocalFileStateCache[dllName].Assembly.Version = new Version("16.3"); - string precomputedCachePath = standardCache.Path + ".cache"; - rarWriterTask.AssemblyInformationCacheOutputPath = precomputedCachePath; - rarWriterTask._cache.IsDirty = true; - rarWriterTask.WriteStateFile(); - // The cache is already written; this change should do nothing. - rarWriterTask._cache.instanceLocalFileStateCache[dllName].Assembly = null; - - ResolveAssemblyReference rarReaderTask = new ResolveAssemblyReference(); - rarReaderTask.StateFile = standardCache.Path; - rarReaderTask.AssemblyInformationCachePaths = new ITaskItem[] - { - new TaskItem(precomputedCachePath) - }; - - // At this point, we should have created two cache files: one "normal" one and one "precomputed" one. - // When we read the state file the first time, it should read from the caches produced in a normal - // build, partially because we can read it faster. If that cache does not exist, as with the second - // time we try to read the state file, it defaults to reading the "precomputed" cache. In this case, - // the normal cache does not have dll.dll, whereas the precomputed cache does, so it should not be - // present when we read the first time but should be present the second time. Then we verify that the - // information contained in that cache matches what we'd expect. - rarReaderTask.ReadStateFile(File.GetLastWriteTime, Array.Empty(), p => true); - rarReaderTask._cache.instanceLocalFileStateCache.ShouldNotContainKey(dllName); - File.Delete(standardCache.Path); - rarReaderTask._cache = null; - rarReaderTask.ReadStateFile(File.GetLastWriteTime, Array.Empty(), p => true); - rarReaderTask._cache.instanceLocalFileStateCache.ShouldContainKey(dllName); - SystemState.FileState assembly3 = rarReaderTask._cache.instanceLocalFileStateCache[dllName]; - assembly3.Assembly.FullName.ShouldBe("notDll.dll"); - assembly3.Assembly.Version.Major.ShouldBe(16); - assembly3.RuntimeVersion.ShouldBe("v4.0.30319"); - assembly3.FrameworkNameAttribute.Version.ShouldBe(Version.Parse("4.7.2")); - assembly3.scatterFiles.Length.ShouldBe(2); - assembly3.scatterFiles[1].ShouldBe("second"); - } - } - } -} diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index 4f561d035b3..aefdd24ab45 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -1859,28 +1859,11 @@ private void LogConflict(Reference reference, string fusionName, StringBuilder l /// /// Reads the state file (if present) into the cache. If not present, attempts to read from CacheInputPaths, then creates a new cache if necessary. /// - internal void ReadStateFile(GetLastWriteTime getLastWriteTime, AssemblyTableInfo[] installedAssemblyTableInfo, Func fileExists = null) + internal void ReadStateFile(GetLastWriteTime getLastWriteTime, AssemblyTableInfo[] installedAssemblyTableInfo) { - var deserializeOptions = new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - deserializeOptions.Converters.Add(new SystemState.Converter()); - try - { - _cache = JsonSerializer.Deserialize(File.ReadAllText(_stateFile), deserializeOptions); - } - catch (Exception) - { - // log message - } - - if (_cache == null) - { - _cache = SystemState.DeserializePrecomputedCaches(AssemblyInformationCachePaths ?? Array.Empty(), Log, typeof(SystemState), getLastWriteTime, installedAssemblyTableInfo, fileExists); - } - else - { - _cache.SetGetLastWriteTime(getLastWriteTime); - _cache.SetInstalledAssemblyInformation(installedAssemblyTableInfo); - } + _cache = JsonSerializer.Deserialize(File.ReadAllText(_stateFile), SystemState.JsonSerializerOptions); + _cache.SetGetLastWriteTime(getLastWriteTime); + _cache.SetInstalledAssemblyInformation(installedAssemblyTableInfo); } /// @@ -1888,15 +1871,9 @@ internal void ReadStateFile(GetLastWriteTime getLastWriteTime, AssemblyTableInfo /// internal void WriteStateFile() { - if (!string.IsNullOrEmpty(AssemblyInformationCacheOutputPath)) - { - _cache.SerializePrecomputedCache(AssemblyInformationCacheOutputPath, Log); - } - else if (!string.IsNullOrEmpty(_stateFile) && _cache.IsDirty) + if (!string.IsNullOrEmpty(_stateFile) && _cache.IsDirty) { - var deserializeOptions = new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - deserializeOptions.Converters.Add(new SystemState.Converter()); - File.WriteAllText(_stateFile, JsonSerializer.Serialize(_cache, deserializeOptions)); + File.WriteAllText(_stateFile, JsonSerializer.Serialize(_cache, SystemState.JsonSerializerOptions)); } } #endregion diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index 2dbb3dd9bd9..0627d9b768e 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -111,6 +111,25 @@ internal sealed class SystemState /// private GetAssemblyRuntimeVersion getAssemblyRuntimeVersion; + /// + /// Options for serializing and deserializing SystemStates. + /// + private static JsonSerializerOptions jsonSerializerOptions; + + internal static JsonSerializerOptions JsonSerializerOptions + { + get + { + if (jsonSerializerOptions == null) + { + jsonSerializerOptions = new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; + jsonSerializerOptions.Converters.Add(new SystemState.Converter()); + } + + return jsonSerializerOptions; + } + } + /// /// Class that holds the current file state. /// @@ -673,80 +692,12 @@ out fileState.frameworkName } /// - /// Reads in cached data from stateFiles to build an initial cache. Avoids logging warnings or errors. - /// - internal static SystemState DeserializePrecomputedCaches(ITaskItem[] stateFiles, TaskLoggingHelper log, Type requiredReturnType, GetLastWriteTime getLastWriteTime, AssemblyTableInfo[] installedAssemblyTableInfo, Func fileExists) - { - SystemState retVal = new SystemState(); - retVal.SetGetLastWriteTime(getLastWriteTime); - retVal.SetInstalledAssemblyInformation(installedAssemblyTableInfo); - retVal.isDirty = stateFiles.Length > 0; - HashSet assembliesFound = new HashSet(); - fileExists ??= FileSystems.Default.FileExists; - - foreach (ITaskItem stateFile in stateFiles) - { - // Verify that it's a real stateFile; log message but do not error if not - var deserializeOptions = new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - deserializeOptions.Converters.Add(new SystemState.Converter()); - SystemState sysBase = JsonSerializer.Deserialize(File.ReadAllText(stateFile.ToString()), deserializeOptions); - if (sysBase == null) - { - continue; - } - - foreach (KeyValuePair kvp in sysBase.instanceLocalFileStateCache) - { - string relativePath = kvp.Key; - if (!assembliesFound.Contains(relativePath)) - { - FileState fileState = kvp.Value; - string fullPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(stateFile.ToString()), relativePath)); - if (fileExists(fullPath)) - { - // Correct file path and timestamp - fileState.LastModified = retVal.getLastWriteTime(fullPath); - retVal.instanceLocalFileStateCache[fullPath] = fileState; - assembliesFound.Add(relativePath); - } - } - } - } - - return retVal; - } - - /// - /// Modifies this object to be more portable across machines, then writes it to stateFile. + /// Cached implementation of GetDirectories. /// - internal void SerializePrecomputedCache(string stateFile, TaskLoggingHelper log) - { - Dictionary oldInstanceLocalFileStateCache = instanceLocalFileStateCache; - Dictionary newInstanceLocalFileStateCache = new Dictionary(instanceLocalFileStateCache.Count); - foreach (KeyValuePair kvp in instanceLocalFileStateCache) - { - string relativePath = FileUtilities.MakeRelative(Path.GetDirectoryName(stateFile), kvp.Key); - newInstanceLocalFileStateCache[relativePath] = kvp.Value; - } - instanceLocalFileStateCache = newInstanceLocalFileStateCache; - - if (FileUtilities.FileExistsNoThrow(stateFile)) - { - log.LogWarningWithCodeFromResources("General.StateFileAlreadyPresent", stateFile); - } - JsonSerializerOptions options = new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - options.Converters.Add(new SystemState.Converter()); - File.WriteAllText(stateFile, JsonSerializer.Serialize(this, options)); - instanceLocalFileStateCache = oldInstanceLocalFileStateCache; - } - - /// - /// Cached implementation of GetDirectories. - /// - /// - /// - /// - private string[] GetDirectories(string path, string pattern) + /// + /// + /// + private string[] GetDirectories(string path, string pattern) { // Only cache the *. pattern. This is by far the most common pattern // and generalized caching would require a call to Path.Combine which