Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use architecture specific DOTNET_ROOT variable name #27566

Merged
merged 5 commits into from
Nov 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/BuiltInTools/DotNetWatchTasks/FileSetSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public class FileSetSerializer : Task

public string TargetFrameworkVersion { get; set; }

public string RuntimeIdentifier { get; set; }

public string DefaultAppHostRuntimeIdentifier { get; set; }

public string RunCommand { get; set; }

public string RunArguments { get; set; }
Expand All @@ -37,6 +41,8 @@ public override bool Execute()
{
IsNetCoreApp = IsNetCoreApp,
TargetFrameworkVersion = TargetFrameworkVersion,
RuntimeIdentifier = RuntimeIdentifier,
DefaultAppHostRuntimeIdentifier = DefaultAppHostRuntimeIdentifier,
RunCommand = RunCommand,
RunArguments = RunArguments,
RunWorkingDirectory = RunWorkingDirectory,
Expand Down
2 changes: 2 additions & 0 deletions src/BuiltInTools/dotnet-watch/DotNetWatch.targets
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ them to a file.
<FileSetSerializer
IsNetCoreApp="$(_IsNetCoreApp)"
TargetFrameworkVersion="$(TargetFrameworkVersion)"
RuntimeIdentifier="$(RuntimeIdentifier)"
DefaultAppHostRuntimeIdentifier="$(DefaultAppHostRuntimeIdentifier)"
RunCommand="$(RunCommand)"
RunArguments="$(RunArguments)"
RunWorkingDirectory="$(RunWorkingDirectory)"
Expand Down
9 changes: 7 additions & 2 deletions src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Watcher.Internal;
using Microsoft.DotNet.Watcher.Tools;
using Microsoft.Extensions.Tools.Internal;
Expand Down Expand Up @@ -305,8 +306,12 @@ private static void ConfigureExecutable(DotNetWatchContext context, ProcessSpec
processSpec.EnvironmentVariables["ASPNETCORE_URLS"] = context.LaunchSettingsProfile.ApplicationUrl;
}

var rootVariableName = Environment.Is64BitProcess ? "DOTNET_ROOT" : "DOTNET_ROOT(x86)";
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(rootVariableName)))
var rootVariableName = EnvironmentVariableNames.TryGetDotNetRootVariableName(
project.RuntimeIdentifier,
project.DefaultAppHostRuntimeIdentifier,
project.TargetFrameworkVersion);

