#include "error_codes.h"
#include "libhost.h"
#include "runtime_config.h"
+#include "sdk_info.h"
#include "sdk_resolver.h"
typedef int(*corehost_load_fn) (const host_interface_t* init);
return muxer.execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr);
}
+// [OBSOLETE] Replaced by hostfxr_resolve_sdk2
//
// Determines the directory location of the SDK accounting for
// global.json and multi-level lookup policy.
return cli_sdk.size() + 1;
}
+enum hostfxr_resolve_sdk2_flags_t : int32_t
+{
+ disallow_prerelease = 0x1,
+};
+
+enum class hostfxr_resolve_sdk2_result_key_t : int32_t
+{
+ resolved_sdk_dir = 0,
+ global_json_path = 1,
+};
+
+typedef void (*hostfxr_resolve_sdk2_result_fn)(
+ hostfxr_resolve_sdk2_result_key_t key,
+ const pal::char_t* value);
+
+//
+// Determines the directory location of the SDK accounting for
+// global.json and multi-level lookup policy.
+//
+// Invoked via MSBuild SDK resolver to locate SDK props and targets
+// from an msbuild other than the one bundled by the CLI.
+//
+// Parameters:
+// exe_dir
+// The main directory where SDKs are located in sdk\[version]
+// sub-folders. Pass the directory of a dotnet executable to
+// mimic how that executable would search in its own directory.
+// It is also valid to pass nullptr or empty, in which case
+// multi-level lookup can still search other locations if
+// it has not been disabled by the user's environment.
+//
+// working_dir
+// The directory where the search for global.json (which can
+// control the resolved SDK version) starts and proceeds
+// upwards.
+//
+// flags
+// Bitwise flags that influence resolution.
+// disallow_prerelease (0x1)
+// do not allow resolution to return a prerelease SDK version
+// unless prerelease version was specified via global.json.
+//
+// result
+// Callback invoked to return values. It can be invoked more
+// than once. String values passed are valid only for the
+// duration of a call.
+//
+// If resolution succeeds, result will be invoked with
+// resolved_sdk_dir key and the value will hold the
+// path to the resolved SDK director, otherwise it will
+// be null.
+//
+// If global.json is used then result will be invoked with
+// global_json_path key and the value will hold the path
+// to global.json. If there was no global.json found,
+// or the contents of global.json did not impact resolution
+// (e.g. no version specified), then result will not be
+// invoked with global_json_path key.
+//
+// Return value:
+// 0 on success, otherwise failure
+// 0x8000809b - SDK could not be resolved (SdkResolverResolveFailure)
+//
+// 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_resolve_sdk2(
+ const pal::char_t* exe_dir,
+ const pal::char_t* working_dir,
+ int32_t flags,
+ hostfxr_resolve_sdk2_result_fn result)
+{
+ trace::setup();
+
+ trace::info(_X("--- Invoked hostfxr [commit hash: %s] hostfxr_resolve_sdk2"), _STRINGIFY(REPO_COMMIT_HASH));
+
+ if (exe_dir == nullptr)
+ {
+ exe_dir = _X("");
+ }
+
+ if (working_dir == nullptr)
+ {
+ working_dir = _X("");
+ }
+
+ pal::string_t resolved_sdk_dir;
+ pal::string_t global_json_path;
+
+ bool success = sdk_resolver_t::resolve_sdk_dotnet_path(
+ exe_dir,
+ working_dir,
+ &resolved_sdk_dir,
+ (flags & hostfxr_resolve_sdk2_flags_t::disallow_prerelease) != 0,
+ &global_json_path);
+
+ if (success)
+ {
+ result(
+ hostfxr_resolve_sdk2_result_key_t::resolved_sdk_dir,
+ resolved_sdk_dir.c_str());
+ }
+
+ if (!global_json_path.empty())
+ {
+ result(
+ hostfxr_resolve_sdk2_result_key_t::global_json_path,
+ global_json_path.c_str());
+ }
+
+ return success
+ ? StatusCode::Success
+ : StatusCode::SdkResolverResolveFailure;
+}
+
+
+typedef void (*hostfxr_get_available_sdks_result_fn)(
+ int32_t sdk_count,
+ const pal::char_t *sdk_dirs[]);
+
+//
+// Returns the list of all available SDKs ordered by ascending version.
+//
+// Invoked by MSBuild resolver when the latest SDK used without global.json
+// present is incompatible with the current MSBuild version. It will select
+// the compatible SDK that is closest to the end of this list.
+//
+// Parameters:
+// exe_dir
+// The path to the dotnet executable.
+//
+// result
+// Callback invoke to return the list of SDKs by their directory paths.
+// String array and its elements are valid for the duration of the call.
+//
+// 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_get_available_sdks(
+ const pal::char_t* exe_dir,
+ hostfxr_get_available_sdks_result_fn result)
+{
+ trace::setup();
+
+ trace::info(_X("--- Invoked hostfxr [commit hash: %s] hostfxr_get_available_sdks"), _STRINGIFY(REPO_COMMIT_HASH));
+
+ if (exe_dir == nullptr)
+ {
+ exe_dir = _X("");
+ }
+
+ std::vector<sdk_info> sdk_infos;
+ sdk_info::get_all_sdk_infos(exe_dir, &sdk_infos);
+
+ if (sdk_infos.empty())
+ {
+ result(0, nullptr);
+ }
+ else
+ {
+ std::vector<const pal::char_t*> sdk_dirs;
+ sdk_dirs.reserve(sdk_infos.size());
+
+ for (const auto& sdk_info : sdk_infos)
+ {
+ sdk_dirs.push_back(sdk_info.full_path.c_str());
+ }
+
+ result(sdk_dirs.size(), &sdk_dirs[0]);
+ }
+
+ return StatusCode::Success;
+}
+
//
// Returns the native directories of the runtime based upon
// the specified app.
#include "trace.h"
#include "utils.h"
-bool compare_by_version(const sdk_info &a, const sdk_info &b)
+bool compare_by_version_ascending_then_hive_depth_descending(const sdk_info &a, const sdk_info &b)
{
- return a.version < b.version;
+ if (a.version < b.version)
+ {
+ return true;
+ }
+
+ // With multi-level lookup enabled, it is possible to find two SDKs with
+ // the same version. For that edge case, we make the ordering put SDKs
+ // from farther away (global location) hives earlier than closer ones
+ // (current dotnet exe location). Without this tie-breaker, the ordering
+ // would be non-deterministic.
+ //
+ // Furthermore, nearer earlier than farther is so that the MSBuild resolver
+ // can do a linear search from the end of the list to the front to find the
+ // best compatible SDK.
+ //
+ // Example:
+ // * dotnet dir has version 4.0, 5.0, 6.0
+ // * global dir has 5.0
+ // * 6.0 is incompatible with calling msbuild
+ // * 5.0 is compatible with calling msbuild
+ //
+ // MSBuild should select 5.0 from dotnet dir (matching probe order) in muxer
+ // and not 5.0 from global dir.
+ if (a.version == b.version)
+ {
+ return a.hive_depth > b.hive_depth;
+ }
+
+ return false;
}
void sdk_info::get_all_sdk_infos(
}
}
+ int32_t hive_depth = 0;
+
for (pal::string_t dir : hive_dir)
{
- auto sdk_dir = dir;
- trace::verbose(_X("Gathering SDK locations in [%s]"), sdk_dir.c_str());
+ auto base_dir = dir;
+ trace::verbose(_X("Gathering SDK locations in [%s]"), base_dir.c_str());
- append_path(&sdk_dir, _X("sdk"));
+ append_path(&base_dir, _X("sdk"));
- if (pal::directory_exists(sdk_dir))
+ if (pal::directory_exists(base_dir))
{
std::vector<pal::string_t> versions;
- pal::readdir_onlydirectories(sdk_dir, &versions);
+ pal::readdir_onlydirectories(base_dir, &versions);
for (const auto& ver : versions)
{
// Make sure we filter out any non-version folders.
{
trace::verbose(_X("Found SDK version [%s]"), ver.c_str());
- sdk_info info(sdk_dir, parsed);
+ auto full_dir = base_dir;
+ append_path(&full_dir, ver.c_str());
+
+ sdk_info info(base_dir, full_dir, parsed, hive_depth);
sdk_infos->push_back(info);
}
}
}
+
+ hive_depth++;
}
- std::sort(sdk_infos->begin(), sdk_infos->end(), compare_by_version);
+ std::sort(sdk_infos->begin(), sdk_infos->end(), compare_by_version_ascending_then_hive_depth_descending);
}
/*static*/ bool sdk_info::print_all_sdks(const pal::string_t& own_dir, const pal::string_t& leading_whitespace)
get_all_sdk_infos(own_dir, &sdk_infos);
for (sdk_info info : sdk_infos)
{
- trace::println(_X("%s%s [%s]"), leading_whitespace.c_str(), info.version.as_str().c_str(), info.path.c_str());
+ trace::println(_X("%s%s [%s]"), leading_whitespace.c_str(), info.version.as_str().c_str(), info.base_path.c_str());
}
return sdk_infos.size() > 0;
struct sdk_info
{
- sdk_info(pal::string_t path, fx_ver_t version)
- : path(path)
- , version(version) { }
+ sdk_info(const pal::string_t& base_path, const pal::string_t& full_path, const fx_ver_t& version, int32_t hive_depth)
+ : base_path(base_path)
+ , full_path(full_path)
+ , version(version)
+ , hive_depth(hive_depth) { }
static void get_all_sdk_infos(
const pal::string_t& own_dir,
static bool print_all_sdks(const pal::string_t& own_dir, const pal::string_t& leading_whitespace);
- pal::string_t path;
+ pal::string_t base_path;
+ pal::string_t full_path;
fx_ver_t version;
+ int32_t hive_depth;
};
#endif // __SDK_INFO_H_
return retval;
}
-pal::string_t resolve_sdk_version(pal::string_t sdk_path, bool parse_only_production, pal::string_t global_cli_version)
+pal::string_t resolve_sdk_version(pal::string_t sdk_path, bool disallow_prerelease, pal::string_t global_cli_version)
{
fx_ver_t specified(-1, -1, -1);
trace::error(_X("The specified SDK version '%s' could not be parsed"), global_cli_version.c_str());
return pal::string_t();
}
+
+ // Always consider prereleases when the version specified in global.json is itself a prerelease
+ if (specified.is_prerelease())
+ {
+ disallow_prerelease = false;
+ }
}
trace::verbose(_X("--- Resolving SDK version from SDK dir [%s]"), sdk_path.c_str());
trace::verbose(_X("Considering version... [%s]"), version.c_str());
fx_ver_t ver(-1, -1, -1);
- if (fx_ver_t::parse(version, &ver, parse_only_production))
+ if (fx_ver_t::parse(version, &ver, disallow_prerelease))
{
if (global_cli_version.empty() ||
// If a global cli version is specified:
return resolve_sdk_dotnet_path(dotnet_root, cwd, cli_sdk);
}
-bool higher_sdk_version(const pal::string_t& new_version, pal::string_t* version, bool parse_only_production)
+bool higher_sdk_version(const pal::string_t& new_version, pal::string_t* version)
{
+ bool disallow_prerelease = false;
bool retval = false;
fx_ver_t ver(-1, -1, -1);
fx_ver_t new_ver(-1, -1, -1);
- if (fx_ver_t::parse(new_version, &new_ver, parse_only_production))
+ if (fx_ver_t::parse(new_version, &new_ver, disallow_prerelease))
{
- if (!fx_ver_t::parse(*version, &ver, parse_only_production) || (new_ver > ver))
+ if (!fx_ver_t::parse(*version, &ver, disallow_prerelease) || (new_ver > ver))
{
version->assign(new_version);
retval = true;
return retval;
}
-bool sdk_resolver_t::resolve_sdk_dotnet_path(const pal::string_t& dotnet_root, const pal::string_t& cwd, pal::string_t* cli_sdk)
+bool sdk_resolver_t::resolve_sdk_dotnet_path(
+ const pal::string_t& dotnet_root,
+ const pal::string_t& cwd,
+ pal::string_t* cli_sdk,
+ bool disallow_prerelease,
+ pal::string_t* global_json_path)
{
pal::string_t global;
trace::verbose(_X("Searching SDK directory in [%s]"), dir.c_str());
pal::string_t current_sdk_path = dir;
append_path(¤t_sdk_path, _X("sdk"));
- bool parse_only_production = false; // false -- implies both production and prerelease.
if (global_cli_version.empty())
{
- pal::string_t new_cli_version = resolve_sdk_version(current_sdk_path, parse_only_production, global_cli_version);
- if (higher_sdk_version(new_cli_version, &cli_version, parse_only_production))
+ pal::string_t new_cli_version = resolve_sdk_version(current_sdk_path, disallow_prerelease, global_cli_version);
+ if (higher_sdk_version(new_cli_version, &cli_version))
{
sdk_path = current_sdk_path;
}
}
else
{
+ if (global_json_path != nullptr)
+ {
+ global_json_path->assign(global);
+ }
+
pal::string_t probing_sdk_path = current_sdk_path;
append_path(&probing_sdk_path, global_cli_version.c_str());
}
else
{
- pal::string_t new_cli_version = resolve_sdk_version(current_sdk_path, parse_only_production, global_cli_version);
- if (higher_sdk_version(new_cli_version, &cli_version, parse_only_production))
+ pal::string_t new_cli_version = resolve_sdk_version(current_sdk_path, disallow_prerelease, global_cli_version);
+ if (higher_sdk_version(new_cli_version, &cli_version))
{
sdk_path = current_sdk_path;
}
static bool resolve_sdk_dotnet_path(
const pal::string_t& dotnet_root,
- const pal::string_t& cwd,
- pal::string_t* cli_sdk);
+ const pal::string_t& cwd,
+ pal::string_t* cli_sdk,
+ bool disallow_prerelease = false,
+ pal::string_t* global_json_path = nullptr);
};
HostApiBufferTooSmall = 0x80008098,
LibHostUnknownCommand = 0x80008099,
LibHostAppRootFindFailure = 0x8000809a,
+ SdkResolverResolveFailure = 0x8000809b,
};
#endif // __ERROR_CODES_H__
<TargetFramework>$(NETCoreAppFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeFrameworkVersion>$(MNAVersion)</RuntimeFrameworkVersion>
+ <LangVersion>Latest</LangVersion>
+ <DefineConstants Condition="'$(OS)' == 'Windows_NT'">WINDOWS;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
using System;
+using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
{
public static class Program
{
- [DllImport("hostfxr", CharSet = CharSet.Unicode)]
- static extern uint hostfxr_get_native_search_directories(int argc, IntPtr argv, StringBuilder buffer, int bufferSize, ref int required_buffer_size);
+#if WINDOWS
+ const CharSet OSCharSet = CharSet.Unicode;
+#else
+ const CharSet OSCharSet = CharSet.Ansi; // actually UTF8 on Unix
+#endif
+
+ [DllImport("hostfxr", CharSet = OSCharSet)]
+ static extern uint hostfxr_get_native_search_directories(
+ int argc,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
+ string[] argv,
+ StringBuilder buffer,
+ int bufferSize,
+ ref int required_buffer_size);
+
+ [Flags]
+ internal enum hostfxr_resolve_sdk2_flags_t : int
+ {
+ disallow_prerelease = 0x1,
+ }
+
+ internal enum hostfxr_resolve_sdk2_result_key_t : int
+ {
+ resolved_sdk_dir = 0,
+ global_json_path = 1,
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = OSCharSet)]
+ internal delegate void hostfxr_resolve_sdk2_result_fn(
+ hostfxr_resolve_sdk2_result_key_t key,
+ string value);
+
+ [DllImport("hostfxr", CharSet = OSCharSet, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int hostfxr_resolve_sdk2(
+ string exe_dir,
+ string working_dir,
+ hostfxr_resolve_sdk2_flags_t flags,
+ hostfxr_resolve_sdk2_result_fn result);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = OSCharSet)]
+ internal delegate void hostfxr_get_available_sdks_result_fn(
+ int sdk_count,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
+ string[] sdk_dirs);
+
+ [DllImport("hostfxr", CharSet = OSCharSet, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int hostfxr_get_available_sdks(
+ string exe_dir,
+ hostfxr_get_available_sdks_result_fn result);
const uint HostApiBufferTooSmall = 0x80008098;
- public static void Main(string[] args)
+ public static int Main(string[] args)
+ {
+ // write exception details to stdout so tha they can be seen in test assertion failures.
+ try
+ {
+ MainCore(args);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ public static void MainCore(string[] args)
{
Console.WriteLine("Hello World!");
Console.WriteLine(string.Join(Environment.NewLine, args));
// A small operation involving NewtonSoft.Json to ensure the assembly is loaded properly
var t = typeof(Newtonsoft.Json.JsonReader);
+ // Enable tracing so that test assertion failures are easier to diagnose.
+ Environment.SetEnvironmentVariable("COREHOST_TRACE", "1");
+
+ // If requested, test multilevel lookup using fake ProgramFiles location.
+ // Note that this has to be set here and not in the calling test process because
+ // %ProgramFiles% gets reset on process creation.
+ string testMultilevelLookupProgramFiles = Environment.GetEnvironmentVariable(
+ "TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES");
+
+ if (testMultilevelLookupProgramFiles != null)
+ {
+ Environment.SetEnvironmentVariable("ProgramFiles", testMultilevelLookupProgramFiles);
+ Environment.SetEnvironmentVariable("ProgramFiles(x86)", testMultilevelLookupProgramFiles);
+ Environment.SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "1");
+ }
+ else
+ {
+ // never rely on machine state in test if we're not faking the multi-level lookup
+ Environment.SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0");
+ }
+
if (args.Length == 0)
{
throw new Exception("Invalid number of arguments passed");
}
string apiToTest = args[0];
- if (apiToTest == "hostfxr_get_native_search_directories")
+ switch (apiToTest)
{
- Test_hostfxr_get_native_search_directories(args);
- }
- else
- {
- throw new Exception("Invalid args[0]");
+ case nameof(hostfxr_get_native_search_directories):
+ Test_hostfxr_get_native_search_directories(args);
+ break;
+ case nameof(hostfxr_resolve_sdk2):
+ Test_hostfxr_resolve_sdk2(args);
+ break;
+ case nameof(hostfxr_get_available_sdks):
+ Test_hostfxr_get_available_sdks(args);
+ break;
+ default:
+ throw new ArgumentException($"Invalid API to test passed as args[0]): {apiToTest}");
}
}
{
if (args.Length != 3)
{
- throw new Exception("Invalid number of arguments passed");
+ throw new ArgumentException("Invalid number of arguments passed");
}
string pathToDotnet = args[1];
string pathToApp = args[2];
-
- IntPtr[] argv = new IntPtr[2];
- argv[0] = Marshal.StringToHGlobalUni(pathToDotnet);
- argv[1] = Marshal.StringToHGlobalUni(pathToApp);
-
- GCHandle gch = GCHandle.Alloc(argv, GCHandleType.Pinned);
+ string[] argv = new[] { pathToDotnet, pathToApp };
// Start with 0 bytes allocated to test re-entry and required_buffer_size
StringBuilder buffer = new StringBuilder(0);
uint rc = 0;
for (int i = 0; i < 2; i++)
{
- rc = hostfxr_get_native_search_directories(argv.Length, gch.AddrOfPinnedObject(), buffer, buffer.Capacity + 1, ref required_buffer_size);
+ rc = hostfxr_get_native_search_directories(argv.Length, argv, buffer, buffer.Capacity + 1, ref required_buffer_size);
if (rc != HostApiBufferTooSmall)
{
break;
buffer = new StringBuilder(required_buffer_size);
}
- gch.Free();
- for (int i = 0; i < argv.Length; ++i)
+ if (rc == 0)
{
- Marshal.FreeHGlobal(argv[i]);
+ Console.WriteLine("hostfxr_get_native_search_directories:Success");
+ Console.WriteLine($"hostfxr_get_native_search_directories buffer:[{buffer}]");
}
+ else
+ {
+ Console.WriteLine($"hostfxr_get_native_search_directories:Fail[{rc}]");
+ }
+ }
+
+ /// <summary>
+ /// Test invoking the native hostfxr api hostfxr_resolve_sdk2
+ /// </summary>
+ /// <param name="args[0]">hostfxr_get_available_sdks</param>
+ /// <param name="args[1]">Directory of dotnet executable</param>
+ /// <param name="args[2]">Working directory where search for global.json begins</param>
+ /// <param name="args[3]">Flags</param>
+ static void Test_hostfxr_resolve_sdk2(string[] args)
+ {
+ if (args.Length != 4)
+ {
+ throw new ArgumentException("Invalid number of arguments passed");
+ }
+
+ var data = new List<(hostfxr_resolve_sdk2_result_key_t, string)>();
+ int rc = hostfxr_resolve_sdk2(
+ exe_dir: args[1],
+ working_dir: args[2],
+ flags: Enum.Parse<hostfxr_resolve_sdk2_flags_t>(args[3]),
+ result: (key, value) => data.Add((key, value)));
if (rc == 0)
{
- Console.WriteLine("hostfxr_get_native_search_directories:Success");
- Console.WriteLine($"hostfxr_get_native_search_directories buffer:[{buffer}]");
+ Console.WriteLine("hostfxr_resolve_sdk2:Success");
+ }
+ else
+ {
+ Console.WriteLine($"hostfxr_resolve_sdk2:Fail[{rc}]");
+ }
+
+ Console.WriteLine($"hostfxr_resolve_sdk2 data:[{string.Join(';', data)}]");
+ }
+
+ /// <summary>
+ /// Test invoking the native hostfxr api hostfxr_get_available_sdks
+ /// </summary>
+ /// <param name="args[0]">hostfxr_get_available_sdks</param>
+ /// <param name="args[1]">Directory of dotnet executable</param>
+ static void Test_hostfxr_get_available_sdks(string[] args)
+ {
+ if (args.Length != 2)
+ {
+ throw new ArgumentException("Invalid number of arguments passed");
+ }
+
+ string[] sdks = null;
+ int rc = hostfxr_get_available_sdks(
+ exe_dir: args[1],
+ (sdk_count, sdk_dirs) => sdks = sdk_dirs);
+
+ if (rc == 0)
+ {
+ Console.WriteLine("hostfxr_get_available_sdks:Success");
+ Console.WriteLine($"hostfxr_get_available_sdks sdks:[{string.Join(';', sdks)}]");
}
else
{
using Xunit;
using FluentAssertions;
using Microsoft.DotNet.CoreSetup.Test;
+using System.Collections.Generic;
+using Microsoft.DotNet.Cli.Build;
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHostApis
{
[Fact]
public void Muxer_activation_of_Publish_Output_Portable_DLL_hostfxr_get_native_search_directories_Succeeds()
{
- // Currently the native API is used only on Windows, although it has been manually tested on Unix.
- // Limit OS here to avoid issues with DllImport not being able to find the shared library.
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return;
- }
-
var fixture = sharedTestState.PreviouslyPublishedAndRestoredPortableApiTestProjectFixture.Copy();
var dotnet = fixture.BuiltDotnet;
var appDll = fixture.TestProject.AppDll;
dotnet.Exec(appDll, args)
.CaptureStdOut()
+ .CaptureStdErr()
.Execute()
.Should()
.Pass()
.NotHaveStdErrContaining("Waiting for breadcrumb thread to exit...");
}
+ private class SdkResolutionFixture
+ {
+ private readonly TestProjectFixture _fixture;
+
+ public DotNetCli Dotnet => _fixture.BuiltDotnet;
+ public string AppDll => _fixture.TestProject.AppDll;
+ public string ExeDir => Path.Combine(_fixture.TestProject.ProjectDirectory, "ed");
+ public string ProgramFiles => Path.Combine(ExeDir, "pf");
+ public string WorkingDir => Path.Combine(_fixture.TestProject.ProjectDirectory, "wd");
+ public string GlobalSdkDir => Path.Combine(ProgramFiles, "dotnet", "sdk");
+ public string LocalSdkDir => Path.Combine(ExeDir, "sdk");
+ public string GlobalJson => Path.Combine(WorkingDir, "global.json");
+ public string[] GlobalSdks = new[] { "4.5.6", "1.2.3", "2.3.4-preview" };
+ public string[] LocalSdks = new[] { "0.1.2", "5.6.7-preview", "1.2.3" };
+
+ public SdkResolutionFixture(SharedTestState state)
+ {
+ _fixture = state.PreviouslyPublishedAndRestoredPortableApiTestProjectFixture.Copy();
+
+ Directory.CreateDirectory(WorkingDir);
+
+ // start with an empty global.json, it will be ignored, but prevent one lying on disk
+ // on a given machine from impacting the test.
+ File.WriteAllText(GlobalJson, "{}");
+
+ foreach (string sdk in GlobalSdks)
+ {
+ Directory.CreateDirectory(Path.Combine(GlobalSdkDir, sdk));
+ }
+
+ foreach (string sdk in LocalSdks)
+ {
+ Directory.CreateDirectory(Path.Combine(LocalSdkDir, sdk));
+ }
+ }
+ }
+
+ [Fact]
+ public void Hostfxr_get_available_sdks_with_multilevel_lookup()
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // multilevel lookup is not supported on non-Windows
+ return;
+ }
+
+ var f = new SdkResolutionFixture(sharedTestState);
+
+ // With multi-level lookup (windows onnly): get local and global sdks sorted by ascending version,
+ // with global sdk coming before local sdk when versions are equal
+ string expectedList = string.Join(';', new[]
+ {
+ Path.Combine(f.LocalSdkDir, "0.1.2"),
+ Path.Combine(f.GlobalSdkDir, "1.2.3"),
+ Path.Combine(f.LocalSdkDir, "1.2.3"),
+ Path.Combine(f.GlobalSdkDir, "2.3.4-preview"),
+ Path.Combine(f.GlobalSdkDir, "4.5.6"),
+ Path.Combine(f.LocalSdkDir, "5.6.7-preview"),
+ });
+
+ f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_get_available_sdks", f.ExeDir })
+ .EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES", f.ProgramFiles)
+ .CaptureStdOut()
+ .CaptureStdErr()
+ .Execute()
+ .Should()
+ .Pass()
+ .And
+ .HaveStdOutContaining("hostfxr_get_available_sdks:Success")
+ .And
+ .HaveStdOutContaining($"hostfxr_get_available_sdks sdks:[{expectedList}]");
+ }
+
+ [Fact]
+ public void Hostfxr_get_available_sdks_without_multilevel_lookup()
+ {
+ // Without multi-level lookup: get only sdks sorted by ascending version
+
+ var f = new SdkResolutionFixture(sharedTestState);
+
+ string expectedList = 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"),
+ });
+
+ f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_get_available_sdks", f.ExeDir })
+ .CaptureStdOut()
+ .CaptureStdErr()
+ .Execute()
+ .Should()
+ .Pass()
+ .And
+ .HaveStdOutContaining("hostfxr_get_available_sdks:Success")
+ .And
+ .HaveStdOutContaining($"hostfxr_get_available_sdks sdks:[{expectedList}]");
+ }
+
+ [Fact]
+ public void Hostfxr_resolve_sdk2_without_global_json_or_flags()
+ {
+ // with no global.json and no flags, pick latest SDK
+
+ var f = new SdkResolutionFixture(sharedTestState);
+
+ string expectedData = string.Join(';', new[]
+ {
+ ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.7-preview")),
+ });
+
+ f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_resolve_sdk2", f.ExeDir, f.WorkingDir, "0" })
+ .CaptureStdOut()
+ .CaptureStdErr()
+ .Execute()
+ .Should()
+ .Pass()
+ .And
+ .HaveStdOutContaining("hostfxr_resolve_sdk2:Success")
+ .And
+ .HaveStdOutContaining($"hostfxr_resolve_sdk2 data:[{expectedData}]");
+ }
+
+ [Fact]
+ public void Hostfxr_resolve_sdk2_without_global_json_and_disallowing_previews()
+ {
+ // Without global.json and disallowing previews, pick latest non-preview
+
+ var f = new SdkResolutionFixture(sharedTestState);
+
+ string expectedData = string.Join(';', new[]
+ {
+ ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "1.2.3"))
+ });
+
+ f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_resolve_sdk2", f.ExeDir, f.WorkingDir, "disallow_prerelease" })
+ .CaptureStdOut()
+ .CaptureStdErr()
+ .Execute()
+ .Should()
+ .Pass()
+ .And
+ .HaveStdOutContaining("hostfxr_resolve_sdk2:Success")
+ .And
+ .HaveStdOutContaining($"hostfxr_resolve_sdk2 data:[{expectedData}]");
+ }
+
+ [Fact]
+ public void Hostfxr_resolve_sdk2_with_global_json_and_disallowing_previews()
+ {
+ // With global.json specifying a preview, roll forward to preview
+ // since flag has no impact if global.json specifies a preview.
+ // Also check that global.json that impacted resolution is reported.
+
+ var f = new SdkResolutionFixture(sharedTestState);
+
+ File.WriteAllText(f.GlobalJson, "{ \"sdk\": { \"version\": \"5.6.6-preview\" } }");
+ string expectedData = string.Join(';', new[]
+ {
+ ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.7-preview")),
+ ("global_json_path", f.GlobalJson),
+ });
+
+ f.Dotnet.Exec(f.AppDll, new[] { "hostfxr_resolve_sdk2", f.ExeDir, f.WorkingDir, "disallow_prerelease" })
+ .CaptureStdOut()
+ .CaptureStdErr()
+ .Execute()
+ .Should()
+ .Pass()
+ .And
+ .HaveStdOutContaining("hostfxr_resolve_sdk2:Success")
+ .And
+ .HaveStdOutContaining($"hostfxr_resolve_sdk2 data:[{expectedData}]");
+ }
+
public class SharedTestState : IDisposable
{
public TestProjectFixture PreviouslyPublishedAndRestoredPortableApiTestProjectFixture { get; set; }
"opt",
"corebreadcrumbs");
Directory.CreateDirectory(BreadcrumbLocation);
+
+ // On non-Windows, we can't just P/Invoke to already loaded hostfxr, so copy it next to the app dll.
+ var fixture = PreviouslyPublishedAndRestoredPortableApiTestProjectFixture;
+ var hostfxr = Path.Combine(
+ fixture.BuiltDotnet.GreatestVersionHostFxrPath,
+ $"{fixture.SharedLibraryPrefix}hostfxr{fixture.SharedLibraryExtension}");
+
+ File.Copy(
+ hostfxr,
+ Path.GetDirectoryName(fixture.TestProject.AppDll));
}
}