Skip to content

Commit

Permalink
Fix bug mixing .NET SDKs (#73)
Browse files Browse the repository at this point in the history
My home box has both the 7.0.400 and 8.0.100 RC2 .NET SDKs installed.
This ended up revealing a subtle bug in the CLI when executing `dotnet
test`.

The test content in this repo generates a global.json that pins the SDK
to 7.0.400. That is done for consistency in testing across developer
machines and CI. The repository itself has no global.json file so it
floats to the _latest_ .NET SDK.

This combination revealed an odd bug in MSBuild. When executing `dotnet
test` the CLI ended up spawning a `dotnet` that sets the
`%MSBuildSDKsPath%` environment variable. Because `dotnet` spawned as an
8.0 process it set that path to the 8.0 SDK.

When executing the `dotnet new` actions inside the tests the global.json
was read by the runtime host and ended up spawning a `dotnet` process
from the 7.0 runtime. But msbuild ended up loading tasks / targets from
8.0 SDK due to the `%MSBuildSDKsPath%` value. Those targeted `net8.0`
  and hence failed to load in the 7.0 runtime.

```
The template "Console App" was created successfully.

 Processing post-creation actions...
 Restoring
 C:\Users\jaredpar\AppData\Local\Temp\CompilerLogFixture\be3521749f1e49c8bb93b2212110c4a8\scratch
 dir\0aa90c51a9794319bb0d1370e52350dc\example-no-generator.csproj:
   Determining projects to restore...
   C:\Program
   Files\dotnet\sdk\8.0.100-rc.2.23502.2\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.DefaultItems.Shared.targets(152,5):
   error MSB4062: The "CheckForImplicitPackageReferenceOverrides" task
   could not be loaded from the assembly C:\Program
   Files\dotnet\sdk\8.0.100-rc.2.23502.2\Sdks\Microsoft.NET.Sdk\targets\..\tools\net8.0\Microsoft.NET.Build.Tasks.dll.
   Could not load file or assembly 'System.Runtime, Version=8.0.0.0,
   Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot
   find the file specified. Confirm that the <UsingTask> declaration is
   correct, that the assembly and all its dependencies are available,
   and that the task contains a public class that implements
   Microsoft.Build.Framework.ITask.
   [C:\Users\jaredpar\AppData\Local\Temp\CompilerLogFixture\be3521749f1e49c8bb93b2212110c4a8\scratch
   dir\0aa90c51a9794319bb0d1370e52350dc\example-no-generator.csproj]
```

Thanks to @rainersigwald and @baronfel for tracking this down!

dotnet/msbuild#9411

variable
  • Loading branch information
jaredpar authored Nov 10, 2023
1 parent d02db09 commit 8170b0d
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 4 deletions.
29 changes: 27 additions & 2 deletions src/Shared/DotnetUtil.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Build.Logging.StructuredLogger;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration.Internal;
using System.Diagnostics;
Expand All @@ -12,11 +14,34 @@ namespace Basic.CompilerLog;

internal static class DotnetUtil
{
private static readonly Lazy<Dictionary<string, string>> _lazyDotnetEnvironmentVariables = new(CreateDotnetEnvironmentVariables);

private static Dictionary<string, string> CreateDotnetEnvironmentVariables()
{
// The CLI, particularly when run from dotnet test, will set the MSBuildSDKsPath environment variable
// to point to the current SDK. That could be an SDK that is higher than the version that our tests
// are executing under. For example `dotnet test` could spawn an 8.0 process but we end up testing
// the 7.0.400 SDK. This environment variable though will point to 8.0 and end up causing load
// issues. Clear it out here so that the `dotnet` commands have a fresh context.
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables())
{
var key = (string)entry.Key;
if (!string.Equals(key, "MSBuildSDKsPath", StringComparison.OrdinalIgnoreCase))
{
map.Add(key, (string)entry.Value!);

}
}
return map;
}

internal static ProcessResult Command(string args, string? workingDirectory = null) =>
ProcessUtil.Run(
"dotnet",
args,
workingDirectory: workingDirectory);
workingDirectory: workingDirectory,
environment: _lazyDotnetEnvironmentVariables.Value);

internal static void CommandOrThrow(string args, string? workingDirectory = null)
{
Expand Down
16 changes: 14 additions & 2 deletions src/Shared/ProcessUtil.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing.Printing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MessagePack.Formatters;

namespace Basic.CompilerLog;

Expand All @@ -28,7 +30,8 @@ internal static class ProcessUtil
internal static ProcessResult Run(
string fileName,
string args,
string? workingDirectory = null)
string? workingDirectory = null,
Dictionary<string, string>? environment = null)
{
var info = new ProcessStartInfo()
{
Expand All @@ -40,11 +43,20 @@ internal static ProcessResult Run(
RedirectStandardError = true,
};

if (environment is not null)
{
info.Environment.Clear();
foreach (var tuple in environment)
{
info.Environment.Add(tuple.Key, tuple.Value);
}
}

var process = Process.Start(info)!;
var standardOut = process.StandardOutput.ReadToEnd();
var standardError = process.StandardError.ReadToEnd();

process.WaitForExit();

return new ProcessResult(
process.ExitCode,
standardOut,
Expand Down

0 comments on commit 8170b0d

Please sign in to comment.