diff --git a/src/installer/tests/HostActivation.Tests/CommandExtensions.cs b/src/installer/tests/HostActivation.Tests/CommandExtensions.cs index 054e6c39d1436..d8460c1368bdb 100644 --- a/src/installer/tests/HostActivation.Tests/CommandExtensions.cs +++ b/src/installer/tests/HostActivation.Tests/CommandExtensions.cs @@ -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); diff --git a/src/installer/tests/HostActivation.Tests/InstallLocationCommandResultExtensions.cs b/src/installer/tests/HostActivation.Tests/InstallLocationCommandResultExtensions.cs new file mode 100644 index 0000000000000..c85b7d3e56cc5 --- /dev/null +++ b/src/installer/tests/HostActivation.Tests/InstallLocationCommandResultExtensions.cs @@ -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 HaveUsedDotNetRootInstallLocation(this CommandResultAssertions assertion, string installLocation, string rid) + { + return assertion.HaveUsedDotNetRootInstallLocation(installLocation, rid, null); + } + + public static AndConstraint 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 HaveUsedConfigFileInstallLocation(this CommandResultAssertions assertion, string installLocation) + { + return assertion.HaveStdErrContaining($"Using install location '{installLocation}'."); + } + + public static AndConstraint HaveUsedGlobalInstallLocation(this CommandResultAssertions assertion, string installLocation) + { + return assertion.HaveStdErrContaining($"Using global installation location [{installLocation}]"); + } + + public static AndConstraint HaveFoundDefaultInstallLocationInConfigFile(this CommandResultAssertions assertion, string installLocation) + { + return assertion.HaveStdErrContaining($"Found install location path '{installLocation}'."); + } + + public static AndConstraint HaveFoundArchSpecificInstallLocationInConfigFile(this CommandResultAssertions assertion, string installLocation, string arch) + { + return assertion.HaveStdErrContaining($"Found architecture-specific install location path: '{installLocation}' ('{arch}')."); + } + } +} diff --git a/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs b/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs new file mode 100644 index 0000000000000..c1b00cafd4e6a --- /dev/null +++ b/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs @@ -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 + { + 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(); + } + } + } +} diff --git a/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs b/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs index 97b3387604b62..76d11337c03ab 100644 --- a/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs +++ b/src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs @@ -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"); diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs index 0f18161b02682..0af2ce3477412 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs @@ -116,7 +116,7 @@ public void GetHostFxrPath_GlobalInstallation(bool explicitLoad, bool useAssembl { if (useRegisteredLocation) { - registeredInstallLocationOverride.SetInstallLocation(installLocation, sharedState.RepoDirectories.BuildArchitecture); + registeredInstallLocationOverride.SetInstallLocation((sharedState.RepoDirectories.BuildArchitecture, installLocation)); } result = Command.Create(sharedState.NativeHostPath, $"{GetHostFxrPath} {explicitLoad} {(useAssemblyPath ? sharedState.TestAssemblyPath : string.Empty)}") @@ -180,21 +180,32 @@ public void GetHostFxrPath_HostFxrAlreadyLoaded() [Theory] [SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")] - [InlineData("{0}", true)] - [InlineData("{0}\n", true)] - [InlineData("{0}\nSome other text", true)] - [InlineData("", false)] - [InlineData("\n{0}", false)] - [InlineData(" {0}", false)] - [InlineData("{0} \n", false)] - [InlineData("{0} ", false)] - public void GetHostFxrPath_InstallLocationFile(string value, bool shouldPass) + [InlineData("{0}", false, true)] + [InlineData("{0}\n", false, true)] + [InlineData("{0}\nSome other text", false, true)] + [InlineData("", false, false)] + [InlineData("\n{0}", false, false)] + [InlineData(" {0}", false, false)] + [InlineData("{0} \n", false, false)] + [InlineData("{0} ", false, false)] + [InlineData("{0}", true, true)] + [InlineData("{0}\n", true, true)] + [InlineData("{0}\nSome other text", true, true)] + [InlineData("", true, false)] + [InlineData("\n{0}", true, false)] + [InlineData(" {0}", true, false)] + [InlineData("{0} \n", true, false)] + [InlineData("{0} ", true, false)] + public void GetHostFxrPath_InstallLocationFile(string value, bool shouldUseArchSpecificInstallLocation, bool shouldPass) { string installLocation = Path.Combine(sharedState.ValidInstallRoot, "dotnet"); using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(sharedState.NethostPath)) { - File.WriteAllText(registeredInstallLocationOverride.PathValueOverride, string.Format(value, installLocation)); + if (shouldUseArchSpecificInstallLocation) + registeredInstallLocationOverride.SetInstallLocation((sharedState.RepoDirectories.BuildArchitecture, string.Format(value, installLocation))); + else + registeredInstallLocationOverride.SetInstallLocation((string.Empty, string.Format(value, installLocation))); CommandResult result = Command.Create(sharedState.NativeHostPath, GetHostFxrPath) .EnableTracingAndCaptureOutputs() @@ -223,6 +234,94 @@ public void GetHostFxrPath_InstallLocationFile(string value, bool shouldPass) } } + [Fact] + [SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")] + public void GetHostFxrPath_GlobalInstallation_HasMoreThanOneDefaultInstallationPath() + { + string installLocation = Path.Combine(sharedState.ValidInstallRoot, "dotnet"); + using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(sharedState.NethostPath)) + { + registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] { + (string.Empty, installLocation), (string.Empty, installLocation) + }); + + CommandResult result = Command.Create(sharedState.NativeHostPath, GetHostFxrPath) + .EnableTracingAndCaptureOutputs() + .ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride) + .EnvironmentVariable( // Redirect the default install location to an invalid location so that it doesn't cause the test to pass + Constants.TestOnlyEnvironmentVariables.DefaultInstallPath, + sharedState.InvalidInstallRoot) + .DotNetRoot(null) + .Execute(); + + result.Should().Pass() + .And.HaveStdErrContaining($"Looking for install_location file in '{registeredInstallLocationOverride.PathValueOverride}'.") + .And.HaveStdErrContaining($"Found install location path '{installLocation}'.") + .And.HaveStdErrContaining($"Only the first line in '{registeredInstallLocationOverride.PathValueOverride}' may not have an architecture prefix.") + .And.HaveStdErrContaining($"Using install location '{installLocation}'.") + .And.HaveStdOutContaining($"hostfxr_path: {sharedState.HostFxrPath}".ToLower()); + } + } + + [Fact] + [SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")] + public void GetHostFxrPath_GlobalInstallation_HasNoDefaultInstallationPath() + { + string installLocation = Path.Combine(sharedState.ValidInstallRoot, "dotnet"); + using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(sharedState.NethostPath)) + { + registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] { + (sharedState.RepoDirectories.BuildArchitecture, installLocation), + ("someOtherArch", $"{installLocation}/invalid") + }); + + CommandResult result = Command.Create(sharedState.NativeHostPath, GetHostFxrPath) + .EnableTracingAndCaptureOutputs() + .ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride) + .EnvironmentVariable( // Redirect the default install location to an invalid location so that it doesn't cause the test to pass + Constants.TestOnlyEnvironmentVariables.DefaultInstallPath, + sharedState.InvalidInstallRoot) + .DotNetRoot(null) + .Execute(); + + result.Should().Pass() + .And.HaveStdErrContaining($"Looking for install_location file in '{registeredInstallLocationOverride.PathValueOverride}'.") + .And.HaveStdErrContaining($"Found architecture-specific install location path: '{installLocation}' ('{sharedState.RepoDirectories.BuildArchitecture.ToLower()}').") + .And.HaveStdErrContaining($"Using install location '{installLocation}'.") + .And.HaveStdOutContaining($"hostfxr_path: {sharedState.HostFxrPath}".ToLower()); + } + } + + [Fact] + [SkipOnPlatform(TestPlatforms.Windows, "This test targets the install_location config file which is only used on Linux and macOS.")] + public void GetHostFxrPath_GlobalInstallation_ArchitectureSpecificPathIsPickedOverDefaultPath() + { + string installLocation = Path.Combine(sharedState.ValidInstallRoot, "dotnet"); + using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(sharedState.NethostPath)) + { + registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] { + (string.Empty, $"{installLocation}/a/b/c"), + (sharedState.RepoDirectories.BuildArchitecture, installLocation) + }); + + CommandResult result = Command.Create(sharedState.NativeHostPath, GetHostFxrPath) + .EnableTracingAndCaptureOutputs() + .ApplyRegisteredInstallLocationOverride(registeredInstallLocationOverride) + .EnvironmentVariable( // Redirect the default install location to an invalid location so that it doesn't cause the test to pass + Constants.TestOnlyEnvironmentVariables.DefaultInstallPath, + sharedState.InvalidInstallRoot) + .DotNetRoot(null) + .Execute(); + + result.Should().Pass() + .And.HaveStdErrContaining($"Looking for install_location file in '{registeredInstallLocationOverride.PathValueOverride}'.") + .And.HaveStdErrContaining($"Found install location path '{installLocation}/a/b/c'.") + .And.HaveStdErrContaining($"Found architecture-specific install location path: '{installLocation}' ('{sharedState.RepoDirectories.BuildArchitecture.ToLower()}').") + .And.HaveStdErrContaining($"Using install location '{installLocation}'.") + .And.HaveStdOutContaining($"hostfxr_path: {sharedState.HostFxrPath}".ToLower()); + } + } + [Fact] public void GetHostFxrPath_InvalidParameters() { diff --git a/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs b/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs index 14060b5e944bf..a2a0d963e1f09 100644 --- a/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs +++ b/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs @@ -257,7 +257,7 @@ public void AppHost_FrameworkDependent_Succeeds() Command.Create(appExe) .CaptureStdErr() .CaptureStdOut() - .DotNetRoot(builtDotnet) + .DotNetRoot(builtDotnet, sharedTestState.RepoDirectories.BuildArchitecture) .MultilevelLookup(false) .Execute() .Should().Pass() @@ -268,7 +268,7 @@ public void AppHost_FrameworkDependent_Succeeds() // Verify running from within the working directory Command.Create(appExe) .WorkingDirectory(fixture.TestProject.OutputDirectory) - .DotNetRoot(builtDotnet) + .DotNetRoot(builtDotnet, sharedTestState.RepoDirectories.BuildArchitecture) .MultilevelLookup(false) .CaptureStdErr() .CaptureStdOut() @@ -297,10 +297,10 @@ public void AppHost_FrameworkDependent_GlobalLocation_Succeeds(bool useRegistere using (var registeredInstallLocationOverride = new RegisteredInstallLocationOverride(appExe)) { - string architecture = fixture.CurrentRid.Split('-')[1]; + string architecture = fixture.RepoDirProvider.BuildArchitecture; if (useRegisteredLocation) { - registeredInstallLocationOverride.SetInstallLocation(builtDotnet, architecture); + registeredInstallLocationOverride.SetInstallLocation(new (string, string)[] { (architecture, builtDotnet) }); } // Verify running with the default working directory @@ -357,7 +357,7 @@ public void MissingRuntimeConfig_Fails(bool useAppHost) if (useAppHost) { command = Command.Create(sharedTestState.MockApp.AppExe) - .DotNetRoot(sharedTestState.BuiltDotNet.BinPath); + .DotNetRoot(sharedTestState.BuiltDotNet.BinPath, sharedTestState.RepoDirectories.BuildArchitecture); } else { @@ -386,7 +386,7 @@ public void MissingFrameworkInRuntimeConfig_Fails(bool useAppHost) if (useAppHost) { command = Command.Create(app.AppExe) - .DotNetRoot(sharedTestState.BuiltDotNet.BinPath); + .DotNetRoot(sharedTestState.BuiltDotNet.BinPath, sharedTestState.RepoDirectories.BuildArchitecture); } else { @@ -540,7 +540,7 @@ public void AppHost_GUI_NoCustomErrorWriter_FrameworkMissing_ErrorReportedInDial Command command = Command.Create(appExe) .EnableTracingAndCaptureOutputs() - .DotNetRoot(dotnet.BinPath) + .DotNetRoot(dotnet.BinPath, sharedTestState.RepoDirectories.BuildArchitecture) .MultilevelLookup(false) .Start(); diff --git a/src/installer/tests/HostActivation.Tests/RegisteredInstallLocationOverride.cs b/src/installer/tests/HostActivation.Tests/RegisteredInstallLocationOverride.cs index 7a30c5d6fd08d..d37cfafe64953 100644 --- a/src/installer/tests/HostActivation.Tests/RegisteredInstallLocationOverride.cs +++ b/src/installer/tests/HostActivation.Tests/RegisteredInstallLocationOverride.cs @@ -4,8 +4,10 @@ using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.Win32; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; namespace Microsoft.DotNet.CoreSetup.Test.HostActivation @@ -61,18 +63,24 @@ public RegisteredInstallLocationOverride(string productBinaryPath) } } - public void SetInstallLocation(string installLocation, string architecture) + public void SetInstallLocation(params (string Architecture, string Path)[] locations) { + Debug.Assert(locations.Length >= 1); if (OperatingSystem.IsWindows()) { - using (RegistryKey dotnetLocationKey = key.CreateSubKey($@"Setup\InstalledVersions\{architecture}")) + foreach (var location in locations) { - dotnetLocationKey.SetValue("InstallLocation", installLocation); + using (RegistryKey dotnetLocationKey = key.CreateSubKey($@"Setup\InstalledVersions\{location.Architecture}")) + { + dotnetLocationKey.SetValue("InstallLocation", location.Path); + } } } else { - File.WriteAllText(PathValueOverride, installLocation); + File.WriteAllText(PathValueOverride, string.Join(Environment.NewLine, + locations.Select(l => string.Format("{0}{1}", + (!string.IsNullOrWhiteSpace(l.Architecture) ? l.Architecture + "=" : ""), l.Path)))); } } diff --git a/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs b/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs index b7ec6eb968891..352abb04f0dc1 100644 --- a/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs +++ b/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs @@ -204,8 +204,7 @@ public void Running_Publish_Output_Standalone_EXE_With_DOTNET_ROOT_Fails() // self-contained layout since a flat layout of the shared framework is not supported. Command.Create(appExe) .EnvironmentVariable("COREHOST_TRACE", "1") - .EnvironmentVariable("DOTNET_ROOT", newOutDir) - .EnvironmentVariable("DOTNET_ROOT(x86)", newOutDir) + .DotNetRoot(newOutDir) .CaptureStdErr() .CaptureStdOut() .Execute(fExpectedToFail: true) diff --git a/src/installer/tests/TestUtils/TestProjectFixture.cs b/src/installer/tests/TestUtils/TestProjectFixture.cs index 2ccce4dded155..7b9da29d1a908 100644 --- a/src/installer/tests/TestUtils/TestProjectFixture.cs +++ b/src/installer/tests/TestUtils/TestProjectFixture.cs @@ -358,7 +358,7 @@ public TestProjectFixture RestoreProject(string[] fallbackSources, string extraM public TestProjectFixture EnsureRestored(params string[] fallbackSources) { - if ( ! TestProject.IsRestored()) + if (!TestProject.IsRestored()) { RestoreProject(fallbackSources); } @@ -368,7 +368,7 @@ public TestProjectFixture EnsureRestored(params string[] fallbackSources) public TestProjectFixture EnsureRestoredForRid(string rid, params string[] fallbackSources) { - if ( ! TestProject.IsRestored()) + if (!TestProject.IsRestored()) { string extraMSBuildProperties = $"/p:TestTargetRid={rid}"; RestoreProject(fallbackSources, extraMSBuildProperties); diff --git a/src/native/corehost/deps_format.cpp b/src/native/corehost/deps_format.cpp index f0beb6ed7e54e..8ab0f22caff2c 100644 --- a/src/native/corehost/deps_format.cpp +++ b/src/native/corehost/deps_format.cpp @@ -84,7 +84,7 @@ void deps_json_t::reconcile_libraries_with_targets( size_t pos = lib_name.find(_X("/")); entry.library_name = lib_name.substr(0, pos); entry.library_version = lib_name.substr(pos + 1); - entry.library_type = pal::to_lower(library.value[_X("type")].GetString()); + entry.library_type = to_lower(library.value[_X("type")].GetString()); entry.library_hash = hash; entry.library_path = library_path; entry.library_hash_path = library_hash_path; diff --git a/src/native/corehost/fxr/command_line.cpp b/src/native/corehost/fxr/command_line.cpp index 082f81d802c12..deb80b38b7f52 100644 --- a/src/native/corehost/fxr/command_line.cpp +++ b/src/native/corehost/fxr/command_line.cpp @@ -86,7 +86,7 @@ namespace while (arg_i < argc) { const pal::char_t* arg = argv[arg_i]; - pal::string_t arg_lower = pal::to_lower(arg); + pal::string_t arg_lower = to_lower(arg); const auto &iter = std::find_if(known_opts.cbegin(), known_opts.cend(), [&](const known_options &opt) { return arg_lower == get_host_option(opt).option; }); if (iter == known_opts.cend()) diff --git a/src/native/corehost/fxr_resolver.cpp b/src/native/corehost/fxr_resolver.cpp index d457047ddc8f9..6e1356457d49a 100644 --- a/src/native/corehost/fxr_resolver.cpp +++ b/src/native/corehost/fxr_resolver.cpp @@ -65,10 +65,10 @@ bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* o return true; } - // For framework-dependent apps, use DOTNET_ROOT + // For framework-dependent apps, use DOTNET_ROOT_ pal::string_t default_install_location; - pal::string_t dotnet_root_env_var_name = get_dotnet_root_env_var_name(); - if (get_file_path_from_env(dotnet_root_env_var_name.c_str(), out_dotnet_root)) + pal::string_t dotnet_root_env_var_name; + if (get_dotnet_root_from_env(&dotnet_root_env_var_name, out_dotnet_root)) { trace::info(_X("Using environment variable %s=[%s] as runtime location."), dotnet_root_env_var_name.c_str(), out_dotnet_root->c_str()); } @@ -134,7 +134,7 @@ bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* o #endif // !FEATURE_APPHOST && !FEATURE_LIBHOST } -bool fxr_resolver::try_get_path_from_dotnet_root(const pal::string_t &dotnet_root, pal::string_t *out_fxr_path) +bool fxr_resolver::try_get_path_from_dotnet_root(const pal::string_t& dotnet_root, pal::string_t* out_fxr_path) { pal::string_t fxr_dir = dotnet_root; append_path(&fxr_dir, _X("host")); @@ -148,7 +148,7 @@ bool fxr_resolver::try_get_path_from_dotnet_root(const pal::string_t &dotnet_roo return get_latest_fxr(std::move(fxr_dir), out_fxr_path); } -bool fxr_resolver::try_get_existing_fxr(pal::dll_t *out_fxr, pal::string_t *out_fxr_path) +bool fxr_resolver::try_get_existing_fxr(pal::dll_t* out_fxr, pal::string_t* out_fxr_path) { if (!pal::get_loaded_library(LIBFXR_NAME, "hostfxr_main", out_fxr, out_fxr_path)) return false; diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 27daf76c72723..b6622cd5dff52 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -104,13 +104,13 @@ namespace pal { #if defined(_WIN32) - #ifdef EXPORT_SHARED_API - #define SHARED_API extern "C" __declspec(dllexport) - #else - #define SHARED_API extern "C" - #endif +#ifdef EXPORT_SHARED_API +#define SHARED_API extern "C" __declspec(dllexport) +#else +#define SHARED_API extern "C" +#endif - #define STDMETHODCALLTYPE __stdcall +#define STDMETHODCALLTYPE __stdcall typedef wchar_t char_t; typedef std::wstring string_t; @@ -151,13 +151,13 @@ namespace pal inline int strcasecmp(const char_t* str1, const char_t* str2) { return ::_wcsicmp(str1, str2); } inline int strncmp(const char_t* str1, const char_t* str2, size_t len) { return ::wcsncmp(str1, str2, len); } inline int strncasecmp(const char_t* str1, const char_t* str2, size_t len) { return ::_wcsnicmp(str1, str2, len); } - inline int pathcmp(const pal::string_t &path1, const pal::string_t &path2) { return strcasecmp(path1.c_str(), path2.c_str()); } + inline int pathcmp(const pal::string_t& path1, const pal::string_t& path2) { return strcasecmp(path1.c_str(), path2.c_str()); } inline string_t to_string(int value) { return std::to_wstring(value); } inline size_t strlen(const char_t* str) { return ::wcslen(str); } #pragma warning(suppress : 4996) // error C4996: '_wfopen': This function or variable may be unsafe. - inline FILE * file_open(const string_t& path, const char_t* mode) { return ::_wfopen(path.c_str(), mode); } + inline FILE* file_open(const string_t& path, const char_t* mode) { return ::_wfopen(path.c_str(), mode); } inline void file_vprintf(FILE* f, const char_t* format, va_list vl) { ::vfwprintf(f, format, vl); ::fputwc(_X('\n'), f); } inline void err_fputs(const char_t* message) { ::fputws(message, stderr); ::fputwc(_X('\n'), stderr); } @@ -170,32 +170,32 @@ namespace pal // Suppressing warning since the 'safe' version requires an input buffer that is unnecessary for // uses of this function. #pragma warning(suppress : 4996) // error C4996: '_wcserror': This function or variable may be unsafe. - inline const char_t* strerror(int errnum){ return ::_wcserror(errnum); } + inline const char_t* strerror(int errnum) { return ::_wcserror(errnum); } bool pal_utf8string(const string_t& str, std::vector* out); bool pal_clrstring(const string_t& str, std::vector* out); bool clr_palstring(const char* cstr, string_t* out); inline bool mkdir(const char_t* dir, int mode) { return CreateDirectoryW(dir, NULL) != 0; } - inline bool rmdir (const char_t* path) { return RemoveDirectoryW(path) != 0; } + inline bool rmdir(const char_t* path) { return RemoveDirectoryW(path) != 0; } inline int rename(const char_t* old_name, const char_t* new_name) { return ::_wrename(old_name, new_name); } inline int remove(const char_t* path) { return ::_wremove(path); } inline bool munmap(void* addr, size_t length) { return UnmapViewOfFile(addr) != 0; } inline int get_pid() { return GetCurrentProcessId(); } inline void sleep(uint32_t milliseconds) { Sleep(milliseconds); } #else - #ifdef EXPORT_SHARED_API - #define SHARED_API extern "C" __attribute__((__visibility__("default"))) - #else - #define SHARED_API extern "C" - #endif - - #define __cdecl /* nothing */ - #define __stdcall /* nothing */ - #if !defined(TARGET_FREEBSD) - #define __fastcall /* nothing */ - #endif - #define STDMETHODCALLTYPE __stdcall +#ifdef EXPORT_SHARED_API +#define SHARED_API extern "C" __attribute__((__visibility__("default"))) +#else +#define SHARED_API extern "C" +#endif + +#define __cdecl /* nothing */ +#define __stdcall /* nothing */ +#if !defined(TARGET_FREEBSD) +#define __fastcall /* nothing */ +#endif +#define STDMETHODCALLTYPE __stdcall typedef char char_t; typedef std::string string_t; @@ -219,7 +219,7 @@ namespace pal inline string_t to_string(int value) { return std::to_string(value); } inline size_t strlen(const char_t* str) { return ::strlen(str); } - inline FILE * file_open(const string_t& path, const char_t* mode) { return fopen(path.c_str(), mode); } + inline FILE* file_open(const string_t& path, const char_t* mode) { return fopen(path.c_str(), mode); } inline void file_vprintf(FILE* f, const char_t* format, va_list vl) { ::vfprintf(f, format, vl); ::fputc('\n', f); } inline void err_fputs(const char_t* message) { ::fputs(message, stderr); ::fputc(_X('\n'), stderr); } inline void out_vprintf(const char_t* format, va_list vl) { ::vfprintf(stdout, format, vl); ::fputc('\n', stdout); } @@ -237,7 +237,6 @@ namespace pal inline bool munmap(void* addr, size_t length) { return ::munmap(addr, length) == 0; } inline int get_pid() { return getpid(); } inline void sleep(uint32_t milliseconds) { usleep(milliseconds * 1000); } - #endif inline int snwprintf(char_t* buffer, size_t count, const char_t* format, ...) @@ -252,10 +251,8 @@ namespace pal string_t get_timestamp(); bool getcwd(string_t* recv); - string_t to_lower(const char_t* in); - - inline void file_flush(FILE *f) { std::fflush(f); } + inline void file_flush(FILE* f) { std::fflush(f); } inline void err_flush() { std::fflush(stderr); } inline void out_flush() { std::fflush(stdout); } @@ -283,7 +280,7 @@ namespace pal bool get_own_module_path(string_t* recv); bool get_method_module_path(string_t* recv, void* method); bool get_module_path(dll_t mod, string_t* recv); - bool get_current_module(dll_t *mod); + bool get_current_module(dll_t* mod); bool getenv(const char_t* name, string_t* recv); bool get_default_servicing_directory(string_t* recv); @@ -307,7 +304,7 @@ namespace pal int xtoi(const char_t* input); - bool get_loaded_library(const char_t *library_name, const char *symbol_name, /*out*/ dll_t *dll, /*out*/ string_t *path); + bool get_loaded_library(const char_t* library_name, const char* symbol_name, /*out*/ dll_t* dll, /*out*/ string_t* path); bool load_library(const string_t* path, dll_t* dll); proc_t get_symbol(dll_t library, const char* name); void unload_library(dll_t library); diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index 2db2dd8488f56..411298a08f4ce 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -50,13 +50,6 @@ #error "Don't know how to obtain max path on this platform" #endif -pal::string_t pal::to_lower(const pal::char_t* in) -{ - pal::string_t ret = in; - std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); - return ret; -} - pal::string_t pal::get_timestamp() { std::time_t t = std::time(nullptr); @@ -75,7 +68,7 @@ bool pal::touch_file(const pal::string_t& path) trace::warning(_X("open(%s) failed in %s"), path.c_str(), _STRINGIFY(__FUNCTION__)); return false; } - (void) close(fd); + (void)close(fd); return true; } @@ -146,12 +139,12 @@ bool pal::getcwd(pal::string_t* recv) namespace { - bool get_loaded_library_from_proc_maps(const pal::char_t *library_name, pal::dll_t *dll, pal::string_t *path) + bool get_loaded_library_from_proc_maps(const pal::char_t* library_name, pal::dll_t* dll, pal::string_t* path) { - char *line = nullptr; + char* line = nullptr; size_t lineLen = 0; ssize_t read; - FILE *file = pal::file_open(_X("/proc/self/maps"), _X("r")); + FILE* file = pal::file_open(_X("/proc/self/maps"), _X("r")); if (file == nullptr) return false; @@ -192,10 +185,10 @@ namespace } bool pal::get_loaded_library( - const char_t *library_name, - const char *symbol_name, - /*out*/ dll_t *dll, - /*out*/ pal::string_t *path) + const char_t* library_name, + const char* symbol_name, + /*out*/ dll_t* dll, + /*out*/ pal::string_t* path) { pal::string_t library_name_local; #if defined(TARGET_OSX) @@ -340,7 +333,7 @@ bool pal::get_default_servicing_directory(string_t* recv) bool is_read_write_able_directory(pal::string_t& dir) { return pal::realpath(&dir) && - (access(dir.c_str(), R_OK | W_OK | X_OK) == 0); + (access(dir.c_str(), R_OK | W_OK | X_OK) == 0); } bool get_extraction_base_parent_directory(pal::string_t& directory) @@ -388,18 +381,41 @@ bool pal::get_global_dotnet_dirs(std::vector* recv) bool pal::get_dotnet_self_registered_config_location(pal::string_t* recv) { - *recv = _X("/etc/dotnet/install_location"); + recv->assign(_X("/etc/dotnet/install_location")); // ***Used only for testing*** pal::string_t environment_install_location_override; if (test_only_getenv(_X("_DOTNET_TEST_INSTALL_LOCATION_FILE_PATH"), &environment_install_location_override)) { - *recv = environment_install_location_override; + recv->assign(environment_install_location_override); } return true; } +namespace +{ + bool get_line_from_file(FILE* pFile, pal::string_t& line) + { + line = pal::string_t(); + char buffer[256]; + while (fgets(buffer, sizeof(buffer), pFile)) + { + line += (pal::char_t*)buffer; + size_t len = line.length(); + + // fgets includes the newline character in the string - so remove it. + if (len > 0 && line[len - 1] == '\n') + { + line.pop_back(); + break; + } + } + + return !line.empty(); + } +} + bool pal::get_dotnet_self_registered_dir(pal::string_t* recv) { recv->clear(); @@ -424,35 +440,60 @@ bool pal::get_dotnet_self_registered_dir(pal::string_t* recv) FILE* install_location_file = pal::file_open(install_location_file_path, "r"); if (install_location_file == nullptr) { - trace::verbose(_X("The install_location file failed to open.")); + trace::error(_X("The install_location file ['%s'] failed to open: %s."), install_location_file_path.c_str(), pal::strerror(errno)); return false; } - bool result = false; + pal::string_t install_location; + int current_line = 0; + bool is_first_line = true, install_location_found = false; - char buf[PATH_MAX]; - char* install_location = fgets(buf, sizeof(buf), install_location_file); - if (install_location != nullptr) + while (get_line_from_file(install_location_file, install_location)) { - size_t len = pal::strlen(install_location); + current_line++; + size_t arch_sep = install_location.find(_X('=')); + if (arch_sep == pal::string_t::npos) + { + if (is_first_line) + { + recv->assign(install_location); + install_location_found = true; + trace::verbose(_X("Found install location path '%s'."), install_location.c_str()); + } + else + { + trace::warning(_X("Found unprefixed install location path '%s' on line %d."), install_location.c_str(), current_line); + trace::warning(_X("Only the first line in '%s' may not have an architecture prefix."), install_location_file_path.c_str()); + } + + is_first_line = false; + continue; + } + + pal::string_t arch_prefix = install_location.substr(0, arch_sep); + pal::string_t path_to_location = install_location.substr(arch_sep + 1); - // fgets includes the newline character in the string - so remove it. - if (len > 0 && len < PATH_MAX && install_location[len - 1] == '\n') + trace::verbose(_X("Found architecture-specific install location path: '%s' ('%s')."), path_to_location.c_str(), arch_prefix.c_str()); + if (pal::strcasecmp(arch_prefix.c_str(), get_arch()) == 0) { - install_location[len - 1] = '\0'; + recv->assign(path_to_location); + install_location_found = true; + trace::verbose(_X("Found architecture-specific install location path matching the current host architecture ('%s'): '%s'."), arch_prefix.c_str(), path_to_location.c_str()); + break; } - trace::verbose(_X("Using install location '%s'."), install_location); - *recv = install_location; - result = true; + is_first_line = false; } - else + + fclose(install_location_file); + if (!install_location_found) { - trace::verbose(_X("The install_location file first line could not be read.")); + trace::warning(_X("Did not find any install location in '%s'."), install_location_file_path.c_str()); + return false; } - fclose(install_location_file); - return result; + trace::verbose(_X("Using install location '%s'."), recv->c_str()); + return true; } bool pal::get_default_installation_dir(pal::string_t* recv) @@ -467,17 +508,17 @@ bool pal::get_default_installation_dir(pal::string_t* recv) // *************************** #if defined(TARGET_OSX) - recv->assign(_X("/usr/local/share/dotnet")); + recv->assign(_X("/usr/local/share/dotnet")); #else - recv->assign(_X("/usr/share/dotnet")); + recv->assign(_X("/usr/share/dotnet")); #endif - return true; + return true; } pal::string_t trim_quotes(pal::string_t stringToCleanup) { - pal::char_t quote_array[2] = {'\"', '\''}; - for (size_t index = 0; index < sizeof(quote_array)/sizeof(quote_array[0]); index++) + pal::char_t quote_array[2] = { '\"', '\'' }; + for (size_t index = 0; index < sizeof(quote_array) / sizeof(quote_array[0]); index++) { size_t pos = stringToCleanup.find(quote_array[index]); while (pos != std::string::npos) @@ -553,11 +594,11 @@ pal::string_t pal::get_current_os_rid_platform() if (ret == 0) { - char *pos = strchr(str, '.'); + char* pos = strchr(str, '.'); if (pos) { ridOS.append(_X("freebsd.")) - .append(str, pos - str); + .append(str, pos - str); } } @@ -589,7 +630,7 @@ pal::string_t pal::get_current_os_rid_platform() if (strncmp(utsname_obj.version, "omnios", strlen("omnios")) == 0) { ridOS.append(_X("omnios.")) - .append(utsname_obj.version, strlen("omnios-r"), 2); // e.g. omnios.15 + .append(utsname_obj.version, strlen("omnios-r"), 2); // e.g. omnios.15 } else if (strncmp(utsname_obj.version, "illumos-", strlen("illumos-")) == 0) { @@ -598,7 +639,7 @@ pal::string_t pal::get_current_os_rid_platform() else if (strncmp(utsname_obj.version, "joyent_", strlen("joyent_")) == 0) { ridOS.append(_X("smartos.")) - .append(utsname_obj.version, strlen("joyent_"), 4); // e.g. smartos.2020 + .append(utsname_obj.version, strlen("joyent_"), 4); // e.g. smartos.2020 } return ridOS; @@ -621,11 +662,11 @@ pal::string_t pal::get_current_os_rid_platform() return ridOS; } - char *pos = strchr(utsname_obj.version, '.'); + char* pos = strchr(utsname_obj.version, '.'); if (pos) { ridOS.append(_X("solaris.")) - .append(utsname_obj.version, pos - utsname_obj.version); // e.g. solaris.11 + .append(utsname_obj.version, pos - utsname_obj.version); // e.g. solaris.11 } return ridOS; @@ -771,7 +812,7 @@ bool pal::get_own_executable_path(pal::string_t* recv) bool pal::get_own_module_path(string_t* recv) { Dl_info info; - if (dladdr((void *)&pal::get_own_module_path, &info) == 0) + if (dladdr((void*)&pal::get_own_module_path, &info) == 0) return false; recv->assign(info.dli_fname); @@ -793,7 +834,7 @@ bool pal::get_module_path(dll_t module, string_t* recv) return false; } -bool pal::get_current_module(dll_t *mod) +bool pal::get_current_module(dll_t* mod) { return false; } @@ -876,31 +917,31 @@ static void readdir(const pal::string_t& path, const pal::string_t& pattern, boo } break; - // Handle symlinks and file systems that do not support d_type + // Handle symlinks and file systems that do not support d_type case DT_LNK: case DT_UNKNOWN: - { - struct stat sb; + { + struct stat sb; - if (fstatat(dirfd(dir), entry->d_name, &sb, 0) == -1) - { - continue; - } + if (fstatat(dirfd(dir), entry->d_name, &sb, 0) == -1) + { + continue; + } - if (onlydirectories) - { - if (!S_ISDIR(sb.st_mode)) - { - continue; - } - break; - } - else if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) + if (onlydirectories) + { + if (!S_ISDIR(sb.st_mode)) { continue; } + break; } - break; + else if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) + { + continue; + } + } + break; default: continue; diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index 8a9e35a3236f0..c992f92da8294 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -26,7 +26,7 @@ bool GetModuleFileNameWrapper(HMODULE hModule, pal::string_t* recv) return false; path.resize(dwModuleFileName); - *recv = path; + recv->assign(path); return true; } @@ -40,13 +40,6 @@ bool GetModuleHandleFromAddress(void *addr, HMODULE *hModule) return (res != FALSE); } -pal::string_t pal::to_lower(const pal::char_t* in) -{ - pal::string_t ret = in; - std::transform(ret.begin(), ret.end(), ret.begin(), ::towlower); - return ret; -} - pal::string_t pal::get_timestamp() { std::time_t t = std::time(nullptr); @@ -339,7 +332,7 @@ bool pal::get_dotnet_self_registered_config_location(pal::string_t* recv) const pal::char_t* value; get_dotnet_install_location_registry_path(&key_hive, &sub_key, &value); - *recv = (key_hive == HKEY_CURRENT_USER ? _X("HKCU\\") : _X("HKLM\\")) + sub_key + _X("\\") + value; + recv->assign((key_hive == HKEY_CURRENT_USER ? _X("HKCU\\") : _X("HKLM\\")) + sub_key + _X("\\") + value); return true; #endif } diff --git a/src/native/corehost/hostmisc/utils.cpp b/src/native/corehost/hostmisc/utils.cpp index 055b4f6a31dd8..abf1aeee89e0a 100644 --- a/src/native/corehost/hostmisc/utils.cpp +++ b/src/native/corehost/hostmisc/utils.cpp @@ -161,7 +161,7 @@ void remove_trailing_dir_seperator(pal::string_t* dir) void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl) { - size_t pos = 0; + size_t pos = 0; while ((pos = path->find(match, pos)) != pal::string_t::npos) { (*path)[pos] = repl; @@ -170,7 +170,7 @@ void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl) pal::string_t get_replaced_char(const pal::string_t& path, pal::char_t match, pal::char_t repl) { - size_t pos = path.find(match); + size_t pos = path.find(match); if (pos == pal::string_t::npos) { return path; @@ -241,7 +241,7 @@ bool get_env_shared_store_dirs(std::vector* dirs, const pal::stri return true; } -bool get_global_shared_store_dirs(std::vector* dirs, const pal::string_t& arch, const pal::string_t& tfm) +bool get_global_shared_store_dirs(std::vector* dirs, const pal::string_t& arch, const pal::string_t& tfm) { std::vector global_dirs; if (!pal::get_global_dotnet_dirs(&global_dirs)) @@ -348,14 +348,26 @@ bool try_stou(const pal::string_t& str, unsigned* num) return true; } -pal::string_t get_dotnet_root_env_var_name() +bool get_dotnet_root_from_env(pal::string_t* dotnet_root_env_var_name, pal::string_t* recv) { + *dotnet_root_env_var_name = _X("DOTNET_ROOT_"); + dotnet_root_env_var_name->append(to_upper(get_arch())); + if (get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv)) + return true; + +#if defined(WIN32) if (pal::is_running_in_wow64()) { - return pal::string_t(_X("DOTNET_ROOT(x86)")); + *dotnet_root_env_var_name = _X("DOTNET_ROOT(x86)"); + if (get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv)) + return true; } +#endif - return pal::string_t(_X("DOTNET_ROOT")); + // If no architecture-specific environment variable was set + // fallback to the default DOTNET_ROOT. + *dotnet_root_env_var_name = _X("DOTNET_ROOT"); + return get_file_path_from_env(dotnet_root_env_var_name->c_str(), recv); } /** @@ -402,7 +414,7 @@ void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& na trace::verbose(_X("Runtime config is cfg=%s dev=%s"), cfg->c_str(), dev_cfg->c_str()); } -pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path) +pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t& fxr_path) { // If coreclr exists next to hostfxr, assume everything is local (e.g. self-contained) pal::string_t fxr_dir = get_directory(fxr_path); @@ -414,7 +426,7 @@ pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path) return get_directory(get_directory(fxr_root)); } -pal::string_t get_download_url(const pal::char_t *framework_name, const pal::char_t *framework_version) +pal::string_t get_download_url(const pal::char_t* framework_name, const pal::char_t* framework_version) { pal::string_t url = DOTNET_CORE_APPLAUNCH_URL _X("?"); if (framework_name != nullptr && pal::strlen(framework_name) > 0) @@ -441,6 +453,18 @@ pal::string_t get_download_url(const pal::char_t *framework_name, const pal::cha return url; } +pal::string_t to_lower(const pal::char_t* in) { + pal::string_t ret = in; + std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); + return ret; +} + +pal::string_t to_upper(const pal::char_t* in) { + pal::string_t ret = in; + std::transform(ret.begin(), ret.end(), ret.begin(), ::toupper); + return ret; +} + #define TEST_ONLY_MARKER "d38cc827-e34f-4453-9df4-1e796e9f1d07" // Retrieves environment variable which is only used for testing. diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index d0dc381b69cca..b4e31b599fac1 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -43,16 +43,19 @@ void get_framework_and_sdk_locations(const pal::string_t& dotnet_dir, std::vecto bool get_file_path_from_env(const pal::char_t* env_key, pal::string_t* recv); size_t index_of_non_numeric(const pal::string_t& str, size_t i); bool try_stou(const pal::string_t& str, unsigned* num); -pal::string_t get_dotnet_root_env_var_name(); +bool get_dotnet_root_from_env(pal::string_t* used_dotnet_root_env_var_name, pal::string_t* recv); pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal::string_t& app); pal::string_t get_runtime_config_path(const pal::string_t& path, const pal::string_t& name); pal::string_t get_runtime_config_dev_path(const pal::string_t& path, const pal::string_t& name); void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg); -pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path); +pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t& fxr_path); // Get a download URL for a specific framework and version // If no framework is specified, a download URL for the runtime is returned -pal::string_t get_download_url(const pal::char_t *framework_name = nullptr, const pal::char_t *framework_version = nullptr); +pal::string_t get_download_url(const pal::char_t* framework_name = nullptr, const pal::char_t* framework_version = nullptr); + +pal::string_t to_lower(const pal::char_t* in); +pal::string_t to_upper(const pal::char_t* in); // Retrieves environment variable which is only used for testing. // This will return the value of the variable only if the product binary is stamped @@ -63,7 +66,7 @@ bool test_only_getenv(const pal::char_t* name, pal::string_t* recv); class propagate_error_writer_t { public: - typedef trace::error_writer_fn(__cdecl *set_error_writer_fn)(trace::error_writer_fn error_writer); + typedef trace::error_writer_fn(__cdecl* set_error_writer_fn)(trace::error_writer_fn error_writer); private: set_error_writer_fn m_set_error_writer; diff --git a/src/native/corehost/test/nativehost/nativehost.cpp b/src/native/corehost/test/nativehost/nativehost.cpp index b323f95a82949..80280b7bdbbeb 100644 --- a/src/native/corehost/test/nativehost/nativehost.cpp +++ b/src/native/corehost/test/nativehost/nativehost.cpp @@ -40,7 +40,7 @@ int main(const int argc, const pal::char_t *argv[]) // args: ... [] [] [] [] bool explicit_load = false; if (argc >= 3) - explicit_load = pal::strcmp(pal::to_lower(argv[2]).c_str(), _X("true")) == 0; + explicit_load = pal::strcmp(to_lower(argv[2]).c_str(), _X("true")) == 0; const pal::char_t *assembly_path = nullptr; if (argc >= 4 && pal::strcmp(argv[3], _X("nullptr")) != 0) @@ -117,7 +117,7 @@ int main(const int argc, const pal::char_t *argv[]) if (static_cast(res) == StatusCode::Success) { std::cout << "get_hostfxr_path succeeded" << std::endl; - std::cout << "hostfxr_path: " << tostr(pal::to_lower(fxr_path.c_str())).data() << std::endl; + std::cout << "hostfxr_path: " << tostr(to_lower(fxr_path.c_str())).data() << std::endl; return EXIT_SUCCESS; } else