Skip to content

Commit 97de5c5

Browse files
authored
Add support for multi-arch install locations (#53763)
* Add support for multiple architectures inside install_locations * Add install_location tests * Fallback to DOTNET_ROOT on win32
1 parent d783a8c commit 97de5c5

18 files changed

+560
-151
lines changed

src/installer/tests/HostActivation.Tests/CommandExtensions.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ public static Command EnableTracingAndCaptureOutputs(this Command command)
3535
.CaptureStdErr();
3636
}
3737

38-
public static Command DotNetRoot(this Command command, string dotNetRoot)
38+
public static Command DotNetRoot(this Command command, string dotNetRoot, string architecture = null)
3939
{
40+
if (!string.IsNullOrEmpty(architecture))
41+
return command.EnvironmentVariable($"DOTNET_ROOT_{architecture.ToUpper()}", dotNetRoot);
42+
4043
return command
4144
.EnvironmentVariable("DOTNET_ROOT", dotNetRoot)
4245
.EnvironmentVariable("DOTNET_ROOT(x86)", dotNetRoot);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
5+
using System;
6+
using System.Runtime.InteropServices;
7+
using FluentAssertions;
8+
using Microsoft.DotNet.Cli.Build.Framework;
9+
using Microsoft.DotNet.CoreSetup.Test;
10+
using Xunit;
11+
12+
namespace HostActivation.Tests
13+
{
14+
internal static class InstallLocationCommandResultExtensions
15+
{
16+
private static bool IsRunningInWoW64(string rid) => OperatingSystem.IsWindows() && Environment.Is64BitOperatingSystem && rid.Equals("win-x86");
17+
18+
public static AndConstraint<CommandResultAssertions> HaveUsedDotNetRootInstallLocation(this CommandResultAssertions assertion, string installLocation, string rid)
19+
{
20+
return assertion.HaveUsedDotNetRootInstallLocation(installLocation, rid, null);
21+
}
22+
23+
public static AndConstraint<CommandResultAssertions> HaveUsedDotNetRootInstallLocation(this CommandResultAssertions assertion,
24+
string installLocation,
25+
string rid,
26+
string arch)
27+
{
28+
// 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.
29+
if (string.IsNullOrEmpty(arch))
30+
Assert.NotNull(rid);
31+
32+
string expectedEnvironmentVariable = !string.IsNullOrEmpty(arch) ? $"DOTNET_ROOT_{arch.ToUpper()}" :
33+
IsRunningInWoW64(rid) ? "DOTNET_ROOT(x86)" : "DOTNET_ROOT";
34+
35+
return assertion.HaveStdErrContaining($"Using environment variable {expectedEnvironmentVariable}=[{installLocation}] as runtime location.");
36+
}
37+
38+
public static AndConstraint<CommandResultAssertions> HaveUsedConfigFileInstallLocation(this CommandResultAssertions assertion, string installLocation)
39+
{
40+
return assertion.HaveStdErrContaining($"Using install location '{installLocation}'.");
41+
}
42+
43+
public static AndConstraint<CommandResultAssertions> HaveUsedGlobalInstallLocation(this CommandResultAssertions assertion, string installLocation)
44+
{
45+
return assertion.HaveStdErrContaining($"Using global installation location [{installLocation}]");
46+
}
47+
48+
public static AndConstraint<CommandResultAssertions> HaveFoundDefaultInstallLocationInConfigFile(this CommandResultAssertions assertion, string installLocation)
49+
{
50+
return assertion.HaveStdErrContaining($"Found install location path '{installLocation}'.");
51+
}
52+
53+
public static AndConstraint<CommandResultAssertions> HaveFoundArchSpecificInstallLocationInConfigFile(this CommandResultAssertions assertion, string installLocation, string arch)
54+
{
55+
return assertion.HaveStdErrContaining($"Found architecture-specific install location path: '{installLocation}' ('{arch}').");
56+
}
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.IO;
6+
using System.Runtime.InteropServices;
7+
using Microsoft.DotNet.Cli.Build.Framework;
8+
using Microsoft.DotNet.CoreSetup.Test;
9+
using Microsoft.DotNet.CoreSetup.Test.HostActivation;
10+
using Xunit;
11+
12+
namespace HostActivation.Tests
13+
{
14+
public class MultiArchInstallLocation : IClassFixture<MultiArchInstallLocation.SharedTestState>
15+
{
16+
private SharedTestState sharedTestState;
17+
18+
public MultiArchInstallLocation(SharedTestState fixture)
19+
{
20+
sharedTestState = fixture;
21+
}
22+
23+
[Fact]
24+
public void EnvironmentVariable_CurrentArchitectureIsUsedIfEnvVarSet()
25+
{
26+
var fixture = sharedTestState.PortableAppFixture
27+
.Copy();
28+
29+
var appExe = fixture.TestProject.AppExe;
30+
var arch = fixture.RepoDirProvider.BuildArchitecture.ToUpper();
31+
Command.Create(appExe)
32+
.EnableTracingAndCaptureOutputs()
33+
.DotNetRoot(fixture.BuiltDotnet.BinPath, arch)
34+
.Execute()
35+
.Should().Pass()
36+
.And.HaveUsedDotNetRootInstallLocation(fixture.BuiltDotnet.BinPath, fixture.CurrentRid, arch);
37+
}
38+
39+
[Fact]
40+
public void EnvironmentVariable_IfNoArchSpecificEnvVarIsFoundDotnetRootIsUsed()
41+
{
42+
var fixture = sharedTestState.PortableAppFixture
43+
.Copy();
44+
45+
var appExe = fixture.TestProject.AppExe;
46+
var arch = fixture.RepoDirProvider.BuildArchitecture.ToUpper();
47+
Command.Create(appExe)
48+
.EnableTracingAndCaptureOutputs()
49+
.DotNetRoot(fixture.BuiltDotnet.BinPath)
50+
.Execute()
51+
.Should().Pass()
52+
.And.HaveUsedDotNetRootInstallLocation(fixture.BuiltDotnet.BinPath, fixture.CurrentRid);
53+
}
54+
55+
[Fact]
56+
public void EnvironmentVariable_ArchSpecificDotnetRootIsUsedOverDotnetRoot()
57+
{
58+
var fixture = sharedTestState.PortableAppFixture
59+
.Copy();
60+
61+
var appExe = fixture.TestProject.AppExe;
62+
var arch = fixture.RepoDirProvider.BuildArchitecture.ToUpper();
63+
var dotnet = fixture.BuiltDotnet.BinPath;
64+
Command.Create(appExe)
65+
.EnableTracingAndCaptureOutputs()
66+
.DotNetRoot("non_existent_path")
67+
.DotNetRoot(dotnet, arch)
68+
.Execute()
69+
.Should().Pass()
70+
.And.HaveUsedDotNetRootInstallLocation(dotnet, fixture.CurrentRid, arch)
71+
.And.NotHaveStdErrContaining("Using environment variable DOTNET_ROOT=");
72+
}
73+
74+
[Fact]
75+
[SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")]
76+
public void InstallLocationFile_ArchSpecificLocationIsPickedFirst()
77+
{
78+
var fixture = sharedTestState.PortableAppFixture
79+
.Copy();
80+
81+
var appExe = fixture.TestProject.AppExe;
82+
var arch1 = "someArch";
83+
var path1 = "x/y/z";
84+
var arch2 = fixture.RepoDirProvider.BuildArchitecture;
85+
var path2 = "a/b/c";
86+
87+
using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(appExe))
88+
{
89+
registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] {
90+
(string.Empty, path1),
91+
(arch1, path1),
92+
(arch2, path2)
93+
});
94+
95+
Command.Create(appExe)
96+
.EnableTracingAndCaptureOutputs()
97+
.ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride)
98+
.DotNetRoot(null)
99+
.Execute()
100+
.Should().HaveFoundDefaultInstallLocationInConfigFile(path1)
101+
.And.HaveFoundArchSpecificInstallLocationInConfigFile(path1, arch1)
102+
.And.HaveFoundArchSpecificInstallLocationInConfigFile(path2, arch2)
103+
.And.HaveUsedGlobalInstallLocation(path2);
104+
}
105+
}
106+
107+
[Fact]
108+
[SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")]
109+
public void InstallLocationFile_OnlyFirstLineMayNotSpecifyArchitecture()
110+
{
111+
var fixture = sharedTestState.PortableAppFixture
112+
.Copy();
113+
114+
var appExe = fixture.TestProject.AppExe;
115+
using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(appExe))
116+
{
117+
registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] {
118+
(string.Empty, "a/b/c"),
119+
(string.Empty, "x/y/z"),
120+
});
121+
Command.Create(appExe)
122+
.EnableTracingAndCaptureOutputs()
123+
.ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride)
124+
.DotNetRoot(null)
125+
.Execute()
126+
.Should().HaveFoundDefaultInstallLocationInConfigFile("a/b/c")
127+
.And.HaveStdErrContaining($"Only the first line in '{registeredInstallLocationOverride.PathValueOverride}' may not have an architecture prefix.")
128+
.And.HaveUsedConfigFileInstallLocation("a/b/c");
129+
}
130+
}
131+
132+
[Fact]
133+
[SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")]
134+
public void InstallLocationFile_ReallyLongInstallPathIsParsedCorrectly()
135+
{
136+
var fixture = sharedTestState.PortableAppFixture
137+
.Copy();
138+
139+
var appExe = fixture.TestProject.AppExe;
140+
using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(appExe))
141+
{
142+
var reallyLongPath =
143+
"reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
144+
"reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
145+
"reallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpath";
146+
registeredInstallLocationOverride.SetInstallLocation((string.Empty, reallyLongPath));
147+
148+
Command.Create(appExe)
149+
.EnableTracingAndCaptureOutputs()
150+
.ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride)
151+
.DotNetRoot(null)
152+
.Execute()
153+
.Should().HaveFoundDefaultInstallLocationInConfigFile(reallyLongPath)
154+
.And.HaveUsedConfigFileInstallLocation(reallyLongPath);
155+
}
156+
}
157+
158+
public class SharedTestState : IDisposable
159+
{
160+
public string BaseDirectory { get; }
161+
public TestProjectFixture PortableAppFixture { get; }
162+
public RepoDirectoriesProvider RepoDirectories { get; }
163+
public string InstallLocation { get; }
164+
165+
public SharedTestState()
166+
{
167+
RepoDirectories = new RepoDirectoriesProvider();
168+
var fixture = new TestProjectFixture("PortableApp", RepoDirectories);
169+
fixture
170+
.EnsureRestored()
171+
// App Host generation is turned off by default on macOS
172+
.PublishProject(extraArgs: "/p:UseAppHost=true");
173+
174+
PortableAppFixture = fixture;
175+
BaseDirectory = Path.GetDirectoryName(PortableAppFixture.SdkDotnet.GreatestVersionHostFxrFilePath);
176+
}
177+
178+
public void Dispose()
179+
{
180+
PortableAppFixture.Dispose();
181+
}
182+
}
183+
}
184+
}

src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ public void SdkMultilevelLookup_RegistryAccess()
501501

502502
using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(DotNet.GreatestVersionHostFxrFilePath))
503503
{
504-
registeredInstallLocationOverride.SetInstallLocation(_regDir, RepoDirectories.BuildArchitecture);
504+
registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] { (RepoDirectories.BuildArchitecture, _regDir) });
505505

506506
// Add SDK versions
507507
AddAvailableSdkVersions(_regSdkBaseDir, "9999.0.4");

0 commit comments

Comments
 (0)