diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index 4844d757611..59273724f1d 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -49,7 +49,7 @@ public class ResolveAssemblyReference : TaskExtension /// /// Cache of system state information, used to optimize performance. /// - private SystemState _cache = null; + internal SystemState _cache = null; /// /// Construct @@ -1884,23 +1884,27 @@ private void LogConflict(Reference reference, string fusionName, StringBuilder l /// /// Reads the state file (if present) into the cache. /// - private void ReadStateFile() + internal void ReadStateFile(FileExists fileExists) { _cache = SystemState.DeserializeCacheByTranslator(_stateFile, Log); // Construct the cache if necessary. if (_cache == null) { - _cache = new SystemState(); + _cache = SystemState.DeserializePrecomputedCachesByTranslator(AssemblyInformationCachePaths ?? Array.Empty(), Log, fileExists); } } /// /// Write out the state file if a state name was supplied and the cache is dirty. /// - private void WriteStateFile() + internal void WriteStateFile() { - if (!string.IsNullOrEmpty(_stateFile) && _cache.IsDirty) + if (!String.IsNullOrEmpty(AssemblyInformationCacheOutputPath)) + { + _cache.SerializePrecomputedCacheByTranslator(AssemblyInformationCacheOutputPath, Log); + } + else if (!string.IsNullOrEmpty(_stateFile) && _cache.IsDirty) { _cache.SerializeCacheByTranslator(_stateFile, Log); } @@ -2132,7 +2136,7 @@ ReadMachineTypeFromPEHeader readMachineTypeFromPEHeader } // Load any prior saved state. - ReadStateFile(); + ReadStateFile(fileExists); _cache.SetGetLastWriteTime(getLastWriteTime); _cache.SetInstalledAssemblyInformation(installedAssemblyTableInfo); diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index b4f422959a2..fa5e8de3517 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Runtime.Versioning; using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Tasks.AssemblyDependency; @@ -35,7 +36,7 @@ internal sealed class SystemState : StateFileBase, ITranslatable /// /// Cache at the SystemState instance level. It is serialized and reused between instances. /// - private Dictionary instanceLocalFileStateCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + internal Dictionary instanceLocalFileStateCache = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// LastModified information is purely instance-local. It doesn't make sense to @@ -71,7 +72,7 @@ internal sealed class SystemState : StateFileBase, ITranslatable /// /// True if the contents have changed. /// - private bool isDirty; + internal bool isDirty; /// /// Delegate used internally. @@ -112,7 +113,7 @@ internal sealed class SystemState : StateFileBase, ITranslatable /// Class that holds the current file state. /// [Serializable] - private sealed class FileState : ITranslatable + internal sealed class FileState : ITranslatable { /// /// The last modified time for this file. @@ -276,7 +277,7 @@ internal void SerializeCacheByTranslator(string stateFile, TaskLoggingHelper log /// Read the contents of this object out to the specified file. /// TODO: once all classes derived from StateFileBase adopt the new serialization, we should consider moving this into the base class /// - internal static SystemState DeserializeCacheByTranslator(string stateFile, TaskLoggingHelper log) + internal static SystemState DeserializeCacheByTranslator(string stateFile, TaskLoggingHelper log, bool logWarning = true) { // First, we read the cache from disk if one exists, or if one does not exist, we create one. try @@ -309,7 +310,15 @@ internal static SystemState DeserializeCacheByTranslator(string stateFile, TaskL // any exception imaginable. Catch them all here. // Not being able to deserialize the cache is not an error, but we let the user know anyway. // Don't want to hold up processing just because we couldn't read the file. - log.LogWarningWithCodeFromResources("General.CouldNotReadStateFile", stateFile, e.Message); + if (logWarning) + { + log.LogWarningWithCodeFromResources("General.CouldNotReadStateFile", stateFile, e.Message); + } + else + { + log.LogMessageFromResources("General.CouldNotReadStateFile", stateFile, e.Message); + } + return null; } return null; @@ -337,6 +346,7 @@ public void Translate(ITranslator translator) internal bool IsDirty { get { return isDirty; } + set { isDirty = value; } } /// @@ -596,6 +606,69 @@ out fileState.frameworkName frameworkName = fileState.frameworkName; } + /// + /// Reads in cached data from stateFiles to build an initial cache. Avoids logging warnings or errors. + /// + /// List of locations of caches on disk. + /// How to log + /// Whether a file exists + /// + internal static SystemState DeserializePrecomputedCachesByTranslator(ITaskItem[] stateFiles, TaskLoggingHelper log, FileExists fileExists) + { + SystemState retVal = new SystemState(); + retVal.isDirty = stateFiles.Length > 0; + HashSet assembliesFound = new HashSet(); + + foreach (ITaskItem stateFile in stateFiles) + { + // Verify that it's a real stateFile. Log message but do not error if not. + SystemState sysState = DeserializeCacheByTranslator(stateFile.ToString(), log, false); + if (sysState == null) + { + continue; + } + foreach (KeyValuePair kvp in sysState.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 + retVal.instanceLocalFileStateCache[fullPath] = fileState; + assembliesFound.Add(relativePath); + } + } + } + } + + return retVal; + } + + /// + /// Modifies this object to be more portable across machines, then writes it to filePath. + /// + /// Path to which to write the precomputed cache + /// How to log + internal void SerializePrecomputedCacheByTranslator(string stateFile, TaskLoggingHelper log) + { + 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); + } + SerializeCacheByTranslator(stateFile, log); + } + /// /// Cached implementation of GetDirectories. ///