The AppHost and the global registry key (dotnet/core-setup#5455)
authorJohn Beisner <johnbeisner@users.noreply.github.com>
Tue, 12 Mar 2019 21:07:28 +0000 (14:07 -0700)
committerGitHub <noreply@github.com>
Tue, 12 Mar 2019 21:07:28 +0000 (14:07 -0700)
* The AppHost needs to consider the self-registered dotnet installation location when searching for the HostFXR.

Commit migrated from https://github.com/dotnet/core-setup/commit/ede9e16b4b2396475bdc38700ed6205b3df4c0bf

src/installer/corehost/common/pal.h
src/installer/corehost/common/pal.unix.cpp
src/installer/corehost/common/pal.windows.cpp
src/installer/corehost/corehost.cpp
src/installer/test/HostActivationTests/PortableAppActivation.cs

index e0ea1f6..a476710 100644 (file)
@@ -221,9 +221,9 @@ namespace pal
     bool get_default_servicing_directory(string_t* recv);
     
     //On Linux, there are no global locations
-    //On Windows there will be only one global location
+    //On Windows there will be up to 2 global locations
     bool get_global_dotnet_dirs(std::vector<pal::string_t>* recv);
-
+    bool get_dotnet_self_registered_dir(pal::string_t* recv);
     bool get_default_installation_dir(pal::string_t* recv);
     bool get_default_breadcrumb_store(string_t* recv);
     bool is_path_rooted(const string_t& path);
index 6ffd0b5..18f9813 100644 (file)
@@ -194,13 +194,19 @@ bool is_executable(const pal::string_t& file_path)
     return ((st.st_mode & S_IEXEC) != 0);
 }
 
- bool pal::get_global_dotnet_dirs(std::vector<pal::string_t>* recv)
+bool pal::get_global_dotnet_dirs(std::vector<pal::string_t>* recv)
 {
     // No support for global directories in Unix.
     return false;
 }
 
- bool pal::get_default_installation_dir(pal::string_t* recv)
+bool pal::get_dotnet_self_registered_dir(pal::string_t* recv)
+{
+    // No support for global directories in Unix.
+    return false;
+}
+
+bool pal::get_default_installation_dir(pal::string_t* recv)
 {
 #if defined(__APPLE__)
      recv->assign(_X("/usr/local/share/dotnet"));
index b9c61d5..84d3fcf 100644 (file)
@@ -226,7 +226,7 @@ bool pal::get_default_installation_dir(pal::string_t* recv)
     return true;
 }
 
