Skip to content

Commit

Permalink
Add hostfxr_resolve_frameworks_for_runtime_config for resolving run…
Browse files Browse the repository at this point in the history
…time frameworks (#101451)

- `hostfxr_resolve_frameworks_for_runtime_config` provides a way to run the host's runtime framework resolution in an isolated manner
  - Return code indicates basic success/failure and the optional callback can be used for additional information about what was or was not resolved
  - For framework-dependent, runs through framework resolution and propagates the result
    - Currently `unresolved_count` will always either be 0 or 1 - the host stops at the first failed resolution
  - For self-contained, any `includedFrameworks` per the runtime config are treated as resolved to the app's directory
- Add tests to `NativeHostApis` tests and update `HostApiInvokerApp` test asset to exercise new API
  - Update tests to stop copying hostfxr next to the app - pass in the paths via runtime config and make the test app resolve the load via DllImportResolver
  • Loading branch information
elinor-fung authored May 10, 2024
1 parent 0235164 commit c93ee68
Show file tree
Hide file tree
Showing 11 changed files with 753 additions and 72 deletions.
124 changes: 123 additions & 1 deletion src/installer/tests/Assets/Projects/HostApiInvokerApp/HostFXR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace HostApiInvokerApp
{
public static class HostFXR
public static unsafe class HostFXR
{
internal static class hostfxr
{
Expand Down Expand Up @@ -59,6 +59,34 @@ internal struct hostfxr_dotnet_environment_info
internal IntPtr frameworks;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct hostfxr_framework_result
{
public nuint size;
public string name;
public string requested_version;
public string resolved_version;
public string resolved_path;
};

[StructLayout(LayoutKind.Sequential)]
internal struct hostfxr_resolve_frameworks_result
{
public nuint size;
public nuint resolved_count;
public IntPtr resolved_frameworks;
public nuint unresolved_count;
public IntPtr unresolved_frameworks;
};

[StructLayout(LayoutKind.Sequential)]
internal struct hostfxr_initialize_parameters
{
public nuint size;
public IntPtr host_path;
public IntPtr dotnet_root;
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)]
internal delegate void hostfxr_resolve_sdk2_result_fn(
hostfxr_resolve_sdk2_result_key_t key,
Expand Down Expand Up @@ -101,6 +129,18 @@ internal static extern int hostfxr_get_dotnet_environment_info(
IntPtr reserved,
hostfxr_get_dotnet_environment_info_result_fn result,
IntPtr result_context);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void hostfxr_resolve_frameworks_result_fn(
IntPtr result,
IntPtr result_context);

[DllImport(nameof(hostfxr), CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_resolve_frameworks_for_runtime_config(
string runtime_config_path,
hostfxr_initialize_parameters* parameters,
hostfxr_resolve_frameworks_result_fn callback,
IntPtr result_context);
}

/// <summary>
Expand Down Expand Up @@ -250,6 +290,85 @@ static void Test_hostfxr_get_dotnet_environment_info(string[] args)
Console.WriteLine($"{api} framework paths:[{string.Join(";", frameworks.Select(f => f.path).ToList())}]");
}

/// <summary>
/// Test that invokes hostfxr_resolve_frameworks_for_runtime_config.
/// </summary>
/// <param name="args[0]">Path to runtime config file</param>
/// <param name="args[1]">(Optional) Path to the directory with dotnet.exe</param>
static unsafe void Test_hostfxr_resolve_frameworks_for_runtime_config(string[] args)
{
if (args.Length < 1)
throw new ArgumentException($"Invalid arguments. Expected: {nameof(hostfxr.hostfxr_resolve_frameworks_for_runtime_config)} <runtimeConfigPath> [<dotnetRoot>]");

string runtimeConfigPath = args[0];
string dotnetRoot = null;
if (args.Length >= 2)
dotnetRoot = args[1];

List<hostfxr.hostfxr_framework_result> resolved = new();
List<hostfxr.hostfxr_framework_result> unresolved = new();

IntPtr resultContext = new IntPtr(123);

hostfxr.hostfxr_resolve_frameworks_result_fn callback = (IntPtr resultPtr, IntPtr contextPtr) =>
{
hostfxr.hostfxr_resolve_frameworks_result result = Marshal.PtrToStructure<hostfxr.hostfxr_resolve_frameworks_result>(resultPtr);
if (result.size != (nuint)sizeof(hostfxr.hostfxr_resolve_frameworks_result))
throw new Exception($"Unexpected {nameof(hostfxr.hostfxr_resolve_frameworks_result)}.size: {result.size}. Expected: {sizeof(hostfxr.hostfxr_resolve_frameworks_result)}.");
if (contextPtr != resultContext)
throw new Exception($"Unexpected result_context value: {contextPtr}. Expected: {resultContext}.");
for (int i = 0; i < (int)result.resolved_count; i++)
{
nint ptr = result.resolved_frameworks + i * Marshal.SizeOf<hostfxr.hostfxr_framework_result>();
resolved.Add(Marshal.PtrToStructure<hostfxr.hostfxr_framework_result>(ptr));
}
for (int i = 0; i < (int)result.unresolved_count; i++)
{
nint ptr = result.unresolved_frameworks + i * Marshal.SizeOf<hostfxr.hostfxr_framework_result>();
unresolved.Add(Marshal.PtrToStructure<hostfxr.hostfxr_framework_result>(ptr));
}
};

int rc;
hostfxr.hostfxr_initialize_parameters parameters = new()
{
size = (nuint)sizeof(hostfxr.hostfxr_initialize_parameters),
host_path = IntPtr.Zero,
dotnet_root = dotnetRoot != null ? Marshal.StringToCoTaskMemAuto(dotnetRoot) : IntPtr.Zero
};
try
{
rc = hostfxr.hostfxr_resolve_frameworks_for_runtime_config(
runtime_config_path: runtimeConfigPath,
parameters: &parameters,
callback: callback,
result_context: resultContext);
}
finally
{
Marshal.FreeCoTaskMem(parameters.dotnet_root);
}

string api = nameof(hostfxr.hostfxr_resolve_frameworks_for_runtime_config);
LogResult(api, rc);

Console.WriteLine($"{api} resolved_count: {resolved.Count}");
foreach (var framework in resolved)
{
Console.WriteLine($"{api} resolved_framework: name={framework.name}, version={framework.resolved_version}, path=[{framework.resolved_path}]");
}

Console.WriteLine($"{api} unresolved_count: {unresolved.Count}");
foreach (var framework in unresolved)
{
Console.WriteLine($"{api} unresolved_framework: name={framework.name}, requested_version={framework.requested_version}, path=[{framework.resolved_path}]");
}
}

private static void LogResult(string apiName, int rc)
=> Console.WriteLine(rc == 0 ? $"{apiName}:Success" : $"{apiName}:Fail[0x{rc:x}]");

Expand All @@ -269,6 +388,9 @@ public static bool RunTest(string apiToTest, string[] args)
case nameof(hostfxr.hostfxr_get_dotnet_environment_info):
Test_hostfxr_get_dotnet_environment_info(args);
break;
case nameof(hostfxr.hostfxr_resolve_frameworks_for_runtime_config):
Test_hostfxr_resolve_frameworks_for_runtime_config(args);
break;
default:
return false;
}
Expand Down
20 changes: 16 additions & 4 deletions src/installer/tests/Assets/Projects/HostApiInvokerApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ public static int Main(string[] args)

public static void MainCore(string[] args)
{
Console.WriteLine("Hello World!");
Console.WriteLine(string.Join(Environment.NewLine, args));
if (args.Length == 0)
throw new Exception($"{nameof(HostApiInvokerApp)} requires at least one argument specifying the API to test.");

Console.WriteLine("Arguments:");
foreach (string arg in args)
Console.WriteLine($" {arg}");

// If requested, test multilevel lookup using fake Global SDK directories:
// 1. using a fake ProgramFiles location
Expand All @@ -39,22 +43,30 @@ public static void MainCore(string[] args)
string testMultilevelLookupProgramFiles = Environment.GetEnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES");
string testMultilevelLookupSelfRegistered = Environment.GetEnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED");

string hostfxrPath;
if (testMultilevelLookupProgramFiles != null && testMultilevelLookupSelfRegistered != null)
{
Environment.SetEnvironmentVariable("_DOTNET_TEST_GLOBALLY_REGISTERED_PATH", testMultilevelLookupSelfRegistered);
Environment.SetEnvironmentVariable("ProgramFiles", testMultilevelLookupProgramFiles);
Environment.SetEnvironmentVariable("ProgramFiles(x86)", testMultilevelLookupProgramFiles);
Environment.SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "1");
hostfxrPath = AppContext.GetData("HOSTFXR_PATH_TEST_BEHAVIOR") as string;
}
else
{
// never rely on machine state in test if we're not faking the multi-level lookup
Environment.SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0");
hostfxrPath = AppContext.GetData("HOSTFXR_PATH") as string;
}

if (args.Length == 0)
if (hostfxrPath is not null)
{
throw new Exception("Invalid number of arguments passed");
NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (libraryName, assembly, searchPath) =>
{
return libraryName == nameof(HostFXR.hostfxr)
? NativeLibrary.Load(libraryName, assembly, searchPath)
: default;
});
}

string apiToTest = args[0];
Expand Down
Loading

0 comments on commit c93ee68

Please sign in to comment.