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

Add support for multi-arch install locations #53763

Merged
merged 23 commits into from
Jun 23, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ public static Command EnableTracingAndCaptureOutputs(this Command command)
.CaptureStdErr();
}

public static Command DotNetRoot(this Command command, string dotNetRoot)
public static Command DotNetRoot(this Command command, string dotNetRoot, string architecture = null)
{
if (!string.IsNullOrEmpty(architecture))
return command.EnvironmentVariable($"DOTNET_ROOT_{architecture.ToUpper()}", dotNetRoot);

return command
.EnvironmentVariable("DOTNET_ROOT", dotNetRoot)
.EnvironmentVariable("DOTNET_ROOT(x86)", dotNetRoot);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System;
using System.Runtime.InteropServices;
using FluentAssertions;
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.DotNet.CoreSetup.Test;
using Xunit;

namespace HostActivation.Tests
{
internal static class InstallLocationCommandResultExtensions
{
private static bool IsRunningInWoW64(string rid) => OperatingSystem.IsWindows() && Environment.Is64BitOperatingSystem && rid.Equals("win-x86");

public static AndConstraint<CommandResultAssertions> HaveUsedDotNetRootInstallLocation(this CommandResultAssertions assertion, string installLocation, string rid)
{
return assertion.HaveUsedDotNetRootInstallLocation(installLocation, rid, null);
}

public static AndConstraint<CommandResultAssertions> HaveUsedDotNetRootInstallLocation(this CommandResultAssertions assertion,
string installLocation,
string rid,
string arch)
{
// If no arch is passed and we are on Windows, we need the used RID for determining whether or not we are running on WoW64.
if (string.IsNullOrEmpty(arch))
Assert.NotNull(rid);

string expectedEnvironmentVariable = !string.IsNullOrEmpty(arch) ? $"DOTNET_ROOT_{arch.ToUpper()}" :
IsRunningInWoW64(rid) ? "DOTNET_ROOT(x86)" : "DOTNET_ROOT";

return assertion.HaveStdErrContaining($"Using environment variable {expectedEnvironmentVariable}=[{installLocation}] as runtime location.");
}

public static AndConstraint<CommandResultAssertions> HaveUsedConfigFileInstallLocation(this CommandResultAssertions assertion, string installLocation)
{
return assertion.HaveStdErrContaining($"Using install location '{installLocation}'.");
}

public static AndConstraint<CommandResultAssertions> HaveUsedGlobalInstallLocation(this CommandResultAssertions assertion, string installLocation)
{
return assertion.HaveStdErrContaining($"Using global installation location [{installLocation}]");
}

public static AndConstraint<CommandResultAssertions> HaveFoundDefaultInstallLocationInConfigFile(this CommandResultAssertions assertion, string installLocation)
{
return assertion.HaveStdErrContaining($"Found install location path '{installLocation}'.");
}

public static AndConstraint<CommandResultAssertions> HaveFoundArchSpecificInstallLocationInConfigFile(this CommandResultAssertions assertion, string installLocation, string arch)
{
return assertion.HaveStdErrContaining($"Found architecture-specific install location path: '{installLocation}' ('{arch}').");
}
}
}
184 changes: 184 additions & 0 deletions src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.DotNet.CoreSetup.Test;
using Microsoft.DotNet.CoreSetup.Test.HostActivation;
using Xunit;

namespace HostActivation.Tests
{
public class MultiArchInstallLocation : IClassFixture<MultiArchInstallLocation.SharedTestState>
{
private SharedTestState sharedTestState;

public MultiArchInstallLocation(SharedTestState fixture)
{
sharedTestState = fixture;
}

[Fact]
public void EnvironmentVariable_CurrentArchitectureIsUsedIfEnvVarSet()
{
var fixture = sharedTestState.PortableAppFixture
.Copy();

var appExe = fixture.TestProject.AppExe;
var arch = fixture.RepoDirProvider.BuildArchitecture.ToUpper();
Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.DotNetRoot(fixture.BuiltDotnet.BinPath, arch)
.Execute()
.Should().Pass()
.And.HaveUsedDotNetRootInstallLocation(fixture.BuiltDotnet.BinPath, fixture.CurrentRid, arch);
}

[Fact]
public void EnvironmentVariable_IfNoArchSpecificEnvVarIsFoundDotnetRootIsUsed()
{
var fixture = sharedTestState.PortableAppFixture
.Copy();

var appExe = fixture.TestProject.AppExe;
var arch = fixture.RepoDirProvider.BuildArchitecture.ToUpper();
Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.DotNetRoot(fixture.BuiltDotnet.BinPath)
.Execute()
.Should().Pass()
.And.HaveUsedDotNetRootInstallLocation(fixture.BuiltDotnet.BinPath, fixture.CurrentRid);
}

[Fact]
public void EnvironmentVariable_ArchSpecificDotnetRootIsUsedOverDotnetRoot()
{
var fixture = sharedTestState.PortableAppFixture
.Copy();

var appExe = fixture.TestProject.AppExe;
var arch = fixture.RepoDirProvider.BuildArchitecture.ToUpper();
var dotnet = fixture.BuiltDotnet.BinPath;
Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.DotNetRoot("non_existent_path")
.DotNetRoot(dotnet, arch)
.Execute()
.Should().Pass()
.And.HaveUsedDotNetRootInstallLocation(dotnet, fixture.CurrentRid, arch)
.And.NotHaveStdErrContaining("Using environment variable DOTNET_ROOT=");
}

[Fact]
[SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")]
public void InstallLocationFile_ArchSpecificLocationIsPickedFirst()
{
var fixture = sharedTestState.PortableAppFixture
.Copy();

var appExe = fixture.TestProject.AppExe;
var arch1 = "someArch";
var path1 = "x/y/z";
var arch2 = fixture.RepoDirProvider.BuildArchitecture;
var path2 = "a/b/c";

using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(appExe))
{
registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] {
(string.Empty, path1),
(arch1, path1),
(arch2, path2)
});

Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride)
.DotNetRoot(null)
.Execute()
.Should().HaveFoundDefaultInstallLocationInConfigFile(path1)
.And.HaveFoundArchSpecificInstallLocationInConfigFile(path1, arch1)
.And.HaveFoundArchSpecificInstallLocationInConfigFile(path2, arch2)
.And.HaveUsedGlobalInstallLocation(path2);
}
}

