From 04df295883c3ffb194e292566f60876c3ea23be3 Mon Sep 17 00:00:00 2001 From: Steven Lovelock Date: Fri, 26 Apr 2024 15:08:20 +0100 Subject: [PATCH] Add get_coreclr_command_line which uses hostfxr_initialize_for_dotnet_command_line see https://github.com/dotnet/runtime/blob/main/docs/design/features/native-hosting.md#initializing-host-context --- clr_loader/__init__.py | 34 ++++++++++++++++++++++ clr_loader/hostfxr.py | 64 ++++++++++++++++++++++++++++++++++-------- tests/test_common.py | 13 +++++++++ 3 files changed, 100 insertions(+), 11 deletions(-) diff --git a/clr_loader/__init__.py b/clr_loader/__init__.py index aa604ad..74014d2 100644 --- a/clr_loader/__init__.py +++ b/clr_loader/__init__.py @@ -11,6 +11,7 @@ "get_mono", "get_netfx", "get_coreclr", + "get_coreclr_command_line", "find_dotnet_root", "find_libmono", "find_runtimes", @@ -150,6 +151,39 @@ def get_coreclr( return impl +def get_coreclr_command_line( + *, + entry_dll: StrOrPath, + dotnet_root: Optional[StrOrPath] = None, + properties: Optional[Dict[str, str]] = None +) -> Runtime: + """Get a CoreCLR (.NET Core) runtime instance + The returned ``DotnetCoreRuntimeCommandLine`` also acts as a mapping of the config + properties. They can be retrieved using the index operator and can be + written until the runtime is initialized. The runtime is initialized when + the first function object is retrieved. + :param entry_dll: + The path to the entry dll. + :param dotnet_root: + The root directory of the .NET Core installation. If this is not + specified, we try to discover it using :py:func:`find_dotnet_root`. + :param properties: + Additional runtime properties. These can also be passed using the + ``configProperties`` section in the runtime config.""" + from .hostfxr import DotnetCoreCommandRuntime + + dotnet_root = _maybe_path(dotnet_root) + if dotnet_root is None: + dotnet_root = find_dotnet_root() + + impl = DotnetCoreCommandRuntime(entry_dll=_maybe_path(entry_dll), dotnet_root=dotnet_root) + if properties: + for key, value in properties.items(): + impl[key] = value + + return impl + + def get_netfx( *, domain: Optional[str] = None, config_file: Optional[StrOrPath] = None ) -> Runtime: diff --git a/clr_loader/hostfxr.py b/clr_loader/hostfxr.py index 225b4c7..7a159f3 100644 --- a/clr_loader/hostfxr.py +++ b/clr_loader/hostfxr.py @@ -6,13 +6,15 @@ from .types import Runtime, RuntimeInfo, StrOrPath from .util import check_result -__all__ = ["DotnetCoreRuntime"] +__all__ = ["DotnetCoreRuntime", "DotnetCoreCommandRuntime"] _IS_SHUTDOWN = False -class DotnetCoreRuntime(Runtime): - def __init__(self, runtime_config: Path, dotnet_root: Path, **params: str): +class DotnetCoreRuntimeBase(Runtime): + _version: str + + def __init__(self, dotnet_root: Path): self._handle = None if _IS_SHUTDOWN: @@ -20,15 +22,8 @@ def __init__(self, runtime_config: Path, dotnet_root: Path, **params: str): self._dotnet_root = Path(dotnet_root) self._dll = load_hostfxr(self._dotnet_root) - self._handle = _get_handle(self._dll, self._dotnet_root, runtime_config) self._load_func = None - for key, value in params.items(): - self[key] = value - - # TODO: Get version - self._version = "" - @property def dotnet_root(self) -> Path: return self._dotnet_root @@ -122,7 +117,31 @@ def info(self): ) -def _get_handle(dll, dotnet_root: StrOrPath, runtime_config: StrOrPath): +class DotnetCoreRuntime(DotnetCoreRuntimeBase): + def __init__(self, runtime_config: Path, dotnet_root: Path, **params: str): + super().__init__(dotnet_root) + self._handle = _get_handle_for_runtime_config(self._dll, self._dotnet_root, runtime_config) + + for key, value in params.items(): + self[key] = value + + # TODO: Get version + self._version = "" + + +class DotnetCoreCommandRuntime(DotnetCoreRuntimeBase): + def __init__(self, entry_dll: Path, dotnet_root: Path, **params: str): + super().__init__(dotnet_root) + self._handle = _get_handle_for_dotnet_command_line(self._dll, self._dotnet_root, entry_dll) + + for key, value in params.items(): + self[key] = value + + # TODO: Get version + self._version = "" + + +def _get_handle_for_runtime_config(dll, dotnet_root: StrOrPath, runtime_config: StrOrPath): params = ffi.new("hostfxr_initialize_parameters*") params.size = ffi.sizeof("hostfxr_initialize_parameters") # params.host_path = ffi.new("char_t[]", encode(sys.executable)) @@ -140,6 +159,29 @@ def _get_handle(dll, dotnet_root: StrOrPath, runtime_config: StrOrPath): return handle_ptr[0] +def _get_handle_for_dotnet_command_line(dll, dotnet_root: StrOrPath, entry_dll: StrOrPath): + params = ffi.new("hostfxr_initialize_parameters*") + params.size = ffi.sizeof("hostfxr_initialize_parameters") + params.host_path = ffi.NULL + dotnet_root_p = ffi.new("char_t[]", encode(str(Path(dotnet_root)))) + params.dotnet_root = dotnet_root_p + + handle_ptr = ffi.new("hostfxr_handle*") + + args_ptr = ffi.new("char_t*[1]") + arg_ptr = ffi.new("char_t[]", encode(str(Path(entry_dll)))) + args_ptr[0] = arg_ptr + res = dll.hostfxr_initialize_for_dotnet_command_line( + 1, + args_ptr, + params, handle_ptr + ) + + check_result(res) + + return handle_ptr[0] + + def _get_load_func(dll, handle): delegate_ptr = ffi.new("void**") diff --git a/tests/test_common.py b/tests/test_common.py index 8a9e36d..0e59993 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -75,6 +75,19 @@ def test_coreclr(example_netcore: Path): run_tests(asm) +def test_coreclr_command_line(example_netcore: Path): + run_in_subprocess(_do_test_coreclr_command_line, example_netcore) + + +def _do_test_coreclr_command_line(example_netcore): + from clr_loader import get_coreclr_command_line + + coreclr = get_coreclr_command_line(entry_dll=example_netcore / "example.dll") + asm = coreclr.get_assembly(example_netcore / "example.dll") + + run_tests(asm) + + def test_coreclr_properties(example_netcore: Path): run_in_subprocess( _do_test_coreclr_autogenerated_runtimeconfig,