-bool get_sdk_self_registered_dir(pal::string_t* recv)
+bool pal::get_dotnet_self_registered_dir(pal::string_t* recv)
 {
 #if !defined(_TARGET_AMD64_) && !defined(_TARGET_X86_)
     //  Self-registered SDK installation directory is only supported for x64 and x86 architectures.
@@ -304,7 +304,7 @@ bool pal::get_global_dotnet_dirs(std::vector<pal::string_t>* dirs)
     pal::string_t default_dir;
     pal::string_t custom_dir;
     bool dir_found = false;
-    if (get_sdk_self_registered_dir(&custom_dir))
+    if (pal::get_dotnet_self_registered_dir(&custom_dir))
     {
         dirs->push_back(custom_dir);
         dir_found = true;
index 4bafea0..e318d57 100644 (file)
@@ -127,14 +127,16 @@ bool resolve_fxr_path(const pal::string_t& root_path, pal::string_t* out_dotnet_
     }
     else
     {
-        // Check default installation root as fallback
-        if (!pal::get_default_installation_dir(&default_install_location))
+        if (pal::get_dotnet_self_registered_dir(&default_install_location) || pal::get_default_installation_dir(&default_install_location))
+        {
+            trace::info(_X("Using global installation location [%s] as runtime location."), default_install_location.c_str());
+            out_dotnet_root->assign(default_install_location);
+        }
+        else
         {
             trace::error(_X("A fatal error occurred, the default install location cannot be obtained."));
             return false;
         }
-        trace::info(_X("Using default installation location [%s] as runtime location."), default_install_location.c_str());
-        out_dotnet_root->assign(default_install_location);
     }
 
     fxr_dir = *out_dotnet_root;
@@ -144,12 +146,16 @@ bool resolve_fxr_path(const pal::string_t& root_path, pal::string_t* out_dotnet_
     {
         if (default_install_location.empty())
         {
+            pal::get_dotnet_self_registered_dir(&default_install_location);
+        }
+        if (default_install_location.empty())
+        {
             pal::get_default_installation_dir(&default_install_location);
         }
 
         trace::error(_X("A fatal error occurred. The required library %s could not be found.\n"
             "If this is a self-contained application, that library should exist in [%s].\n"
-            "If this is a framework-dependent application, install the runtime in the default location [%s] or use the %s environment variable to specify the runtime location."),
+            "If this is a framework-dependent application, install the runtime in the global location [%s] or use the %s environment variable to specify the runtime location."),
             LIBFXR_NAME,
             root_path.c_str(),
             default_install_location.c_str(),
index 5714db8..74654af 100644 (file)
@@ -6,6 +6,8 @@ using System;
 using System.IO;
 using System.Security.Cryptography;
 using System.Text;
+using System.Runtime.InteropServices;
+using Microsoft.Win32;
 using Xunit;
 
 namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
@@ -294,6 +296,81 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
                 .And.HaveStdOutContaining($"Framework Version:{sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}");
         }
 
+        [Fact]
+        public void Framework_Dependent_AppHost_From_Global_Registry_Location_Succeeds()
+        {
+            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                return;
+            }
+
+            var fixture = sharedTestState.PortableAppFixture_Published
+                .Copy();
+
+            // Since SDK doesn't support building framework dependent apphost yet, emulate that behavior
+            // by creating the executable from apphost.exe
+            var appExe = fixture.TestProject.AppExe;
+            var appDllName = Path.GetFileName(fixture.TestProject.AppDll);
+
+            string hostExeName = RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("apphost");
+            string builtAppHost = Path.Combine(sharedTestState.RepoDirectories.HostArtifacts, hostExeName);
+            string appDir = Path.GetDirectoryName(appExe);
+            string appDirHostExe = Path.Combine(appDir, hostExeName);
+
+            // Make a copy of apphost first, replace hash and overwrite app.exe, rather than
+            // overwrite app.exe and edit in place, because the file is opened as "write" for
+            // the replacement -- the test fails with ETXTBSY (exit code: 26) in Linux when
+            // executing a file opened in "write" mode.
+            File.Copy(builtAppHost, appDirHostExe, true);
+            using (var sha256 = SHA256.Create())
+            {
+                // Replace the hash with the managed DLL name.
+                var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes("foobar"));
+                var hashStr = BitConverter.ToString(hash).Replace("-", "").ToLower();
+                AppHostExtensions.SearchAndReplace(appDirHostExe, Encoding.UTF8.GetBytes(hashStr), Encoding.UTF8.GetBytes(appDllName), true);
+            }
+            File.Copy(appDirHostExe, appExe, true);
+
+            // Get the framework location that was built
+            string builtDotnet = fixture.BuiltDotnet.BinPath;
+
+            RegistryKey hkcu = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32);
+            RegistryKey interfaceKey = hkcu.CreateSubKey(@"Software\Classes\Interface");
+            string testKeyName = "_DOTNET_Test" + System.Diagnostics.Process.GetCurrentProcess().Id.ToString();
+            RegistryKey testKey = interfaceKey.CreateSubKey(testKeyName);
+            try
+            {
+                string architecture = fixture.CurrentRid.Split('-')[1];
+                RegistryKey dotnetLocationKey = testKey.CreateSubKey($@"Setup\InstalledVersions\{architecture}");
+                dotnetLocationKey.SetValue("InstallLocation", builtDotnet);
+
+                // Verify running with the default working directory
+                Command.Create(appExe)
+                    .CaptureStdErr()
+                    .CaptureStdOut()
+                    .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", testKey.Name)
+                    .Execute()
+                    .Should().Pass()
+                    .And.HaveStdOutContaining("Hello World")
+                    .And.HaveStdOutContaining($"Framework Version:{sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}");
+
+                // Verify running from within the working directory
+                Command.Create(appExe)
+                    .WorkingDirectory(fixture.TestProject.OutputDirectory)
+                    .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", testKey.Name)
+                    .CaptureStdErr()
+                    .CaptureStdOut()
+                    .Execute()
+                    .Should().Pass()
+                    .And.HaveStdOutContaining("Hello World")
+                    .And.HaveStdOutContaining($"Framework Version:{sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}");
+            }
+            finally
+            {
+                interfaceKey.DeleteSubKeyTree(testKeyName);
+            }
+        }
+
         private string MoveDepsJsonToSubdirectory(TestProjectFixture testProjectFixture)
         {
             var subdirectory = Path.Combine(testProjectFixture.TestProject.ProjectDirectory, "d");