if (rootVariableName != null && string.IsNullOrEmpty(Environment.GetEnvironmentVariable(rootVariableName)))
{
processSpec.EnvironmentVariables[rootVariableName] = Path.GetDirectoryName(DotnetMuxer.MuxerPath);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public class MSBuildFileSetResult

public string TargetFrameworkVersion { get; set; }

public string RuntimeIdentifier { get; set; }

public string DefaultAppHostRuntimeIdentifier { get; set; }

public Dictionary<string, ProjectItems> Projects { get; set; }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Watcher.Tools;
using Microsoft.Extensions.Tools.Internal;
using IReporter = Microsoft.Extensions.Tools.Internal.IReporter;
Expand Down Expand Up @@ -146,14 +147,12 @@ public async Task<FileSet> CreateAsync(CancellationToken cancellationToken)
Debug.Assert(fileItems.All(f => Path.IsPathRooted(f.FilePath)), "All files should be rooted paths");
#endif

// TargetFrameworkVersion appears as v6.0 in msbuild. Ignore the leading v
var targetFrameworkVersion = !string.IsNullOrEmpty(result.TargetFrameworkVersion) ?
Version.Parse(result.TargetFrameworkVersion.AsSpan(1)) : // Ignore leading v
null;
var projectInfo = new ProjectInfo(
_projectFile,
result.IsNetCoreApp,
targetFrameworkVersion,
EnvironmentVariableNames.TryParseTargetFrameworkVersion(result.TargetFrameworkVersion),
result.RuntimeIdentifier,
result.DefaultAppHostRuntimeIdentifier,
result.RunCommand,
result.RunArguments,
result.RunWorkingDirectory);
Expand Down
2 changes: 2 additions & 0 deletions src/BuiltInTools/dotnet-watch/ProjectInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public record ProjectInfo
string ProjectPath,
bool IsNetCoreApp,
Version? TargetFrameworkVersion,
string RuntimeIdentifier,
string DefaultAppHostRuntimeIdentifier,
string RunCommand,
string RunArguments,
string RunWorkingDirectory
Expand Down
4 changes: 4 additions & 0 deletions src/BuiltInTools/dotnet-watch/dotnet-watch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
<RuntimeIdentifier />
</PropertyGroup>

<ItemGroup>
<Compile Include="..\..\Common\EnvironmentVariableNames.cs" Link="Common\EnvironmentVariableNames.cs" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="**\*.resx" GenerateSource="true" />
<Content Include="DotNetWatch.targets" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
Expand Down
44 changes: 8 additions & 36 deletions src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Runtime.InteropServices;
using Microsoft.Build.Execution;
using Microsoft.Build.Exceptions;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Tools.Run.LaunchSettings;
using Microsoft.DotNet.CommandFactory;
Expand All @@ -26,7 +27,6 @@ public partial class RunCommand
public bool Interactive { get; private set; }
public IEnumerable<string> RestoreArgs { get; private set; }

private Version Version6_0 = new Version(6, 0);
private bool ShouldBuild => !NoBuild;
private bool HasQuietVerbosity =>
RestoreArgs.All(arg => !arg.StartsWith("-verbosity:", StringComparison.Ordinal) ||
Expand Down Expand Up @@ -261,21 +261,14 @@ private ICommand GetTargetCommand()
var command = CommandFactoryUsingResolver.Create(commandSpec)
.WorkingDirectory(runWorkingDirectory);

if (((TryGetTargetArchitecture(project.GetPropertyValue("RuntimeIdentifier"), out var targetArchitecture) ||
TryGetTargetArchitecture(project.GetPropertyValue("DefaultAppHostRuntimeIdentifier"), out targetArchitecture)) &&
targetArchitecture == RuntimeInformation.ProcessArchitecture) || targetArchitecture == null)
{
var rootVariableName = Environment.Is64BitProcess ? "DOTNET_ROOT" : "DOTNET_ROOT(x86)";
string targetFrameworkVersion = project.GetPropertyValue("TargetFrameworkVersion");
if (!string.IsNullOrEmpty(targetFrameworkVersion) && Version.Parse(targetFrameworkVersion.AsSpan(1)) >= Version6_0)
{
rootVariableName = $"DOTNET_ROOT_{RuntimeInformation.ProcessArchitecture.ToString().ToUpperInvariant()}";
}
var rootVariableName = EnvironmentVariableNames.TryGetDotNetRootVariableName(
project.GetPropertyValue("RuntimeIdentifier"),
project.GetPropertyValue("DefaultAppHostRuntimeIdentifier"),
project.GetPropertyValue("TargetFrameworkVersion"));

if (Environment.GetEnvironmentVariable(rootVariableName) == null)
{
command.EnvironmentVariable(rootVariableName, Path.GetDirectoryName(new Muxer().MuxerPath));
}
if (rootVariableName != null && Environment.GetEnvironmentVariable(rootVariableName) == null)
{
command.EnvironmentVariable(rootVariableName, Path.GetDirectoryName(new Muxer().MuxerPath));
}

return command;
Expand Down Expand Up @@ -329,26 +322,5 @@ private static string FindSingleProjectInDirectory(string directory)

return projectFiles[0];
}

private static bool TryGetTargetArchitecture(string runtimeIdentifier, out Architecture? targetArchitecture)
{
targetArchitecture = null;
int separator = runtimeIdentifier.LastIndexOf("-", StringComparison.InvariantCulture);
if (separator < 0)
{
return false;
}

targetArchitecture = runtimeIdentifier.Substring(separator + 1).ToLowerInvariant() switch
{
"arm" => Architecture.Arm,
"arm64" => Architecture.Arm64,
"x64" => Architecture.X64,
"x86" => Architecture.X86,
_ => null
};

return targetArchitecture != null;
}
}
}
59 changes: 59 additions & 0 deletions src/Common/EnvironmentVariableNames.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#pragma warning disable IDE0240 // Nullable directive is redundant (when file is included to a project that already enables nullable
#nullable enable

using System.Runtime.InteropServices;
using System;

