Skip to content

Commit

Permalink
Pre cache (#6107)
Browse files Browse the repository at this point in the history
  • Loading branch information
Forgind committed Mar 15, 2021
1 parent 7a5b5e0 commit 8c7ccdf
Show file tree
Hide file tree
Showing 19 changed files with 304 additions and 8 deletions.
142 changes: 142 additions & 0 deletions src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs
@@ -0,0 +1,142 @@
// 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<string, SystemState.FileState>() {
{ 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 StandardCacheTakesPrecedence()
{
using (TestEnvironment env = TestEnvironment.Create())
{
TransientTestFile standardCache = env.CreateFile(".cache");
ResolveAssemblyReference rarWriterTask = new ResolveAssemblyReference()
{
_cache = new SystemState()
};
rarWriterTask._cache.instanceLocalFileStateCache = new Dictionary<string, SystemState.FileState>();
rarWriterTask.StateFile = standardCache.Path;
rarWriterTask._cache.IsDirty = true;
// Write standard cache
rarWriterTask.WriteStateFile();

string dllName = Path.Combine(Path.GetDirectoryName(standardCache.Path), "randomFolder", "dll.dll");
rarWriterTask._cache.instanceLocalFileStateCache.Add(dllName,
new SystemState.FileState(DateTime.Now)
{
Assembly = null,
RuntimeVersion = "v4.0.30319",
FrameworkNameAttribute = new System.Runtime.Versioning.FrameworkName(".NETFramework", Version.Parse("4.7.2"), "Profile"),
scatterFiles = new string[] { "first", "second" }
});
string precomputedCachePath = standardCache.Path + ".cache";
rarWriterTask.AssemblyInformationCacheOutputPath = precomputedCachePath;
rarWriterTask._cache.IsDirty = true;
// Write precomputed cache
rarWriterTask.WriteStateFile();

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, it should read from the caches produced in a normal build. 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 it.
rarReaderTask.ReadStateFile(p => true);
rarReaderTask._cache.instanceLocalFileStateCache.ShouldNotContainKey(dllName);
}
}

[Fact]
public void TestPreComputedCacheInputMatchesOutput()
{
using (TestEnvironment env = TestEnvironment.Create())
{
TransientTestFile precomputedCache = env.CreateFile(".cache");
ResolveAssemblyReference rarWriterTask = new ResolveAssemblyReference()
{
_cache = new SystemState()
};
string dllName = Path.Combine(Path.GetDirectoryName(precomputedCache.Path), "randomFolder", "dll.dll");
rarWriterTask._cache.instanceLocalFileStateCache = new Dictionary<string, SystemState.FileState>() {
{ Path.Combine(precomputedCache.Path, "..", "assembly1", "assembly1"), new SystemState.FileState(DateTime.Now) },
{ Path.Combine(precomputedCache.Path, "assembly2"), new SystemState.FileState(DateTime.Now) { Assembly = new Shared.AssemblyNameExtension("hi") } },
{ dllName, new SystemState.FileState(DateTime.Now) {
Assembly = null,
RuntimeVersion = "v4.0.30319",
FrameworkNameAttribute = new System.Runtime.Versioning.FrameworkName(".NETFramework", Version.Parse("4.7.2"), "Profile"),
scatterFiles = new string[] { "first", "second" } } } };

rarWriterTask.AssemblyInformationCacheOutputPath = precomputedCache.Path;
rarWriterTask._cache.IsDirty = true;

// Throws an exception because precomputedCache.Path already exists.
Should.Throw<InvalidOperationException>(() => rarWriterTask.WriteStateFile());
File.Delete(precomputedCache.Path);
rarWriterTask.WriteStateFile();

ResolveAssemblyReference rarReaderTask = new ResolveAssemblyReference();
rarReaderTask.StateFile = precomputedCache.Path.Substring(0, precomputedCache.Path.Length - 6); // Not a real path; should not be used.
rarReaderTask.AssemblyInformationCachePaths = new ITaskItem[]
{
new TaskItem(precomputedCache.Path)
};

// At this point, the standard cache does not exist, so it defaults to reading the "precomputed" cache.
// Then we verify that the information contained in that cache matches what we'd expect.
rarReaderTask.ReadStateFile(p => true);
rarReaderTask._cache.instanceLocalFileStateCache.ShouldContainKey(dllName);
SystemState.FileState assembly3 = rarReaderTask._cache.instanceLocalFileStateCache[dllName];
assembly3.Assembly.ShouldBeNull();
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");
}
}
}
}
21 changes: 15 additions & 6 deletions src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs
Expand Up @@ -49,7 +49,7 @@ public class ResolveAssemblyReference : TaskExtension
/// <summary>
/// Cache of system state information, used to optimize performance.
/// </summary>
private SystemState _cache = null;
internal SystemState _cache = null;

