Add support for multi-arch install locations (#53763)
authorMateo Torres-Ruiz <mateoatr@users.noreply.github.com>
Wed, 23 Jun 2021 07:19:10 +0000 (00:19 -0700)
committerGitHub <noreply@github.com>
Wed, 23 Jun 2021 07:19:10 +0000 (00:19 -0700)
* Add support for multiple architectures inside install_locations

* Add install_location tests

* Fallback to DOTNET_ROOT on win32

18 files changed:
src/installer/tests/HostActivation.Tests/CommandExtensions.cs
src/installer/tests/HostActivation.Tests/InstallLocationCommandResultExtensions.cs [new file with mode: 0644]
src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs [new file with mode: 0644]
src/installer/tests/HostActivation.Tests/MultilevelSDKLookup.cs
src/installer/tests/HostActivation.Tests/NativeHosting/Nethost.cs
src/installer/tests/HostActivation.Tests/PortableAppActivation.cs
src/installer/tests/HostActivation.Tests/RegisteredInstallLocationOverride.cs
src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs
src/installer/tests/TestUtils/TestProjectFixture.cs
src/native/corehost/deps_format.cpp
src/native/corehost/fxr/command_line.cpp
src/native/corehost/fxr_resolver.cpp
src/native/corehost/hostmisc/pal.h
src/native/corehost/hostmisc/pal.unix.cpp
src/native/corehost/hostmisc/pal.windows.cpp
src/native/corehost/hostmisc/utils.cpp
src/native/corehost/hostmisc/utils.h
src/native/corehost/test/nativehost/nativehost.cpp

index 054e6c3..d8460c1 100644 (file)
@@ -35,8 +35,11 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
                 .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 (file)
index 0000000..c85b7d3
--- /dev/null
@@ -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}').");
+        }
+    }
+}
diff --git a/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs b/src/installer/tests/HostActivation.Tests/MultiArchInstallLocation.cs
new file mode 100644 (file)
index 0000000..c1b00ca
--- /dev/null
@@ -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();
+            }
+        }
+    }
+}
index 97b3387..76d1133 100644 (file)
@@ -501,7 +501,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
 
             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");
index 0f18161..0af2ce3 100644 (file)
@@ -116,7 +116,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
             {
                 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 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
 
         [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()
@@ -224,6 +235,94 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
         }
 
         [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()
         {
             Command.Create(sharedState.NativeHostPath, $"{GetHostFxrPath} false [error]")
index 14060b5..a2a0d96 100644 (file)
@@ -257,7 +257,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
             Command.Create(appExe)
                 .CaptureStdErr()
                 .CaptureStdOut()
-                .DotNetRoot(builtDotnet)
+                .DotNetRoot(builtDotnet, sharedTestState.RepoDirectories.BuildArchitecture)
                 .MultilevelLookup(false)
                 .Execute()
                 .Should().Pass()
@@ -268,7 +268,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
             // 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 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
 
             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 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
             if (useAppHost)
             {
                 command = Command.Create(sharedTestState.MockApp.AppExe)
-                    .DotNetRoot(sharedTestState.BuiltDotNet.BinPath);
+                    .DotNetRoot(sharedTestState.BuiltDotNet.BinPath, sharedTestState.RepoDirectories.BuildArchitecture);
             }
             else
             {
@@ -386,7 +386,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
             if (useAppHost)
             {
                 command = Command.Create(app.AppExe)
-                    .DotNetRoot(sharedTestState.BuiltDotNet.BinPath);
+                    .DotNetRoot(sharedTestState.BuiltDotNet.BinPath, sharedTestState.RepoDirectories.BuildArchitecture);
             }
             else
             {
@@ -540,7 +540,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
 
                 Command command = Command.Create(appExe)
                     .EnableTracingAndCaptureOutputs()
-                    .DotNetRoot(dotnet.BinPath)
+                    .DotNetRoot(dotnet.BinPath, sharedTestState.RepoDirectories.BuildArchitecture)
                     .MultilevelLookup(false)
                     .Start();
 
index 7a30c5d..d37cfaf 100644 (file)
@@ -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 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
             }
         }
 
-        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))));
             }
         }
 
index b7ec6eb..352abb0 100644 (file)
@@ -204,8 +204,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
             // 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)
index 2ccce4d..7b9da29 100644 (file)
@@ -358,7 +358,7 @@ namespace Microsoft.DotNet.CoreSetup.Test
 
         public TestProjectFixture EnsureRestored(params string[] fallbackSources)
         {
-            if ( ! TestProject.IsRestored())
+            if (!TestProject.IsRestored())
             {
                 RestoreProject(fallbackSources);
             }
@@ -368,7 +368,7 @@ namespace Microsoft.DotNet.CoreSetup.Test
 
         public TestProjectFixture EnsureRestoredForRid(string rid, params string[] fallbackSources)
         {
-            if ( ! TestProject.IsRestored())
+            if (!TestProject.IsRestored())
             {
                 string extraMSBuildProperties = $"/p:TestTargetRid={rid}";
                 RestoreProject(fallbackSources, extraMSBuildProperties);
index f0beb6e..8ab0f22 100644 (file)
@@ -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;
index 082f81d..deb80b3 100644 (file)
@@ -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())
index d457047..6e13564 100644 (file)
@@ -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_<ARCH>
     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;
index 27daf76..b6622cd 100644 (file)
 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<char>* out);
     bool pal_clrstring(const string_t& str, std::vector<char>* 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(FILEf) { 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_tmod);
     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);
index 2db2dd8..411298a 100644 (file)
 #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;
+        charline = nullptr;
         size_t lineLen = 0;
         ssize_t read;
-        FILE *file = pal::file_open(_X("/proc/self/maps"), _X("r"));
+        FILEfile = 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_tlibrary_name,
+    const charsymbol_name,
+    /*out*/ dll_tdll,
+    /*out*/ pal::string_tpath)
 {
     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<pal::string_t>* 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, '.');
+        charpos = 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, '.');
+    charpos = 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_tmod)
 {
     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;
index 8a9e35a..c992f92 100644 (file)
@@ -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
 }
index 055b4f6..abf1aee 100644 (file)
@@ -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<pal::string_t>* dirs, const pal::stri
     return true;
 }
 
-bool get_global_shared_store_dirs(std::vector<pal::string_t>*  dirs, const pal::string_t& arch, const pal::string_t& tfm)
+bool get_global_shared_store_dirs(std::vector<pal::string_t>* dirs, const pal::string_t& arch, const pal::string_t& tfm)
 {
     std::vector<pal::string_t> 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_tfxr_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.
index d0dc381..b4e31b5 100644 (file)
@@ -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_tfxr_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(__cdeclset_error_writer_fn)(trace::error_writer_fn error_writer);
 
 private:
     set_error_writer_fn m_set_error_writer;
index b323f95..80280b7 100644 (file)
@@ -40,7 +40,7 @@ int main(const int argc, const pal::char_t *argv[])
         // args: ... [<explicit_load>] [<assembly_path>] [<dotnet_root>] [<hostfxr_to_load>]
         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<StatusCode>(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