[Fact]
[SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")]
public void InstallLocationFile_OnlyFirstLineMayNotSpecifyArchitecture()
{
var fixture = sharedTestState.PortableAppFixture
.Copy();

var appExe = fixture.TestProject.AppExe;
using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(appExe))
{
registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] {
(string.Empty, "a/b/c"),
(string.Empty, "x/y/z"),
});
Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride)
.DotNetRoot(null)
.Execute()
.Should().HaveFoundDefaultInstallLocationInConfigFile("a/b/c")
.And.HaveStdErrContaining($"Only the first line in '{registeredInstallLocationOverride.PathValueOverride}' may not have an architecture prefix.")
.And.HaveUsedConfigFileInstallLocation("a/b/c");
}
}

[Fact]
[SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")]
public void InstallLocationFile_ReallyLongInstallPathIsParsedCorrectly()
{
var fixture = sharedTestState.PortableAppFixture
.Copy();

var appExe = fixture.TestProject.AppExe;
using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(appExe))
{
var reallyLongPath =
"reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
"reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
"reallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpath";
registeredInstallLocationOverride.SetInstallLocation((string.Empty, reallyLongPath));

Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride)
.DotNetRoot(null)
.Execute()
.Should().HaveFoundDefaultInstallLocationInConfigFile(reallyLongPath)
.And.HaveUsedConfigFileInstallLocation(reallyLongPath);
}
}

public class SharedTestState : IDisposable
{
public string BaseDirectory { get; }
public TestProjectFixture PortableAppFixture { get; }
public RepoDirectoriesProvider RepoDirectories { get; }
public string InstallLocation { get; }

public SharedTestState()
{
RepoDirectories = new RepoDirectoriesProvider();
var fixture = new TestProjectFixture("PortableApp", RepoDirectories);
fixture
.EnsureRestored()
// App Host generation is turned off by default on macOS
.PublishProject(extraArgs: "/p:UseAppHost=true");

PortableAppFixture = fixture;
BaseDirectory = Path.GetDirectoryName(PortableAppFixture.SdkDotnet.GreatestVersionHostFxrFilePath);
}

public void Dispose()
{
PortableAppFixture.Dispose();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ public void SdkMultilevelLookup_RegistryAccess()

using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(DotNet.GreatestVersionHostFxrFilePath))
{
registeredInstallLocationOverride.SetInstallLocation(_regDir, RepoDirectories.BuildArchitecture);
registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] { (RepoDirectories.BuildArchitecture, _regDir) });

// Add SDK versions
AddAvailableSdkVersions(_regSdkBaseDir, "9999.0.4");
Expand Down
Loading