/// <summary>
/// Construct
Expand Down Expand Up @@ -1883,11 +1883,16 @@ private void LogConflict(Reference reference, string fusionName, StringBuilder l
/// <summary>
/// Reads the state file (if present) into the cache.
/// </summary>
private void ReadStateFile()
internal void ReadStateFile(FileExists fileExists)
{
_cache = SystemState.DeserializeCacheByTranslator(_stateFile, Log);

// Construct the cache if necessary.
// Construct the cache only if we can't find any caches.
if (_cache == null && AssemblyInformationCachePaths != null && AssemblyInformationCachePaths.Length > 0)
{
_cache = SystemState.DeserializePrecomputedCachesByTranslator(AssemblyInformationCachePaths, Log, fileExists);
}

if (_cache == null)
{
_cache = new SystemState();
Expand All @@ -1897,9 +1902,13 @@ private void ReadStateFile()
/// <summary>
/// Write out the state file if a state name was supplied and the cache is dirty.
/// </summary>
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);
}
Expand Down Expand Up @@ -2131,7 +2140,7 @@ ReadMachineTypeFromPEHeader readMachineTypeFromPEHeader
}

// Load any prior saved state.
ReadStateFile();
ReadStateFile(fileExists);
_cache.SetGetLastWriteTime(getLastWriteTime);
_cache.SetInstalledAssemblyInformation(installedAssemblyTableInfo);

Expand Down
2 changes: 2 additions & 0 deletions src/Tasks/Microsoft.Common.CurrentVersion.targets
Expand Up @@ -2226,6 +2226,8 @@ Copyright (C) Microsoft Corporation. All rights reserved.
TargetFrameworkMonikerDisplayName="$(TargetFrameworkMonikerDisplayName)"
TargetedRuntimeVersion="$(TargetedRuntimeVersion)"
StateFile="$(ResolveAssemblyReferencesStateFile)"
AssemblyInformationCachePaths="$(AssemblyInformationCachePaths)"
AssemblyInformationCacheOutputPath="$(AssemblyInformationCacheOutputPath)"
InstalledAssemblySubsetTables="@(InstalledAssemblySubsetTables)"
TargetFrameworkSubsets="@(_ReferenceInstalledAssemblySubsets)"
FullTargetFrameworkSubsetNames="$(FullReferenceAssemblyNames)"
Expand Down
4 changes: 4 additions & 0 deletions src/Tasks/Resources/Strings.resx
Expand Up @@ -451,6 +451,10 @@
<value>MSB3101: Could not write state file "{0}". {1}</value>
<comment>{StrBegin="MSB3101: "}</comment>
</data>
<data name="General.StateFileAlreadyPresent">
<value>MSB3667: There is already a file at "{0}". If you are trying to create a precomputed cache, ensure that you are building a single project that depends on your assemblies rather than building your assemblies themselves. If you are running the ResolveAssemblyReference task normally, do not set the "AssemblyInformationCacheOutputPath" parameter of the ResolveAssemblyReference task.</value>
<comment>{StrBegin="MSB3667: "}</comment>
</data>
<data name="General.DuplicateItemsNotSupported">
<value>MSB3105: The item "{0}" was specified more than once in the "{1}" parameter. Duplicate items are not supported by the "{1}" parameter.</value>
<comment>{StrBegin="MSB3105: "}</comment>
Expand Down
5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.en.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8c7ccdf

Please sign in to comment.