From 64c77806e94c387ba84020f72eb03e846aa5d44b Mon Sep 17 00:00:00 2001 From: Mateo Torres-Ruiz Date: Fri, 12 Feb 2021 16:48:48 -0800 Subject: [PATCH] Add hostfxr_get_dotnet_environment_info API (#48097) * Add hostfxr_get_dotnet_environment_info * Change access modifiers in structs * Remove unnecessary context local * Use global install location if no dotnet_root is specified * Add framework tests * Update framework_info comparison * Remove commented code * Remove unnecessary marshaling Export fn * Add hostfxr_get_dotnet_environment_info to hostfxr exports * Fail if result is nullptr Validate size of structs Test that result_context isn't modified * Remove version_as_str Update tests * Add tests for invalid args --- src/installer/corehost/cli/fxr/framework_info.cpp | 18 +- src/installer/corehost/cli/fxr/framework_info.h | 6 +- src/installer/corehost/cli/fxr/hostfxr.cpp | 133 ++++++++++ .../corehost/cli/fxr/standalone/hostfxr.def | 1 + .../cli/fxr/standalone/hostfxr_unixexports.src | 1 + src/installer/corehost/cli/hostfxr.h | 33 +++ .../TestProjects/HostApiInvokerApp/HostFXR.cs | 126 +++++++++ .../tests/HostActivation.Tests/NativeHostApis.cs | 286 ++++++++++++++++++++- 8 files changed, 596 insertions(+), 8 deletions(-) diff --git a/src/installer/corehost/cli/fxr/framework_info.cpp b/src/installer/corehost/cli/fxr/framework_info.cpp index 838b2c1..0859f6e 100644 --- a/src/installer/corehost/cli/fxr/framework_info.cpp +++ b/src/installer/corehost/cli/fxr/framework_info.cpp @@ -19,7 +19,17 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & return false; } - return a.version < b.version; + if (a.version < b.version) + { + return true; + } + + if (a.version == b.version) + { + return a.hive_depth > b.hive_depth; + } + + return false; } /*static*/ void framework_info::get_all_framework_infos( @@ -30,6 +40,8 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & std::vector hive_dir; get_framework_and_sdk_locations(own_dir, &hive_dir); + int32_t hive_depth = 0; + for (pal::string_t dir : hive_dir) { auto fx_shared_dir = dir; @@ -68,13 +80,15 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & { trace::verbose(_X("Found FX version [%s]"), ver.c_str()); - framework_info info(fx_name, fx_dir, parsed); + framework_info info(fx_name, fx_dir, parsed, hive_depth); framework_infos->push_back(info); } } } } } + + hive_depth++; } std::sort(framework_infos->begin(), framework_infos->end(), compare_by_name_and_version); diff --git a/src/installer/corehost/cli/fxr/framework_info.h b/src/installer/corehost/cli/fxr/framework_info.h index 28eb754..e4eea09 100644 --- a/src/installer/corehost/cli/fxr/framework_info.h +++ b/src/installer/corehost/cli/fxr/framework_info.h @@ -9,10 +9,11 @@ struct framework_info { - framework_info(pal::string_t name, pal::string_t path, fx_ver_t version) + framework_info(pal::string_t name, pal::string_t path, fx_ver_t version, int32_t hive_depth) : name(name) , path(path) - , version(version) { } + , version(version) + , hive_depth(hive_depth) { } static void get_all_framework_infos( const pal::string_t& own_dir, @@ -24,6 +25,7 @@ struct framework_info pal::string_t name; pal::string_t path; fx_ver_t version; + int32_t hive_depth; }; #endif // __FRAMEWORK_INFO_H_ diff --git a/src/installer/corehost/cli/fxr/hostfxr.cpp b/src/installer/corehost/cli/fxr/hostfxr.cpp index d761947..4283232 100644 --- a/src/installer/corehost/cli/fxr/hostfxr.cpp +++ b/src/installer/corehost/cli/fxr/hostfxr.cpp @@ -14,6 +14,7 @@ #include "hostfxr.h" #include "host_context.h" #include "bundle/info.h" +#include namespace { @@ -331,6 +332,138 @@ SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_get_available_sdks( } // +// Returns available SDKs and frameworks. +// +// Resolves the existing SDKs and frameworks from a dotnet root directory (if +// any), or the global default location. If multi-level lookup is enabled and +// the dotnet root location is different than the global location, the SDKs and +// frameworks will be enumerated from both locations. +// +// The SDKs are sorted in ascending order by version, multi-level lookup +// locations are put before private ones. +// +// The frameworks are sorted in ascending order by name followed by version, +// multi-level lookup locations are put before private ones. +// +// Parameters: +// dotnet_root +// The path to a directory containing a dotnet executable. +// +// reserved +// Reserved for future parameters. +// +// result +// Callback invoke to return the list of SDKs and frameworks. +// Structs and their elements are valid for the duration of the call. +// +// result_context +// Additional context passed to the result callback. +// +// Return value: +// 0 on success, otherwise failure. +// +// String encoding: +// Windows - UTF-16 (pal::char_t is 2 byte wchar_t) +// Unix - UTF-8 (pal::char_t is 1 byte char) +// +SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_get_dotnet_environment_info( + const pal::char_t* dotnet_root, + void* reserved, + hostfxr_get_dotnet_environment_info_result_fn result, + void* result_context) +{ + if (result == nullptr) + { + trace::error(_X("hostfxr_get_dotnet_environment_info received an invalid argument: result should not be null.")); + return StatusCode::InvalidArgFailure; + } + + if (reserved != nullptr) + { + trace::error(_X("hostfxr_get_dotnet_environment_info received an invalid argument: reserved should be null.")); + return StatusCode::InvalidArgFailure; + } + + pal::string_t dotnet_dir; + if (dotnet_root == nullptr) + { + if (pal::get_dotnet_self_registered_dir(&dotnet_dir) || pal::get_default_installation_dir(&dotnet_dir)) + { + trace::info(_X("Using global installation location [%s]."), dotnet_dir.c_str()); + } + else + { + trace::info(_X("No default dotnet installation could be obtained.")); + } + } + else + { + dotnet_dir = dotnet_root; + } + + std::vector sdk_infos; + sdk_info::get_all_sdk_infos(dotnet_dir, &sdk_infos); + + std::vector environment_sdk_infos; + std::vector sdk_versions; + if (!sdk_infos.empty()) + { + environment_sdk_infos.reserve(sdk_infos.size()); + sdk_versions.reserve(sdk_infos.size()); + for (const sdk_info& info : sdk_infos) + { + sdk_versions.push_back(info.version.as_str()); + hostfxr_dotnet_environment_sdk_info sdk + { + sizeof(hostfxr_dotnet_environment_sdk_info), + sdk_versions.back().c_str(), + info.full_path.c_str() + }; + + environment_sdk_infos.push_back(sdk); + } + } + + std::vector framework_infos; + framework_info::get_all_framework_infos(dotnet_dir, _X(""), &framework_infos); + + std::vector environment_framework_infos; + std::vector framework_versions; + if (!framework_infos.empty()) + { + environment_framework_infos.reserve(framework_infos.size()); + framework_versions.reserve(framework_infos.size()); + for (const framework_info& info : framework_infos) + { + framework_versions.push_back(info.version.as_str()); + hostfxr_dotnet_environment_framework_info fw + { + sizeof(hostfxr_dotnet_environment_framework_info), + info.name.c_str(), + framework_versions.back().c_str(), + info.path.c_str() + }; + + environment_framework_infos.push_back(fw); + } + } + + const hostfxr_dotnet_environment_info environment_info + { + sizeof(hostfxr_dotnet_environment_info), + _STRINGIFY(HOST_FXR_PKG_VER), + _STRINGIFY(REPO_COMMIT_HASH), + environment_sdk_infos.size(), + (environment_sdk_infos.empty()) ? nullptr : &environment_sdk_infos[0], + environment_framework_infos.size(), + (environment_framework_infos.empty()) ? nullptr : &environment_framework_infos[0] + }; + + result(&environment_info, result_context); + return StatusCode::Success; +} + +// // Returns the native directories of the runtime based upon // the specified app. // diff --git a/src/installer/corehost/cli/fxr/standalone/hostfxr.def b/src/installer/corehost/cli/fxr/standalone/hostfxr.def index e9a8602..3488a4f 100644 --- a/src/installer/corehost/cli/fxr/standalone/hostfxr.def +++ b/src/installer/corehost/cli/fxr/standalone/hostfxr.def @@ -8,6 +8,7 @@ EXPORTS hostfxr_resolve_sdk hostfxr_resolve_sdk2 hostfxr_get_available_sdks + hostfxr_get_dotnet_environment_info hostfxr_get_native_search_directories hostfxr_set_error_writer hostfxr_initialize_for_dotnet_command_line diff --git a/src/installer/corehost/cli/fxr/standalone/hostfxr_unixexports.src b/src/installer/corehost/cli/fxr/standalone/hostfxr_unixexports.src index 105135d..1780b01 100644 --- a/src/installer/corehost/cli/fxr/standalone/hostfxr_unixexports.src +++ b/src/installer/corehost/cli/fxr/standalone/hostfxr_unixexports.src @@ -7,6 +7,7 @@ hostfxr_main hostfxr_resolve_sdk hostfxr_resolve_sdk2 hostfxr_get_available_sdks +hostfxr_get_dotnet_environment_info hostfxr_get_native_search_directories hostfxr_set_error_writer hostfxr_initialize_for_dotnet_command_line diff --git a/src/installer/corehost/cli/hostfxr.h b/src/installer/corehost/cli/hostfxr.h index e062193..2ef0af3 100644 --- a/src/installer/corehost/cli/hostfxr.h +++ b/src/installer/corehost/cli/hostfxr.h @@ -285,4 +285,37 @@ typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_delegate_fn)( // typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_close_fn)(const hostfxr_handle host_context_handle); +struct hostfxr_dotnet_environment_sdk_info +{ + size_t size; + const char_t* version; + const char_t* path; +}; + +typedef void(HOSTFXR_CALLTYPE* hostfxr_get_dotnet_environment_info_result_fn)( + const struct hostfxr_dotnet_environment_info* info, + void* result_context); + +struct hostfxr_dotnet_environment_framework_info +{ + size_t size; + const char_t* name; + const char_t* version; + const char_t* path; +}; + +struct hostfxr_dotnet_environment_info +{ + size_t size; + + const char_t* hostfxr_version; + const char_t* hostfxr_commit_hash; + + int32_t sdk_count; + const hostfxr_dotnet_environment_sdk_info* sdks; + + int32_t framework_count; + const hostfxr_dotnet_environment_framework_info* frameworks; +}; + #endif //__HOSTFXR_H__ diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostFXR.cs b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostFXR.cs index 6e67f50..05eb905 100644 --- a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostFXR.cs +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostFXR.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; namespace HostApiInvokerApp @@ -23,6 +24,38 @@ namespace HostApiInvokerApp global_json_path = 1, } + [StructLayout(LayoutKind.Sequential, CharSet = Utils.OSCharSet)] + internal struct hostfxr_dotnet_environment_sdk_info + { + internal int size; + internal string version; + internal string path; + } + + [StructLayout(LayoutKind.Sequential, CharSet = Utils.OSCharSet)] + internal struct hostfxr_dotnet_environment_framework_info + { + internal int size; + internal string name; + internal string version; + internal string path; + } + + [StructLayout(LayoutKind.Sequential, CharSet = Utils.OSCharSet)] + internal struct hostfxr_dotnet_environment_info + { + internal int size; + + internal string hostfxr_version; + internal string hostfxr_commit_hash; + + internal int sdk_count; + internal IntPtr sdks; + + internal int framework_count; + internal IntPtr frameworks; + } + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = Utils.OSCharSet)] internal delegate void hostfxr_resolve_sdk2_result_fn( hostfxr_resolve_sdk2_result_key_t key, @@ -53,6 +86,18 @@ namespace HostApiInvokerApp [DllImport(nameof(hostfxr), CharSet = Utils.OSCharSet, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr hostfxr_set_error_writer( hostfxr_error_writer_fn error_writer); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = Utils.OSCharSet)] + internal delegate void hostfxr_get_dotnet_environment_info_result_fn( + IntPtr info, + IntPtr result_context); + + [DllImport(nameof(hostfxr), CharSet = Utils.OSCharSet, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern int hostfxr_get_dotnet_environment_info( + string dotnet_root, + IntPtr reserved, + hostfxr_get_dotnet_environment_info_result_fn result, + IntPtr result_context); } /// @@ -142,6 +187,84 @@ namespace HostApiInvokerApp } } + /// + /// Test that invokes native api hostfxr_get_dotnet_environment_info. + /// + /// hostfxr_get_dotnet_environment_info + /// (Optional) Path to the directory with dotnet.exe + static void Test_hostfxr_get_dotnet_environment_info(string[] args) + { + string dotnetExeDir = null; + if (args.Length >= 2) + dotnetExeDir = args[1]; + + string hostfxr_version; + string hostfxr_commit_hash; + List sdks = new List(); + List frameworks = new List(); + + hostfxr.hostfxr_get_dotnet_environment_info_result_fn result_fn = (IntPtr info, IntPtr result_context) => + { + hostfxr.hostfxr_dotnet_environment_info environment_info = Marshal.PtrToStructure(info); + + hostfxr_version = environment_info.hostfxr_version; + hostfxr_commit_hash = environment_info.hostfxr_commit_hash; + + int env_info_size = Marshal.SizeOf(environment_info); + if (env_info_size != environment_info.size) + throw new Exception($"Size field value of hostfxr_dotnet_environment_info struct is {environment_info.size} but {env_info_size} was expected."); + + for (int i = 0; i < environment_info.sdk_count; i++) + { + IntPtr pSdkInfo = new IntPtr(environment_info.sdks.ToInt64() + (i * Marshal.SizeOf())); + sdks.Add(Marshal.PtrToStructure(pSdkInfo)); + + if (Marshal.SizeOf(sdks[i]) != sdks[i].size) + throw new Exception($"Size field value of hostfxr_dotnet_environment_sdk_info struct is {sdks[i].size} but {Marshal.SizeOf(sdks[i])} was expected."); + } + + for (int i = 0; i < environment_info.framework_count; i++) + { + IntPtr pFrameworkInfo = new IntPtr(environment_info.frameworks.ToInt64() + (i * Marshal.SizeOf())); + frameworks.Add(Marshal.PtrToStructure(pFrameworkInfo)); + + if (Marshal.SizeOf(frameworks[i]) != frameworks[i].size) + throw new Exception($"Size field value of hostfxr_dotnet_environment_framework_info struct is {frameworks[i].size} but {Marshal.SizeOf(frameworks[i])} was expected."); + } + + long result_context_as_int = result_context.ToInt64(); + if (result_context_as_int != 42) + throw new Exception($"Invalid result_context value: expected 42 but was {result_context_as_int}."); + }; + + if (dotnetExeDir == "test_invalid_result_ptr") + result_fn = null; + + IntPtr reserved_ptr = IntPtr.Zero; + if (dotnetExeDir == "test_invalid_reserved_ptr") + reserved_ptr = new IntPtr(11); + + int rc = hostfxr.hostfxr_get_dotnet_environment_info( + dotnet_root: dotnetExeDir, + reserved: reserved_ptr, + result: result_fn, + result_context: new IntPtr(42)); + + if (rc != 0) + { + Console.WriteLine($"hostfxr_get_dotnet_environment_info:Fail[{rc}]"); + } + + Console.WriteLine($"hostfxr_get_dotnet_environment_info sdk versions:[{string.Join(";", sdks.Select(s => s.version).ToList())}]"); + Console.WriteLine($"hostfxr_get_dotnet_environment_info sdk paths:[{string.Join(";", sdks.Select(s => s.path).ToList())}]"); + + Console.WriteLine($"hostfxr_get_dotnet_environment_info framework names:[{string.Join(";", frameworks.Select(f => f.name).ToList())}]"); + Console.WriteLine($"hostfxr_get_dotnet_environment_info framework versions:[{string.Join(";", frameworks.Select(f => f.version).ToList())}]"); + Console.WriteLine($"hostfxr_get_dotnet_environment_info framework paths:[{string.Join(";", frameworks.Select(f => f.path).ToList())}]"); + + Console.WriteLine("hostfxr_get_dotnet_environment_info:Success"); + } + public static bool RunTest(string apiToTest, string[] args) { switch (apiToTest) @@ -155,6 +278,9 @@ namespace HostApiInvokerApp case nameof(Test_hostfxr_set_error_writer): Test_hostfxr_set_error_writer(args); break; + case nameof(hostfxr.hostfxr_get_dotnet_environment_info): + Test_hostfxr_get_dotnet_environment_info(args); + break; default: return false; } diff --git a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs index 27d3b27..a213579 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs @@ -3,6 +3,7 @@ using Microsoft.DotNet.Cli.Build; using System; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using Xunit; @@ -66,12 +67,26 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation public string SelfRegistered => Path.Combine(ExeDir, "sr"); public string WorkingDir => Path.Combine(_fixture.TestProject.ProjectDirectory, "wd"); public string ProgramFilesGlobalSdkDir => Path.Combine(ProgramFiles, "dotnet", "sdk"); + public string ProgramFilesGlobalFrameworksDir => Path.Combine(ProgramFiles, "dotnet", "shared"); public string SelfRegisteredGlobalSdkDir => Path.Combine(SelfRegistered, "sdk"); public string LocalSdkDir => Path.Combine(ExeDir, "sdk"); + public string LocalFrameworksDir => Path.Combine(ExeDir, "shared"); public string GlobalJson => Path.Combine(WorkingDir, "global.json"); public string[] ProgramFilesGlobalSdks = new[] { "4.5.6", "1.2.3", "2.3.4-preview" }; + public List<(string fwName, string[] fwVersions)> ProgramFilesGlobalFrameworks = + new List<(string fwName, string[] fwVersions)>() + { + ("HostFxr.Test.A", new[] { "1.2.3", "3.0.0" }), + ("HostFxr.Test.B", new[] { "5.6.7-A" }) + }; public string[] SelfRegisteredGlobalSdks = new[] { "3.0.0", "15.1.4-preview", "5.6.7" }; public string[] LocalSdks = new[] { "0.1.2", "5.6.7-preview", "1.2.3" }; + public List<(string fwName, string[] fwVersions)> LocalFrameworks = + new List<(string fwName, string[] fwVersions)>() + { + ("HostFxr.Test.B", new[] { "4.0.0", "5.6.7-A" }), + ("HostFxr.Test.C", new[] { "3.0.0" }) + }; public SdkResolutionFixture(SharedTestState state) { @@ -98,18 +113,29 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation { Directory.CreateDirectory(Path.Combine(LocalSdkDir, sdk)); } - } + + foreach ((string fwName, string[] fwVersions) in ProgramFilesGlobalFrameworks) + { + foreach (string fwVersion in fwVersions) + Directory.CreateDirectory(Path.Combine(ProgramFilesGlobalFrameworksDir, fwName, fwVersion)); + } + foreach ((string fwName, string[] fwVersions) in LocalFrameworks) + { + foreach (string fwVersion in fwVersions) + Directory.CreateDirectory(Path.Combine(LocalFrameworksDir, fwName, fwVersion)); + } + } } [Fact] public void Hostfxr_get_available_sdks_with_multilevel_lookup() { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // multilevel lookup is not supported on non-Windows return; } - + var f = new SdkResolutionFixture(sharedTestState); // With multi-level lookup (windows only): get local and global sdks sorted by ascending version, @@ -244,6 +270,258 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation } [Fact] + public void Hostfxr_get_dotnet_environment_info_dotnet_root_only() + { + var f = new SdkResolutionFixture(sharedTestState); + string expectedSdkVersions = string.Join(";", new[] + { + "0.1.2", + "1.2.3", + "5.6.7-preview" + }); + + string expectedSdkPaths = string.Join(';', new[] + { + Path.Combine(f.LocalSdkDir, "0.1.2"), + Path.Combine(f.LocalSdkDir, "1.2.3"), + Path.Combine(f.LocalSdkDir, "5.6.7-preview"), + }); + + string expectedFrameworkNames = string.Join(';', new[] + { + "HostFxr.Test.B", + "HostFxr.Test.B", + "HostFxr.Test.C" + }); + + string expectedFrameworkVersions = string.Join(';', new[] + { + "4.0.0", + "5.6.7-A", + "3.0.0" + }); + + string expectedFrameworkPaths = string.Join(';', new[] + { + Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"), + Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"), + Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.C") + }); + + f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_get_dotnet_environment_info", f.ExeDir }) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("hostfxr_get_dotnet_environment_info:Success") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk versions:[{expectedSdkVersions}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk paths:[{expectedSdkPaths}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework names:[{expectedFrameworkNames}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework versions:[{expectedFrameworkVersions}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework paths:[{expectedFrameworkPaths}]"); + } + + [Fact] + public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_with_dotnet_root() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Only Windows supports multi-level lookup. + return; + } + + var f = new SdkResolutionFixture(sharedTestState); + string expectedSdkVersions = string.Join(';', new[] + { + "0.1.2", + "1.2.3", + "1.2.3", + "2.3.4-preview", + "3.0.0", + "4.5.6", + "5.6.7-preview", + "5.6.7", + "15.1.4-preview" + }); + + string expectedSdkPaths = string.Join(';', new[] + { + Path.Combine(f.LocalSdkDir, "0.1.2"), + Path.Combine(f.ProgramFilesGlobalSdkDir, "1.2.3"), + Path.Combine(f.LocalSdkDir, "1.2.3"), + Path.Combine(f.ProgramFilesGlobalSdkDir, "2.3.4-preview"), + Path.Combine(f.SelfRegisteredGlobalSdkDir, "3.0.0"), + Path.Combine(f.ProgramFilesGlobalSdkDir, "4.5.6"), + Path.Combine(f.LocalSdkDir, "5.6.7-preview"), + Path.Combine(f.SelfRegisteredGlobalSdkDir, "5.6.7"), + Path.Combine(f.SelfRegisteredGlobalSdkDir, "15.1.4-preview"), + }); + + string expectedFrameworkNames = string.Join(';', new[] + { + "HostFxr.Test.A", + "HostFxr.Test.A", + "HostFxr.Test.B", + "HostFxr.Test.B", + "HostFxr.Test.B", + "HostFxr.Test.C" + }); + + string expectedFrameworkVersions = string.Join(';', new[] + { + "1.2.3", + "3.0.0", + "4.0.0", + "5.6.7-A", + "5.6.7-A", + "3.0.0" + }); + + string expectedFrameworkPaths = string.Join(';', new[] + { + Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), + Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), + Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"), + Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.B"), + Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"), + Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.C") + }); + + using (TestOnlyProductBehavior.Enable(f.Dotnet.GreatestVersionHostFxrFilePath)) + { + f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_get_dotnet_environment_info", f.ExeDir }) + .EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES", f.ProgramFiles) + .EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED", f.SelfRegistered) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("hostfxr_get_dotnet_environment_info:Success") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk versions:[{expectedSdkVersions}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk paths:[{expectedSdkPaths}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework names:[{expectedFrameworkNames}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework versions:[{expectedFrameworkVersions}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework paths:[{expectedFrameworkPaths}]"); + } + } + + [Fact] + public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_only() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Only Windows supports multi-level lookup. + return; + } + + var f = new SdkResolutionFixture(sharedTestState); + string expectedSdkVersions = string.Join(';', new[] + { + "1.2.3", + "2.3.4-preview", + "3.0.0", + "4.5.6", + "5.6.7", + "15.1.4-preview" + }); + + string expectedSdkPaths = string.Join(';', new[] + { + Path.Combine(f.ProgramFilesGlobalSdkDir, "1.2.3"), + Path.Combine(f.ProgramFilesGlobalSdkDir, "2.3.4-preview"), + Path.Combine(f.SelfRegisteredGlobalSdkDir, "3.0.0"), + Path.Combine(f.ProgramFilesGlobalSdkDir, "4.5.6"), + Path.Combine(f.SelfRegisteredGlobalSdkDir, "5.6.7"), + Path.Combine(f.SelfRegisteredGlobalSdkDir, "15.1.4-preview"), + }); + + string expectedFrameworkNames = string.Join(';', new[] + { + "HostFxr.Test.A", + "HostFxr.Test.A", + "HostFxr.Test.B", + }); + + string expectedFrameworkVersions = string.Join(';', new[] + { + "1.2.3", + "3.0.0", + "5.6.7-A", + }); + + string expectedFrameworkPaths = string.Join(';', new[] + { + Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), + Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.A"), + Path.Combine(f.ProgramFilesGlobalFrameworksDir, "HostFxr.Test.B"), + }); + + using (TestOnlyProductBehavior.Enable(f.Dotnet.GreatestVersionHostFxrFilePath)) + { + // We pass f.WorkingDir so that we don't resolve dotnet_dir to the global installation + // in the native side. + f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_get_dotnet_environment_info", f.WorkingDir }) + .EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES", f.ProgramFiles) + .EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED", f.SelfRegistered) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("hostfxr_get_dotnet_environment_info:Success") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk versions:[{expectedSdkVersions}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info sdk paths:[{expectedSdkPaths}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework names:[{expectedFrameworkNames}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework versions:[{expectedFrameworkVersions}]") + .And.HaveStdOutContaining($"hostfxr_get_dotnet_environment_info framework paths:[{expectedFrameworkPaths}]"); + } + } + + [Fact] + public void Hostfxr_get_dotnet_environment_info_global_install_path() + { + var f = new SdkResolutionFixture(sharedTestState); + + f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_get_dotnet_environment_info" }) + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("hostfxr_get_dotnet_environment_info:Success"); + } + + [Fact] + public void Hostfxr_get_dotnet_environment_info_result_is_nullptr_fails() + { + var f = new SdkResolutionFixture(sharedTestState); + + f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_get_dotnet_environment_info", "test_invalid_result_ptr" }) + .EnvironmentVariable("COREHOST_TRACE", "1") + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + // 0x80008081 (InvalidArgFailure) + .And.HaveStdOutContaining("hostfxr_get_dotnet_environment_info:Fail[-2147450751]") + .And.HaveStdErrContaining("hostfxr_get_dotnet_environment_info received an invalid argument: result should not be null."); + } + + [Fact] + public void Hostfxr_get_dotnet_environment_info_reserved_is_not_nullptr_fails() + { + var f = new SdkResolutionFixture(sharedTestState); + + f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_get_dotnet_environment_info", "test_invalid_reserved_ptr" }) + .EnvironmentVariable("COREHOST_TRACE", "1") + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + // 0x80008081 (InvalidArgFailure) + .And.HaveStdOutContaining("hostfxr_get_dotnet_environment_info:Fail[-2147450751]") + .And.HaveStdErrContaining("hostfxr_get_dotnet_environment_info received an invalid argument: reserved should be null."); + } + + [Fact] public void Hostpolicy_corehost_set_error_writer_test() { var fixture = sharedTestState.HostApiInvokerAppFixture.Copy(); @@ -295,7 +573,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr")); FileUtils.CopyIntoDirectory( - hostfxr, + hostfxr, Path.GetDirectoryName(fixture.TestProject.AppDll)); } } -- 2.7.4