namespace Microsoft.DotNet.Cli
{
static class EnvironmentVariableNames
Expand All @@ -14,5 +20,58 @@ static class EnvironmentVariableNames
public static readonly string TELEMETRY_OPTOUT = "DOTNET_CLI_TELEMETRY_OPTOUT";
public static readonly string ENABLE_PUBLISH_RELEASE_FOR_SOLUTIONS = "DOTNET_CLI_ENABLE_PUBLISH_RELEASE_FOR_SOLUTIONS";
public static readonly string ENABLE_PACK_RELEASE_FOR_SOLUTIONS = "DOTNET_CLI_ENABLE_PACK_RELEASE_FOR_SOLUTIONS";
public static readonly string DOTNET_ROOT = "DOTNET_ROOT";

#if NET7_0_OR_GREATER
private static readonly Version s_version6_0 = new(6, 0);

public static string? TryGetDotNetRootVariableName(string runtimeIdentifier, string defaultAppHostRuntimeIdentifier, string targetFrameworkVersion)
=> TryGetDotNetRootVariableName(runtimeIdentifier, defaultAppHostRuntimeIdentifier, TryParseTargetFrameworkVersion(targetFrameworkVersion));

public static string? TryGetDotNetRootVariableName(string runtimeIdentifier, string defaultAppHostRuntimeIdentifier, Version? targetFrameworkVersion)
=> TryGetDotNetRootVariableNameImpl(runtimeIdentifier, defaultAppHostRuntimeIdentifier, targetFrameworkVersion, RuntimeInformation.ProcessArchitecture, Environment.Is64BitProcess);

internal static string? TryGetDotNetRootVariableNameImpl(string runtimeIdentifier, string defaultAppHostRuntimeIdentifier, Version? targetFrameworkVersion, Architecture currentArchitecture, bool is64bit)
{
// If the app targets the same architecture as SDK is running on or an unknown architecture, set DOTNET_ROOT, DOTNET_ROOT(x86) for 32-bit, DOTNET_ROOT_arch for TFM 6+.
// If the app targets different architecture from the SDK, do not set DOTNET_ROOT.

if (!TryParseArchitecture(runtimeIdentifier, out var targetArchitecture) && !TryParseArchitecture(defaultAppHostRuntimeIdentifier, out targetArchitecture) ||
targetArchitecture == currentArchitecture)
{
var suffix = targetFrameworkVersion != null && targetFrameworkVersion >= s_version6_0 ?
$"_{currentArchitecture.ToString().ToUpperInvariant()}" :
is64bit ? "" : "(x86)";

return DOTNET_ROOT + suffix;
}

return null;
}

internal static bool TryParseArchitecture(string runtimeIdentifier, out Architecture architecture)
{
// RID is [os].[version]-[architecture]-[additional qualifiers]
// See https://learn.microsoft.com/en-us/dotnet/core/rid-catalog

int archStart = runtimeIdentifier.IndexOf('-') + 1;
if (archStart <= 0)
{
architecture = default;
return false;
}

int archEnd = runtimeIdentifier.IndexOf('-', archStart);
var span = runtimeIdentifier.AsSpan(archStart, (archEnd > 0 ? archEnd : runtimeIdentifier.Length) - archStart);

return Enum.TryParse(span, ignoreCase: true, out architecture);
}

public static Version? TryParseTargetFrameworkVersion(string targetFrameworkVersion)
{
// TargetFrameworkVersion appears as "vX.Y" in msbuild. Ignore the leading 'v'.
return !string.IsNullOrEmpty(targetFrameworkVersion) && Version.TryParse(targetFrameworkVersion.Substring(1), out var version) ? version : null;
}
#endif
}
}
2 changes: 1 addition & 1 deletion src/Tests/dotnet-watch.Tests/DotNetWatcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public async Task Run_WithHotReloadEnabled_ReadsLaunchSettings_WhenUsingProjectO
await app.Process.GetOutputLineAsyncWithConsoleHistoryAsync("Environment: Development");
}

[CoreMSBuildOnlyFact]
[CoreMSBuildOnlyFact(Skip = "https://github.com/dotnet/sdk/issues/29047")]
public async Task Run_WithHotReloadEnabled_DoesNotReadConsoleIn_InNonInteractiveMode()
{
var testAsset = _testAssetsManager.CopyTestAsset("WatchAppWithLaunchSettings")
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/dotnet-watch.Tests/NoDepsAppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task RestartProcessOnFileChange()
Assert.NotEqual(processIdentifier, processIdentifier2);
}

