microsoft / MSBuildLocator

An API to locate MSBuild assemblies from an installed Visual Studio location. Use this to ensure that calling the MSBuild API will use the same toolset that a build from Visual Studio or msbuild.exe would.
Other
218 stars 83 forks source link

MSBuild evaluation fails with "System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1" not found #95

Closed AArnott closed 3 years ago

AArnott commented 4 years ago

My unit tests which use the MSBuildLocator started failing recently when MSBuild 16.7 came out with the failure below.

The Microsoft.Build.dll that is loaded by MSBuildLocator has System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0 next to it. I don't have any version of that assembly in my test output directory. Shouldn't the MSBuildLocator's assembly resolver find all of the msbuild's dependencies including this one?

  Error Message:
   System.IO.FileNotFoundException : Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
  Stack Trace:
     at System.MemoryExtensions.AsSpan(String text)
   at Microsoft.Build.Evaluation.Expander`2.Function`1.ExtractPropertyFunction(String expressionFunction, IElementLocation elementLocation, Object propertyValue, UsedUninitializedProperties usedUnInitializedProperties, IFileSystem fileSystem)
   at Microsoft.Build.Evaluation.Expander`2.PropertyExpander`1.ExpandPropertyBody(String propertyBody, Object propertyValue, IPropertyProvider`1 properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties, IFileSystem fileSystem)
   at Microsoft.Build.Evaluation.Expander`2.PropertyExpander`1.ExpandPropertiesLeaveTypedAndEscaped(String expression, IPropertyProvider`1 properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties, IFileSystem fileSystem)
   at Microsoft.Build.Evaluation.Expander`2.PropertyExpander`1.ExpandPropertiesLeaveEscaped(String expression, IPropertyProvider`1 properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties, IFileSystem fileSystem)
   at Microsoft.Build.Evaluation.Expander`2.ExpandIntoStringLeaveEscaped(String expression, ExpanderOptions options, IElementLocation elementLocation)
   at Microsoft.Build.Evaluation.ToolsetReader.ExpandPropertyUnescaped(ToolsetPropertyDefinition property, Expander`2 expander)
   at Microsoft.Build.Evaluation.ToolsetReader.EvaluateAndSetProperty(ToolsetPropertyDefinition property, PropertyDictionary`1 properties, PropertyDictionary`1 globalProperties, PropertyDictionary`1 initialProperties, Boolean accumulateProperties, String& toolsPath, String& binPath, Expander`2& expander)
   at Microsoft.Build.Evaluation.ToolsetReader.ReadToolset(ToolsetPropertyDefinition toolsVersion, PropertyDictionary`1 globalProperties, PropertyDictionary`1 initialProperties, Boolean accumulateProperties)
   at Microsoft.Build.Evaluation.ToolsetReader.ReadEachToolset(Dictionary`2 toolsets, PropertyDictionary`1 globalProperties, PropertyDictionary`1 initialProperties, Boolean accumulateProperties)
   at Microsoft.Build.Evaluation.ToolsetReader.ReadToolsets(Dictionary`2 toolsets, PropertyDictionary`1 globalProperties, PropertyDictionary`1 initialProperties, Boolean accumulateProperties, String& msBuildOverrideTasksPath, String& defaultOverrideToolsVersion)
   at Microsoft.Build.Evaluation.ToolsetReader.ReadAllToolsets(Dictionary`2 toolsets, ToolsetRegistryReader registryReader, ToolsetConfigurationReader configurationReader, PropertyDictionary`1 environmentProperties, PropertyDictionary`1 globalProperties, ToolsetDefinitionLocations locations)
   at Microsoft.Build.Evaluation.ProjectCollection.InitializeToolsetCollection(ToolsetRegistryReader registryReader, ToolsetConfigurationReader configReader)
   at Microsoft.Build.Evaluation.ProjectCollection..ctor(IDictionary`2 globalProperties, IEnumerable`1 loggers, IEnumerable`1 remoteLoggers, ToolsetDefinitionLocations toolsetDefinitionLocations, Int32 maxNodeCount, Boolean onlyLogCriticalEvents, Boolean loadProjectsReadOnly)
   at Microsoft.Build.Evaluation.ProjectCollection..ctor()
   at BuildIntegrationTests.Init() in D:\git\Nerdbank.GitVersioning\src\NerdBank.GitVersioning.Tests\BuildIntegrationTests.cs:line 64
   at BuildIntegrationTests..ctor(ITestOutputHelper logger) in D:\git\Nerdbank.GitVersioning\src\NerdBank.GitVersioning.Tests\BuildIntegrationTests.cs:line 55
jairbubbles commented 3 years ago

Same issue on my side. It's not pretty but adding an AssemblyResolve seems to work:

AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
  var filePath = Path.Combine(latestInstance.MSBuildPath, $"{new AssemblyName(e.Name).Name}.dll");
  if (File.Exists(filePath))
  {
    return Assembly.LoadFrom(filePath);
  }
  return null;
};

Please note that depending on the version of Microsoft.Build package used and the msbuild.exe targetted I have also seen different errors involving System.Collections.Immutable.dll, System.IO.Compression.dll or System.Memory.

Forgind commented 3 years ago

Was this fixed by #94?

jairbubbles commented 3 years ago

I think I tested the pre-release version that should have contained the fix and I still had issues. It might have been on different assembly though.

Why do we keep a fixed list of MsBuild assemblies? Why don't we just load if it's found in the MsBuild directory? It seems more future-proof to me.

Forgind commented 3 years ago

Why do we keep a fixed list of MsBuild assemblies? Why don't we just load if it's found in the MsBuild directory? It seems more future-proof to me.

A very reasonable question. I can imagine it would be worse for perf, since a lot of that stuff isn't really MSBuild's, but I doubt that would be a huge factor, and it would certainly be more convenient and future-proof. I put a change here. We'll see how it goes. 🙂

Forgind commented 3 years ago

107 was merged, though it isn't in the NuGet package yet. Does that work?

AArnott commented 3 years ago

@forgind: No.

I locally built master (d007e5e6f7) and tested it against my original repro (https://github.com/dotnet/Nerdbank.GitVersioning.git 0f97225) and ran the AssemblyInfo test. The error changed from a missing Unsafe dll to this one:

  Message: 
    System.AggregateException : A directory or directories in "msbuildSearchPaths" do not exist
  Stack Trace: 
    MSBuildLocator.RegisterMSBuildPath(String[] msbuildSearchPaths)
    MSBuildLocator.RegisterMSBuildPath(String msbuildPath)
    MSBuildLocator.RegisterInstance(VisualStudioInstance instance)
    MSBuildLocator.RegisterDefaults()
    MSBuildExtensions.LoadMSBuild() line 26
    MSBuildFixture.ctor() line 5
Forgind commented 3 years ago

I think that error has now been fixed.

AArnott commented 3 years ago

Confirmed. As of 23fb66e50a36d6 the test passes now.