Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hostfxr_resolve_frameworks_for_runtime_config for resolving runtime frameworks #101451

Merged
merged 8 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
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