Skip to content

Commit

Permalink
Merge branch 'json-serialization' of https://github.com/forgind/msbuild
Browse files Browse the repository at this point in the history
… into serialization-only
  • Loading branch information
Forgind committed Dec 21, 2020
2 parents 73cb93c + df2d5c9 commit 92e5b6d
Show file tree
Hide file tree
Showing 25 changed files with 553 additions and 149 deletions.
8 changes: 5 additions & 3 deletions src/Shared/AssemblyNameExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Text;
using System.Reflection;
using System.Globalization;
using System.Collections.Generic;
using System.Configuration.Assemblies;
using System.Globalization;
Expand Down Expand Up @@ -324,6 +321,11 @@ internal Version Version
CreateAssemblyName();
return asAssemblyName.Version;
}
set
{
CreateAssemblyName();
asAssemblyName.Version = value;
}
}

/// <summary>
Expand Down
108 changes: 108 additions & 0 deletions src/Tasks.UnitTests/RARPrecomputedCache_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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 TestPreComputedCacheInputAndOutput()
{
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>() {
{ 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<AssemblyTableInfo>(), p => true);
rarReaderTask._cache.instanceLocalFileStateCache.ShouldNotContainKey(dllName);
File.Delete(standardCache.Path);
rarReaderTask._cache = null;
rarReaderTask.ReadStateFile(File.GetLastWriteTime, Array.Empty<AssemblyTableInfo>(), 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");
}
}
}
}
45 changes: 32 additions & 13 deletions src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Xml.Linq;

using Microsoft.Build.Eventing;
Expand Down Expand Up @@ -49,7 +51,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 @@ -1855,27 +1857,46 @@ private void LogConflict(Reference reference, string fusionName, StringBuilder l

#region StateFile
/// <summary>
/// Reads the state file (if present) into the cache.
/// Reads the state file (if present) into the cache. If not present, attempts to read from CacheInputPaths, then creates a new cache if necessary.
/// </summary>
private void ReadStateFile()
internal void ReadStateFile(GetLastWriteTime getLastWriteTime, AssemblyTableInfo[] installedAssemblyTableInfo, Func<string, bool> fileExists = null)
{
_cache = (SystemState)StateFileBase.DeserializeCache(_stateFile, Log, typeof(SystemState));
var deserializeOptions = new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
deserializeOptions.Converters.Add(new SystemState.Converter());
try
{
_cache = JsonSerializer.Deserialize<SystemState>(File.ReadAllText(_stateFile), deserializeOptions);
}
catch (Exception)
{
// log message
}

// Construct the cache if necessary.
if (_cache == null)
{
_cache = new SystemState();
_cache = SystemState.DeserializePrecomputedCaches(AssemblyInformationCachePaths ?? Array.Empty<ITaskItem>(), Log, typeof(SystemState), getLastWriteTime, installedAssemblyTableInfo, fileExists);
}
else
{
_cache.SetGetLastWriteTime(getLastWriteTime);
_cache.SetInstalledAssemblyInformation(installedAssemblyTableInfo);
}
}

/// <summary>
/// Write out the state file if a state name was supplied and the cache is dirty.
/// If CacheOutputPath is non-null, writes out a cache to that location. Otherwise, writes 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.SerializePrecomputedCache(AssemblyInformationCacheOutputPath, Log);
}
else if (!string.IsNullOrEmpty(_stateFile) && _cache.IsDirty)
{
_cache.SerializeCache(_stateFile, Log);
var deserializeOptions = new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
deserializeOptions.Converters.Add(new SystemState.Converter());
File.WriteAllText(_stateFile, JsonSerializer.Serialize<SystemState>(_cache, deserializeOptions));
}
}
#endregion
Expand Down Expand Up @@ -2105,9 +2126,7 @@ ReadMachineTypeFromPEHeader readMachineTypeFromPEHeader
}

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

// Cache delegates.
getAssemblyName = _cache.CacheDelegate(getAssemblyName);
Expand Down
3 changes: 2 additions & 1 deletion src/Tasks/Microsoft.Build.Tasks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -986,13 +986,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Collections.Immutable" />
<PackageReference Include="System.Reflection.Metadata" />
<PackageReference Include="System.Resources.Extensions" />
</ItemGroup>

<!-- Tasks need to mimic redistributing the compilers, so add references to both full framework and .net core -->
<ItemGroup>
<!-- Reference this package to get binaries at runtime even when Arcade is not adding compiler references -->
<PackageReference Include="Microsoft.Net.Compilers.Toolset" ExcludeAssets="all" Condition="'$(UsingToolMicrosoftNetCompilers)' == 'false'" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
Expand All @@ -1004,7 +1006,6 @@

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
<PackageReference Include="System.CodeDom" />
<PackageReference Include="System.Reflection.Metadata" />
<PackageReference Include="System.Reflection.TypeExtensions" />
<PackageReference Include="System.Runtime.InteropServices" />
<PackageReference Include="System.Security.Cryptography.Pkcs" />
Expand Down
3 changes: 1 addition & 2 deletions src/Tasks/RegisterAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ public override bool Execute()

if ((AssemblyListFile?.ItemSpec.Length > 0))
{
cacheFile = (AssemblyRegistrationCache)StateFileBase.DeserializeCache(AssemblyListFile.ItemSpec, Log, typeof(AssemblyRegistrationCache)) ??
new AssemblyRegistrationCache();
cacheFile = StateFileBase.DeserializeCache<AssemblyRegistrationCache>(AssemblyListFile.ItemSpec, Log) ?? new AssemblyRegistrationCache();
}

bool taskReturnValue = true;
Expand Down
2 changes: 1 addition & 1 deletion src/Tasks/ResGenDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ internal override void SerializeCache(string stateFile, TaskLoggingHelper log)
/// </summary>
internal static ResGenDependencies DeserializeCache(string stateFile, bool useSourcePath, TaskLoggingHelper log)
{
var retVal = (ResGenDependencies)DeserializeCache(stateFile, log, typeof(ResGenDependencies)) ?? new ResGenDependencies();
var retVal = DeserializeCache<ResGenDependencies>(stateFile, log) ?? new ResGenDependencies();

// Ensure that the cache is properly initialized with respect to how resgen will
// resolve linked files within .resx files. ResGen has two different
Expand Down
2 changes: 1 addition & 1 deletion src/Tasks/ResolveComReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ public override bool Execute()
allProjectRefs = new List<ComReferenceInfo>();
allDependencyRefs = new List<ComReferenceInfo>();

_timestampCache = (ResolveComReferenceCache)StateFileBase.DeserializeCache(StateFile, Log, typeof(ResolveComReferenceCache));
_timestampCache = StateFileBase.DeserializeCache<ResolveComReferenceCache>(StateFile, Log);

if (_timestampCache?.ToolPathsMatchCachePaths(_tlbimpPath, _aximpPath) != true)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Tasks/Resources/Strings.resx
Original file line number Diff line number Diff line change
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.

Loading

0 comments on commit 92e5b6d

Please sign in to comment.