[Fact]
[Fact(Skip = "https://github.com/dotnet/sdk/issues/29046")]
public async Task RestartProcessThatTerminatesAfterFileChange()
{
var testAsset = _testAssetsManager.CopyTestAsset(AppName)
Expand Down
97 changes: 97 additions & 0 deletions src/Tests/dotnet.Tests/EnvironmentVariableNamesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System;
using System.Runtime.InteropServices;
using Microsoft.DotNet.Cli;
using Xunit;

namespace dotnet.Tests;

public class EnvironmentVariableNamesTests
{
[Theory]
[InlineData("os.1.2-x86", Architecture.X86)]
[InlineData("os.1.2-x64", Architecture.X64)]
[InlineData("os.1.2-arm", Architecture.Arm)]
[InlineData("os.1.2-arm64", Architecture.Arm64)]
[InlineData("os.1.2-wasm", Architecture.Wasm)]
[InlineData("os.1.2-s390x", Architecture.S390x)]
[InlineData("os.1.2-loongarch64", Architecture.LoongArch64)]
[InlineData("os.1.2-armv6", Architecture.Armv6)]
[InlineData("os.1.2-ppc64le", Architecture.Ppc64le)]
[InlineData("os.1.2-lOOngaRch64", Architecture.LoongArch64)] // case-insensitive
[InlineData("os-x86", Architecture.X86)]
[InlineData("-x86", Architecture.X86)]
[InlineData("-x86-", Architecture.X86)]
public static void TryParseArchitecture(string rid, Architecture expected)
{
Assert.True(EnvironmentVariableNames.TryParseArchitecture(rid, out var actual));
Assert.Equal(expected, actual);

Assert.True(EnvironmentVariableNames.TryParseArchitecture(rid + "-xyz", out actual));
Assert.Equal(expected, actual);
}

[Theory]
[InlineData("")]
[InlineData("-")]
[InlineData("--")]
[InlineData("---")]
[InlineData("x86")]
[InlineData("os")]
[InlineData("os.")]
[InlineData("os.1")]
[InlineData("os.1.2")]
[InlineData("os.1.2-")]
[InlineData("os.1.2--")]
[InlineData("os.1.2-unknown")]
[InlineData("os.1.2-unknown-")]
[InlineData("os.1.2-unknown-x")]
[InlineData("os.1.2-armel")] // currently not defined
public static void TryParseArchitecture_Invalid(string rid)
{
Assert.False(EnvironmentVariableNames.TryParseArchitecture(rid, out _));
}

[Theory]
[InlineData("os-x86", null, Architecture.X86, true, "DOTNET_ROOT")]
[InlineData("os-x86", null, Architecture.X86, false, "DOTNET_ROOT(x86)")]
[InlineData("os-x86", "v5.0", Architecture.X86, true, "DOTNET_ROOT")]
[InlineData("os-x86", "v5.0", Architecture.X86, false, "DOTNET_ROOT(x86)")]
[InlineData("os-x86", "v6.0", Architecture.X86, true, "DOTNET_ROOT_X86")]
[InlineData("os-x86", "v6.0", Architecture.X86, false, "DOTNET_ROOT_X86")]
[InlineData("os-x64", "v5.0", Architecture.X64, true, "DOTNET_ROOT")]
[InlineData("os-x64", "v6.0", Architecture.X64, true, "DOTNET_ROOT_X64")]
[InlineData("os-arm64", "v6.0", Architecture.Arm64, true, "DOTNET_ROOT_ARM64")]
[InlineData("os-armv6", "v6.0", Architecture.Armv6, true, "DOTNET_ROOT_ARMV6")]
[InlineData("os-armv6", "v6.0", Architecture.Arm64, true, null)]
[InlineData("os-x64", "v6.0", Architecture.X86, false, null)]
public static void TryGetDotNetRootVariableName_KnownArchitecture(string rid, string frameworkVersion, Architecture currentArchitecture, bool is64bit, string expected)
{
var parsedVersion = EnvironmentVariableNames.TryParseTargetFrameworkVersion(frameworkVersion);
Assert.Equal(expected, EnvironmentVariableNames.TryGetDotNetRootVariableNameImpl(rid, "os-unknown", parsedVersion, currentArchitecture, is64bit));
Assert.Equal(expected, EnvironmentVariableNames.TryGetDotNetRootVariableNameImpl(rid, "os-armv6", parsedVersion, currentArchitecture, is64bit));
Assert.Equal(expected, EnvironmentVariableNames.TryGetDotNetRootVariableNameImpl("os-unknown", rid, parsedVersion, currentArchitecture, is64bit));
}

[Theory]
[InlineData(null, Architecture.X86, true, "DOTNET_ROOT")]
[InlineData(null, Architecture.X86, false, "DOTNET_ROOT(x86)")]
[InlineData("v5.0", Architecture.X86, true, "DOTNET_ROOT")]
[InlineData("v5.0", Architecture.X86, false, "DOTNET_ROOT(x86)")]
[InlineData("v6.0", Architecture.X86, true, "DOTNET_ROOT_X86")]
[InlineData("v6.0", Architecture.X86, false, "DOTNET_ROOT_X86")]
[InlineData("v5.0", Architecture.X64, true, "DOTNET_ROOT")]
[InlineData("v6.0", Architecture.X64, true, "DOTNET_ROOT_X64")]
[InlineData("v6.0", Architecture.Arm64, true, "DOTNET_ROOT_ARM64")]
[InlineData("v6.0", Architecture.Armv6, true, "DOTNET_ROOT_ARMV6")]
[InlineData("v6.0", Architecture.Wasm, true, "DOTNET_ROOT_WASM")]
[InlineData("v6.0", Architecture.Wasm, false, "DOTNET_ROOT_WASM")]
public static void TryGetDotNetRootVariableName_UnknownArchitecture(string frameworkVersion, Architecture currentArchitecture, bool is64bit, string expected)
{
var parsedVersion = EnvironmentVariableNames.TryParseTargetFrameworkVersion(frameworkVersion);
Assert.Equal(expected, EnvironmentVariableNames.TryGetDotNetRootVariableNameImpl("os-unknown", "os-unknown", parsedVersion, currentArchitecture, is64bit));
}
}