Skip to content

Commit

Permalink
Add support for multi-arch install locations (#53763)
Browse files Browse the repository at this point in the history
* Add support for multiple architectures inside install_locations

* Add install_location tests

* Fallback to DOTNET_ROOT on win32
  • Loading branch information
mateoatr authored Jun 23, 2021
1 parent d783a8c commit 97de5c5
Show file tree
Hide file tree
Showing 18 changed files with 560 additions and 151 deletions.
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

0 comments on commit 97de5c5

Please sign in to comment.