From 79d6c6792ebc09e43d514a3ce554b32a89a2eb19 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Fri, 13 Jan 2023 04:52:54 -0600 Subject: [PATCH 01/19] fix mono set dirs (#48) * fix mono set dirs * fix path --- clr_loader/__init__.py | 2 +- clr_loader/mono.py | 2 +- clr_loader/util/find.py | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/clr_loader/__init__.py b/clr_loader/__init__.py index 4b08148..aa604ad 100644 --- a/clr_loader/__init__.py +++ b/clr_loader/__init__.py @@ -70,7 +70,7 @@ def get_mono( libmono = _maybe_path(libmono) if libmono is None: - libmono = find_libmono(sgen=sgen) + libmono = find_libmono(sgen=sgen, assembly_dir=assembly_dir) impl = Mono( # domain=domain, diff --git a/clr_loader/mono.py b/clr_loader/mono.py index 158ddb7..7c3f20d 100644 --- a/clr_loader/mono.py +++ b/clr_loader/mono.py @@ -136,7 +136,7 @@ def initialize( _MONO = load_mono(libmono) if assembly_dir is not None and config_dir is not None: - _MONO.mono_set_dirs(assembly_dir, config_dir) + _MONO.mono_set_dirs(assembly_dir.encode("utf8"), config_dir.encode("utf8")) # Load in global config (i.e /etc/mono/config) global_encoded = global_config_file or ffi.NULL diff --git a/clr_loader/util/find.py b/clr_loader/util/find.py index d5d7b89..6ef7bc3 100644 --- a/clr_loader/util/find.py +++ b/clr_loader/util/find.py @@ -105,7 +105,7 @@ def find_runtimes() -> Iterator[DotnetCoreRuntimeSpec]: return find_runtimes_in_root(dotnet_root) -def find_libmono(*, sgen: bool = True) -> Path: +def find_libmono(*, assembly_dir: str = None, sgen: bool = True) -> Path: """Find a suitable libmono dynamic library On Windows and macOS, we check the default installation directories. @@ -137,9 +137,12 @@ def find_libmono(*, sgen: bool = True) -> Path: ) else: - from ctypes.util import find_library - - path = find_library(unix_name) + if assembly_dir == None: + from ctypes.util import find_library + path = find_library(unix_name) + else: + libname = "lib" + unix_name + ".so" + path = Path(assembly_dir) / "lib" / libname if path is None: raise RuntimeError("Could not find libmono") From 7dd9b463099425087f4de51c7a68b365a4612344 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 7 Aug 2023 14:10:46 +0200 Subject: [PATCH 02/19] Add Python 3.11 to CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6f9e75..0c6749e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python: ['3.10', '3.9', '3.8', '3.7'] # pypy3 + python: ['3.11', '3.10', '3.9', '3.8', '3.7'] # pypy3 steps: - uses: actions/checkout@v3 From d8f51e9e28f41f2a495327e7cbecb50edd6af243 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 7 Aug 2023 14:33:45 +0200 Subject: [PATCH 03/19] Drop CI for EOLd Python 3.7 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c6749e..41f4e0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python: ['3.11', '3.10', '3.9', '3.8', '3.7'] # pypy3 + python: ['3.11', '3.10', '3.9', '3.8'] # pypy3 steps: - uses: actions/checkout@v3 From 63d7f698d3be00bd1f609545e12f87cc3c549c42 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 7 Aug 2023 14:39:44 +0200 Subject: [PATCH 04/19] Fix handling of non-ASCII assembly paths on .NET Framework (#62) --- netfx_loader/ClrLoader.cs | 6 +++--- tests/test_common.py | 35 +++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/netfx_loader/ClrLoader.cs b/netfx_loader/ClrLoader.cs index e8b2767..af89cee 100644 --- a/netfx_loader/ClrLoader.cs +++ b/netfx_loader/ClrLoader.cs @@ -51,9 +51,9 @@ public static IntPtr CreateAppDomain( [DllExport("pyclr_get_function", CallingConvention.Cdecl)] public static IntPtr GetFunction( IntPtr domain, - [MarshalAs(UnmanagedType.LPStr)] string assemblyPath, - [MarshalAs(UnmanagedType.LPStr)] string typeName, - [MarshalAs(UnmanagedType.LPStr)] string function + [MarshalAs(UnmanagedType.LPUTF8Str)] string assemblyPath, + [MarshalAs(UnmanagedType.LPUTF8Str)] string typeName, + [MarshalAs(UnmanagedType.LPUTF8Str)] string function ) { try diff --git a/tests/test_common.py b/tests/test_common.py index f42020a..250c290 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,3 +1,4 @@ +import shutil import pytest from subprocess import check_call import os @@ -24,7 +25,7 @@ def build_example(tmpdir_factory, framework): return out -def test_mono(example_netstandard): +def test_mono(example_netstandard: Path): from clr_loader import get_mono mono = get_mono() @@ -33,7 +34,7 @@ def test_mono(example_netstandard): run_tests(asm) -def test_mono_debug(example_netstandard): +def test_mono_debug(example_netstandard: Path): from clr_loader import get_mono mono = get_mono( @@ -46,7 +47,8 @@ def test_mono_debug(example_netstandard): run_tests(asm) -def test_mono_signal_chaining(example_netstandard): + +def test_mono_signal_chaining(example_netstandard: Path): from clr_loader import get_mono mono = get_mono(set_signal_chaining=True) @@ -54,7 +56,8 @@ def test_mono_signal_chaining(example_netstandard): run_tests(asm) -def test_mono_set_dir(example_netstandard): + +def test_mono_set_dir(example_netstandard: Path): from clr_loader import get_mono mono = get_mono(assembly_dir="/usr/lib", config_dir="/etc") @@ -62,7 +65,8 @@ def test_mono_set_dir(example_netstandard): run_tests(asm) -def test_coreclr(example_netcore): + +def test_coreclr(example_netcore: Path): from clr_loader import get_coreclr coreclr = get_coreclr(runtime_config=example_netcore / "example.runtimeconfig.json") @@ -71,7 +75,7 @@ def test_coreclr(example_netcore): run_tests(asm) -def test_coreclr_autogenerated_runtimeconfig(example_netstandard): +def test_coreclr_autogenerated_runtimeconfig(example_netstandard: Path): from multiprocessing import get_context p = get_context("spawn").Process( @@ -82,7 +86,7 @@ def test_coreclr_autogenerated_runtimeconfig(example_netstandard): p.close() -def _do_test_coreclr_autogenerated_runtimeconfig(example_netstandard): +def _do_test_coreclr_autogenerated_runtimeconfig(example_netstandard: Path): from clr_loader import get_coreclr coreclr = get_coreclr() @@ -94,9 +98,24 @@ def _do_test_coreclr_autogenerated_runtimeconfig(example_netstandard): @pytest.mark.skipif( sys.platform != "win32", reason=".NET Framework only exists on Windows" ) -def test_netfx(example_netstandard): +def test_netfx(example_netstandard: Path): + from clr_loader import get_netfx + + netfx = get_netfx() + asm = netfx.get_assembly(example_netstandard / "example.dll") + + run_tests(asm) + + +@pytest.mark.skipif( + sys.platform != "win32", reason=".NET Framework only exists on Windows" +) +def test_netfx_chinese_path(example_netstandard: Path, tmpdir_factory): from clr_loader import get_netfx + tmp_path = Path(tmpdir_factory.mktemp("example-中国")) + shutil.copytree(example_netstandard, tmp_path, dirs_exist_ok=True) + netfx = get_netfx() asm = netfx.get_assembly(os.path.join(example_netstandard, "example.dll")) From 000bc780b33b5dbb3f0c1891776fda0971cc2df2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 7 Aug 2023 15:21:26 +0200 Subject: [PATCH 05/19] Delay actually loading the runtime to fix property setting (#63) * Delay actually loading the runtime to fix property setting * Launch test for properties in separate process --- clr_loader/hostfxr.py | 20 +++++++++----------- tests/test_common.py | 21 ++++++++++++++++++--- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/clr_loader/hostfxr.py b/clr_loader/hostfxr.py index 522fec5..225b4c7 100644 --- a/clr_loader/hostfxr.py +++ b/clr_loader/hostfxr.py @@ -20,9 +20,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._is_initialized = False self._handle = _get_handle(self._dll, self._dotnet_root, runtime_config) - self._load_func = _get_load_func(self._dll, self._handle) + self._load_func = None for key, value in params.items(): self[key] = value @@ -36,7 +35,7 @@ def dotnet_root(self) -> Path: @property def is_initialized(self) -> bool: - return self._is_initialized + return self._load_func is not None @property def is_shutdown(self) -> bool: @@ -81,10 +80,15 @@ def __iter__(self) -> Generator[Tuple[str, str], None, None]: for i in range(size_ptr[0]): yield (decode(keys_ptr[i]), decode(values_ptr[i])) + def _get_load_func(self): + if self._load_func is None: + self._load_func = _get_load_func(self._dll, self._handle) + + return self._load_func + def _get_callable(self, assembly_path: StrOrPath, typename: str, function: str): # TODO: Maybe use coreclr_get_delegate as well, supported with newer API # versions of hostfxr - self._is_initialized = True # Append assembly name to typename assembly_path = Path(assembly_path) @@ -92,7 +96,7 @@ def _get_callable(self, assembly_path: StrOrPath, typename: str, function: str): typename = f"{typename}, {assembly_name}" delegate_ptr = ffi.new("void**") - res = self._load_func( + res = self._get_load_func()( encode(str(assembly_path)), encode(typename), encode(function), @@ -103,12 +107,6 @@ def _get_callable(self, assembly_path: StrOrPath, typename: str, function: str): check_result(res) return ffi.cast("component_entry_point_fn", delegate_ptr[0]) - def _check_initialized(self) -> None: - if self._handle is None: - raise RuntimeError("Runtime is shut down") - elif not self._is_initialized: - raise RuntimeError("Runtime is not initialized") - def shutdown(self) -> None: if self._handle is not None: self._dll.hostfxr_close(self._handle) diff --git a/tests/test_common.py b/tests/test_common.py index 250c290..a33dec0 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -18,7 +18,7 @@ def example_netcore(tmpdir_factory): def build_example(tmpdir_factory, framework): out = Path(tmpdir_factory.mktemp(f"example-{framework}")) - proj_path = Path(__file__).parent.parent / "example" + proj_path = Path(__file__).parent.parent / "example" / "example.csproj" check_call(["dotnet", "build", str(proj_path), "-o", str(out), "-f", framework]) @@ -75,6 +75,19 @@ def test_coreclr(example_netcore: Path): run_tests(asm) +def test_coreclr_properties(example_netcore: Path): + from multiprocessing import get_context + + p = get_context("spawn").Process( + target=_do_test_coreclr_autogenerated_runtimeconfig, + args=(example_netstandard,), + kwargs=dict(properties=dict(APP_CONTEXT_BASE_DIRECTORY=str(example_netcore))), + ) + p.start() + p.join() + p.close() + + def test_coreclr_autogenerated_runtimeconfig(example_netstandard: Path): from multiprocessing import get_context @@ -86,10 +99,12 @@ def test_coreclr_autogenerated_runtimeconfig(example_netstandard: Path): p.close() -def _do_test_coreclr_autogenerated_runtimeconfig(example_netstandard: Path): +def _do_test_coreclr_autogenerated_runtimeconfig( + example_netstandard: Path, **properties +): from clr_loader import get_coreclr - coreclr = get_coreclr() + coreclr = get_coreclr(properties=properties) asm = coreclr.get_assembly(example_netstandard / "example.dll") run_tests(asm) From 70e2c72da9db0c769df1df195891da5c74a55dd5 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 16 Dec 2022 16:33:59 +0100 Subject: [PATCH 06/19] Fix parameter passing for .NET Framework domains --- clr_loader/netfx.py | 11 +++++++++-- netfx_loader/ClrLoader.cs | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/clr_loader/netfx.py b/clr_loader/netfx.py index 24460ca..4efe841 100644 --- a/clr_loader/netfx.py +++ b/clr_loader/netfx.py @@ -14,13 +14,15 @@ def __init__( ): initialize() if config_file is not None: - config_file_s = str(config_file) + config_file_s = str(config_file).encode("utf8") else: config_file_s = ffi.NULL + domain_s = domain.encode("utf8") if domain else ffi.NULL + self._domain_name = domain self._config_file = config_file - self._domain = _FW.pyclr_create_appdomain(domain or ffi.NULL, config_file_s) + self._domain = _FW.pyclr_create_appdomain(domain_s, config_file_s) def info(self) -> RuntimeInfo: return RuntimeInfo( @@ -41,6 +43,11 @@ def _get_callable(self, assembly_path: StrOrPath, typename: str, function: str): function.encode("utf8"), ) + if func == ffi.NULL: + raise RuntimeError( + f"Failed to resolve {typename}.{function} from {assembly_path}" + ) + return func def shutdown(self): diff --git a/netfx_loader/ClrLoader.cs b/netfx_loader/ClrLoader.cs index af89cee..32b4c01 100644 --- a/netfx_loader/ClrLoader.cs +++ b/netfx_loader/ClrLoader.cs @@ -32,8 +32,10 @@ public static IntPtr CreateAppDomain( { var setup = new AppDomainSetup { + ApplicationBase = AppDomain.CurrentDomain.BaseDirectory, ConfigurationFile = configFile }; + Print($"Base: {AppDomain.CurrentDomain.BaseDirectory}"); var domain = AppDomain.CreateDomain(name, null, setup); Print($"Located domain {domain}"); From 05235b979a6fa169898318af7d752428a1bbd850 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 16 Dec 2022 16:40:08 +0100 Subject: [PATCH 07/19] Add unit test for using a separate domain --- tests/test_common.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_common.py b/tests/test_common.py index a33dec0..3cba3cd 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -137,6 +137,18 @@ def test_netfx_chinese_path(example_netstandard: Path, tmpdir_factory): run_tests(asm) +@pytest.mark.skipif( + sys.platform != "win32", reason=".NET Framework only exists on Windows" +) +def test_netfx_separate_domain(example_netstandard): + from clr_loader import get_netfx + + netfx = get_netfx(domain="some_domain") + asm = netfx.get_assembly(os.path.join(example_netstandard, "example.dll")) + + run_tests(asm) + + def run_tests(asm): func = asm.get_function("Example.TestClass", "Test") test_data = b"testy mctestface" From 59f47f9088da9cf999439bb15ff7ac131633fec7 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 7 Aug 2023 16:22:10 +0200 Subject: [PATCH 08/19] Move netfx tests to individual subprocesses --- tests/test_common.py | 53 ++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/tests/test_common.py b/tests/test_common.py index 3cba3cd..8a9e36d 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -76,27 +76,15 @@ def test_coreclr(example_netcore: Path): def test_coreclr_properties(example_netcore: Path): - from multiprocessing import get_context - - p = get_context("spawn").Process( - target=_do_test_coreclr_autogenerated_runtimeconfig, - args=(example_netstandard,), - kwargs=dict(properties=dict(APP_CONTEXT_BASE_DIRECTORY=str(example_netcore))), + run_in_subprocess( + _do_test_coreclr_autogenerated_runtimeconfig, + example_netstandard, + properties=dict(APP_CONTEXT_BASE_DIRECTORY=str(example_netcore)), ) - p.start() - p.join() - p.close() def test_coreclr_autogenerated_runtimeconfig(example_netstandard: Path): - from multiprocessing import get_context - - p = get_context("spawn").Process( - target=_do_test_coreclr_autogenerated_runtimeconfig, args=(example_netstandard,) - ) - p.start() - p.join() - p.close() + run_in_subprocess(_do_test_coreclr_autogenerated_runtimeconfig, example_netstandard) def _do_test_coreclr_autogenerated_runtimeconfig( @@ -114,37 +102,31 @@ def _do_test_coreclr_autogenerated_runtimeconfig( sys.platform != "win32", reason=".NET Framework only exists on Windows" ) def test_netfx(example_netstandard: Path): - from clr_loader import get_netfx - - netfx = get_netfx() - asm = netfx.get_assembly(example_netstandard / "example.dll") - - run_tests(asm) + run_in_subprocess(_do_test_netfx, example_netstandard) @pytest.mark.skipif( sys.platform != "win32", reason=".NET Framework only exists on Windows" ) def test_netfx_chinese_path(example_netstandard: Path, tmpdir_factory): - from clr_loader import get_netfx - tmp_path = Path(tmpdir_factory.mktemp("example-中国")) shutil.copytree(example_netstandard, tmp_path, dirs_exist_ok=True) - netfx = get_netfx() - asm = netfx.get_assembly(os.path.join(example_netstandard, "example.dll")) - - run_tests(asm) + run_in_subprocess(_do_test_netfx, tmp_path) @pytest.mark.skipif( sys.platform != "win32", reason=".NET Framework only exists on Windows" ) def test_netfx_separate_domain(example_netstandard): + run_in_subprocess(_do_test_netfx, example_netstandard, domain="some domain") + + +def _do_test_netfx(example_netstandard, **kwargs): from clr_loader import get_netfx - netfx = get_netfx(domain="some_domain") - asm = netfx.get_assembly(os.path.join(example_netstandard, "example.dll")) + netfx = get_netfx(**kwargs) + asm = netfx.get_assembly(example_netstandard / "example.dll") run_tests(asm) @@ -154,3 +136,12 @@ def run_tests(asm): test_data = b"testy mctestface" res = func(test_data) assert res == len(test_data) + + +def run_in_subprocess(func, *args, **kwargs): + from multiprocessing import get_context + + p = get_context("spawn").Process(target=func, args=args, kwargs=kwargs) + p.start() + p.join() + p.close() From f2058b49ee08817c373474d28c637f36feec4fa1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 21 Nov 2023 12:22:31 +0100 Subject: [PATCH 09/19] Ensure that an uninitialised NetFx object does not fail in shutdown --- clr_loader/netfx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clr_loader/netfx.py b/clr_loader/netfx.py index 4efe841..4d46b37 100644 --- a/clr_loader/netfx.py +++ b/clr_loader/netfx.py @@ -12,6 +12,8 @@ class NetFx(Runtime): def __init__( self, domain: Optional[str] = None, config_file: Optional[Path] = None ): + self._domain = None + initialize() if config_file is not None: config_file_s = str(config_file).encode("utf8") From 3e2dff02bc08a840c74c0910e0303c92c8a7c306 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 10 Jul 2024 09:23:39 +0200 Subject: [PATCH 10/19] Add ruff formatting and linting (#65) --- .github/workflows/ci.yml | 14 ++++++++++++++ clr_loader/__init__.py | 2 +- clr_loader/mono.py | 3 ++- clr_loader/util/find.py | 3 ++- doc/conf.py | 7 ++++--- tests/test_common.py | 1 - 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41f4e0c..3495443 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,20 @@ jobs: path: "dist/*" if-no-files-found: error + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depths: 0 + - uses: actions/setup-python@v4 + - name: Install Ruff + run: pip install ruff + - name: Check format + run: ruff format --check + - name: Check lints + run: ruff check + test: runs-on: ${{ matrix.os }} needs: build diff --git a/clr_loader/__init__.py b/clr_loader/__init__.py index aa604ad..71eb09d 100644 --- a/clr_loader/__init__.py +++ b/clr_loader/__init__.py @@ -32,7 +32,7 @@ def get_mono( jit_options: Optional[Sequence[str]] = None, assembly_dir: Optional[str] = None, config_dir: Optional[str] = None, - set_signal_chaining: bool = False + set_signal_chaining: bool = False, ) -> Runtime: """Get a Mono runtime instance diff --git a/clr_loader/mono.py b/clr_loader/mono.py index 7c3f20d..1899ea3 100644 --- a/clr_loader/mono.py +++ b/clr_loader/mono.py @@ -86,7 +86,8 @@ class MethodDesc: def __init__(self, typename, function): self._desc = f"{typename}:{function}" self._ptr = _MONO.mono_method_desc_new( - self._desc.encode("utf8"), 1 # include_namespace + self._desc.encode("utf8"), + 1, # include_namespace ) def search(self, image): diff --git a/clr_loader/util/find.py b/clr_loader/util/find.py index 6ef7bc3..65bc7ac 100644 --- a/clr_loader/util/find.py +++ b/clr_loader/util/find.py @@ -137,8 +137,9 @@ def find_libmono(*, assembly_dir: str = None, sgen: bool = True) -> Path: ) else: - if assembly_dir == None: + if assembly_dir is None: from ctypes.util import find_library + path = find_library(unix_name) else: libname = "lib" + unix_name + ".so" diff --git a/doc/conf.py b/doc/conf.py index 1ad2427..385629c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,3 +1,6 @@ +import sys +from pathlib import Path + project = "clr-loader" copyright = "2022, Benedikt Reinartz" author = "Benedikt Reinartz" @@ -5,9 +8,7 @@ extensions = ["sphinx.ext.autodoc"] # Add parent to path for autodoc -import sys, os - -sys.path.append(os.path.abspath("..")) +sys.path.append(str(Path("..").absolute())) # autodoc_typehints = "both" diff --git a/tests/test_common.py b/tests/test_common.py index 8a9e36d..139f192 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,7 +1,6 @@ import shutil import pytest from subprocess import check_call -import os import sys from pathlib import Path From 8e6dcd3cccc9bc98c877b9464f7f2000a108a49b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 12 Dec 2024 20:53:06 +0100 Subject: [PATCH 11/19] Use uv and test 3.13 and 3.12 (#72) --- .github/workflows/ci.yml | 26 +++--- pyproject.toml | 10 ++- uv.lock | 174 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 16 deletions(-) create mode 100644 uv.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3495443..ae40be9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,13 +13,9 @@ jobs: with: fetch-depth: 0 - uses: actions/setup-dotnet@v1 - - uses: actions/setup-python@v4 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build + - uses: astral-sh/setup-uv@v4 - name: Build - run: python -m build + run: uv build - name: Upload source distribution uses: actions/upload-artifact@v3 with: @@ -33,9 +29,9 @@ jobs: - uses: actions/checkout@v3 with: fetch-depths: 0 - - uses: actions/setup-python@v4 + - uses: astral-sh/setup-uv@v4 - name: Install Ruff - run: pip install ruff + run: uv tool install ruff - name: Check format run: ruff format --check - name: Check lints @@ -46,8 +42,8 @@ jobs: needs: build strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python: ['3.11', '3.10', '3.9', '3.8'] # pypy3 + os: [ubuntu-22.04, windows-latest, macos-13] + python: ['3.13', '3.12', '3.11', '3.10', '3.9', '3.8'] # pypy3 steps: - uses: actions/checkout@v3 @@ -58,7 +54,7 @@ jobs: dotnet-version: '6.0.x' - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v4 + uses: astral-sh/setup-uv@v4 with: python-version: ${{ matrix.python }} @@ -76,8 +72,8 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install pytest + uv venv + uv pip install pytest - name: Download wheel uses: actions/download-artifact@v3 @@ -88,11 +84,11 @@ jobs: - name: Install wheel shell: bash run: | - pip install dist/*.whl + uv pip install dist/*.whl - name: Test with pytest run: | - pytest + uv run pytest deploy: runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index 5491c06..94857e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,10 @@ requires-python = ">=3.7" readme = "README.md" -dependencies = ["cffi>=1.13"] +dependencies = [ + "cffi >= 1.13; python_version <= '3.8'", + "cffi >= 1.17; python_version >= '3.8'", +] classifiers = [ "Development Status :: 4 - Beta", @@ -32,6 +35,11 @@ email = "filmor@gmail.com" Sources = "https://github.com/pythonnet/clr-loader" Documentation = "https://pythonnet.github.io/clr-loader/" +[optional-dependencies] +dev = [ + "pytest" +] + [tool.setuptools] zip-safe = false package-data = {"clr_loader.ffi" = ["dlls/x86/*.dll", "dlls/amd64/*.dll"]} diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..60809e6 --- /dev/null +++ b/uv.lock @@ -0,0 +1,174 @@ +version = 1 +requires-python = ">=3.7" +resolution-markers = [ + "python_full_version < '3.8'", + "python_full_version == '3.8.*'", + "python_full_version >= '3.9'", +] + +[[package]] +name = "cffi" +version = "1.15.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "pycparser", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", size = 508501 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/ff/c4b7a358526f231efa46a375c959506c87622fb4a2c5726e827c55e6adf2/cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", size = 179233 }, + { url = "https://files.pythonhosted.org/packages/ea/be/c4ad40ad441ac847b67c7a37284ae3c58f39f3e638c6b0f85fb662233825/cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", size = 174280 }, + { url = "https://files.pythonhosted.org/packages/ed/a3/c5f01988ddb70a187c3e6112152e01696188c9f8a4fa4c68aa330adbb179/cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", size = 421712 }, + { url = "https://files.pythonhosted.org/packages/ef/41/19da352d341963d29a33bdb28433ba94c05672fb16155f794fad3fd907b0/cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", size = 449886 }, + { url = "https://files.pythonhosted.org/packages/af/da/9441d56d7dd19d07dcc40a2a5031a1f51c82a27cee3705edf53dadcac398/cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", size = 450520 }, + { url = "https://files.pythonhosted.org/packages/aa/02/ab15b3aa572759df752491d5fa0f74128cd14e002e8e3257c1ab1587810b/cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", size = 446015 }, + { url = "https://files.pythonhosted.org/packages/88/89/c34caf63029fb7628ec2ebd5c88ae0c9bd17db98c812e4065a4d020ca41f/cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", size = 441830 }, + { url = "https://files.pythonhosted.org/packages/32/bd/d0809593f7976828f06a492716fbcbbfb62798bbf60ea1f65200b8d49901/cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", size = 434743 }, + { url = "https://files.pythonhosted.org/packages/0e/65/0d7b5dad821ced4dcd43f96a362905a68ce71e6b5f5cfd2fada867840582/cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", size = 464113 }, + { url = "https://files.pythonhosted.org/packages/9f/52/1e2b43cfdd7d9a39f48bc89fcaee8d8685b1295e205a4f1044909ac14d89/cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", size = 170412 }, + { url = "https://files.pythonhosted.org/packages/0e/e2/a23af3d81838c577571da4ff01b799b0c2bbde24bd924d97e228febae810/cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", size = 179060 }, + { url = "https://files.pythonhosted.org/packages/23/8b/2e8c2469eaf89f7273ac685164949a7e644cdfe5daf1c036564208c3d26b/cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", size = 179198 }, + { url = "https://files.pythonhosted.org/packages/f9/96/fc9e118c47b7adc45a0676f413b4a47554e5f3b6c99b8607ec9726466ef1/cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", size = 174221 }, + { url = "https://files.pythonhosted.org/packages/10/72/617ee266192223a38b67149c830bd9376b69cf3551e1477abc72ff23ef8e/cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", size = 441694 }, + { url = "https://files.pythonhosted.org/packages/91/bc/b7723c2fe7a22eee71d7edf2102cd43423d5f95ff3932ebaa2f82c7ec8d0/cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", size = 470613 }, + { url = "https://files.pythonhosted.org/packages/5d/4e/4e0bb5579b01fdbfd4388bd1eb9394a989e1336203a4b7f700d887b233c1/cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", size = 472199 }, + { url = "https://files.pythonhosted.org/packages/37/5a/c37631a86be838bdd84cc0259130942bf7e6e32f70f4cab95f479847fb91/cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", size = 462588 }, + { url = "https://files.pythonhosted.org/packages/71/d7/0fe0d91b0bbf610fb7254bb164fa8931596e660d62e90fb6289b7ee27b09/cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", size = 450543 }, + { url = "https://files.pythonhosted.org/packages/d3/56/3e94aa719ae96eeda8b68b3ec6e347e0a23168c6841dc276ccdcdadc9f32/cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", size = 474253 }, + { url = "https://files.pythonhosted.org/packages/87/ee/ddc23981fc0f5e7b5356e98884226bcb899f95ebaefc3e8e8b8742dd7e22/cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", size = 170313 }, + { url = "https://files.pythonhosted.org/packages/43/a0/cc7370ef72b6ee586369bacd3961089ab3d94ae712febf07a244f1448ffd/cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", size = 179001 }, + { url = "https://files.pythonhosted.org/packages/b5/7d/df6c088ef30e78a78b0c9cca6b904d5abb698afb5bc8f5191d529d83d667/cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", size = 178906 }, + { url = "https://files.pythonhosted.org/packages/c2/0b/3b09a755ddb977c167e6d209a7536f6ade43bb0654bad42e08df1406b8e4/cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", size = 405992 }, + { url = "https://files.pythonhosted.org/packages/5b/1a/e1ee5bed11d8b6540c05a8e3c32448832d775364d4461dd6497374533401/cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", size = 435560 }, + { url = "https://files.pythonhosted.org/packages/d3/e1/e55ca2e0dd446caa2cc8f73c2b98879c04a1f4064ac529e1836683ca58b8/cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", size = 435478 }, + { url = "https://files.pythonhosted.org/packages/2e/7a/68c35c151e5b7a12650ecc12fdfb85211aa1da43e9924598451c4a0a3839/cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", size = 430395 }, + { url = "https://files.pythonhosted.org/packages/93/d0/2e2b27ea2f69b0ec9e481647822f8f77f5fc23faca2dd00d1ff009940eb7/cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", size = 427911 }, + { url = "https://files.pythonhosted.org/packages/50/34/4cc590ad600869502c9838b4824982c122179089ed6791a8b1c95f0ff55e/cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", size = 169721 }, + { url = "https://files.pythonhosted.org/packages/32/2a/63cb8c07d151de92ff9d897b2eb27ba6a0e78dda8e4c5f70d7b8c16cd6a2/cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", size = 179301 }, + { url = "https://files.pythonhosted.org/packages/87/4b/64e8bd9d15d6b22b6cb11997094fbe61edf453ea0a97c8675cb7d1c3f06f/cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", size = 178940 }, + { url = "https://files.pythonhosted.org/packages/22/c6/df826563f55f7e9dd9a1d3617866282afa969fe0d57decffa1911f416ed8/cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", size = 421947 }, + { url = "https://files.pythonhosted.org/packages/c1/25/16a082701378170559bb1d0e9ef2d293cece8dc62913d79351beb34c5ddf/cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", size = 449906 }, + { url = "https://files.pythonhosted.org/packages/df/02/aef53d4aa43154b829e9707c8c60bab413cd21819c4a36b0d7aaa83e2a61/cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", size = 451028 }, + { url = "https://files.pythonhosted.org/packages/79/4b/33494eb0adbcd884656c48f6db0c98ad8a5c678fb8fb5ed41ab546b04d8c/cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", size = 446520 }, + { url = "https://files.pythonhosted.org/packages/b7/8b/06f30caa03b5b3ac006de4f93478dbd0239e2a16566d81a106c322dc4f79/cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", size = 442655 }, + { url = "https://files.pythonhosted.org/packages/47/97/137f0e3d2304df2060abb872a5830af809d7559a5a4b6a295afb02728e65/cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", size = 170236 }, + { url = "https://files.pythonhosted.org/packages/c9/e3/0a52838832408cfbbf3a59cb19bcd17e64eb33795c9710ca7d29ae10b5b7/cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", size = 178835 }, + { url = "https://files.pythonhosted.org/packages/18/8f/5ff70c7458d61fa8a9752e5ee9c9984c601b0060aae0c619316a1e1f1ee5/cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", size = 179249 }, + { url = "https://files.pythonhosted.org/packages/3a/75/a162315adeaf47e94a3b7f886a8e31d77b9e525a387eef2d6f0efc96a7c8/cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0", size = 174297 }, + { url = "https://files.pythonhosted.org/packages/85/1f/a3c533f8d377da5ca7edb4f580cc3edc1edbebc45fac8bb3ae60f1176629/cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", size = 420641 }, + { url = "https://files.pythonhosted.org/packages/77/b7/d3618d612be01e184033eab90006f8ca5b5edafd17bf247439ea4e167d8a/cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", size = 448814 }, + { url = "https://files.pythonhosted.org/packages/a9/ba/e082df21ebaa9cb29f2c4e1d7e49a29b90fcd667d43632c6674a16d65382/cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", size = 449647 }, + { url = "https://files.pythonhosted.org/packages/af/cb/53b7bba75a18372d57113ba934b27d0734206c283c1dfcc172347fbd9f76/cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", size = 445191 }, + { url = "https://files.pythonhosted.org/packages/2d/86/3ca57cddfa0419f6a95d1c8478f8f622ba597e3581fd501bbb915b20eb75/cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", size = 441236 }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b3a73ab7d82a64664c7c4ea470e4ec4a3c73bb4f02575c543a41e272de5/cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", size = 433865 }, + { url = "https://files.pythonhosted.org/packages/da/ff/ab939e2c7b3f40d851c0f7192c876f1910f3442080c9c846532993ec3cef/cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", size = 463090 }, + { url = "https://files.pythonhosted.org/packages/c6/3d/dd085bb831b22ce4d0b7ba8550e6d78960f02f770bbd1314fea3580727f8/cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", size = 170382 }, + { url = "https://files.pythonhosted.org/packages/a8/16/06b84a7063a4c0a2b081030fdd976022086da9c14e80a9ed4ba0183a98a9/cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", size = 179079 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", + "python_full_version >= '3.9'", +] +dependencies = [ + { name = "pycparser", marker = "python_full_version >= '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, + { url = "https://files.pythonhosted.org/packages/48/08/15bf6b43ae9bd06f6b00ad8a91f5a8fe1069d4c9fab550a866755402724e/cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", size = 182457 }, + { url = "https://files.pythonhosted.org/packages/c2/5b/f1523dd545f92f7df468e5f653ffa4df30ac222f3c884e51e139878f1cb5/cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", size = 425932 }, + { url = "https://files.pythonhosted.org/packages/53/93/7e547ab4105969cc8c93b38a667b82a835dd2cc78f3a7dad6130cfd41e1d/cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", size = 448585 }, + { url = "https://files.pythonhosted.org/packages/56/c4/a308f2c332006206bb511de219efeff090e9d63529ba0a77aae72e82248b/cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", size = 456268 }, + { url = "https://files.pythonhosted.org/packages/ca/5b/b63681518265f2f4060d2b60755c1c77ec89e5e045fc3773b72735ddaad5/cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", size = 436592 }, + { url = "https://files.pythonhosted.org/packages/bb/19/b51af9f4a4faa4a8ac5a0e5d5c2522dcd9703d07fac69da34a36c4d960d3/cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", size = 446512 }, + { url = "https://files.pythonhosted.org/packages/e2/63/2bed8323890cb613bbecda807688a31ed11a7fe7afe31f8faaae0206a9a3/cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", size = 171576 }, + { url = "https://files.pythonhosted.org/packages/2f/70/80c33b044ebc79527447fd4fbc5455d514c3bb840dede4455de97da39b4d/cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", size = 181229 }, + { url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220 }, + { url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605 }, + { url = "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", size = 424910 }, + { url = "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", size = 447200 }, + { url = "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", size = 454565 }, + { url = "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", size = 435635 }, + { url = "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", size = 445218 }, + { url = "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", size = 460486 }, + { url = "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", size = 437911 }, + { url = "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", size = 460632 }, + { url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820 }, + { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290 }, +] + +[[package]] +name = "clr-loader" +version = "0.2.7.dev2+g3e2dff0.d20241212" +source = { editable = "." } +dependencies = [ + { name = "cffi", version = "1.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, +] + +[package.metadata] +requires-dist = [ + { name = "cffi", marker = "python_full_version >= '3.8'", specifier = ">=1.17" }, + { name = "cffi", marker = "python_full_version < '3.9'", specifier = ">=1.13" }, +] + +[[package]] +name = "pycparser" +version = "2.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", size = 170877 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", size = 118697 }, +] From 9b5914e3cfcd57bf4f5d4afc8d40c4733aedc4aa Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 12 Dec 2024 21:14:08 +0100 Subject: [PATCH 12/19] Workaround for setuptools bug #4759 --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 94857e4..c3ed5df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61", "setuptools_scm[toml]", "wheel"] +requires = ["setuptools>=75", "setuptools_scm[toml]"] build-backend = "setuptools.build_meta" [project] @@ -43,6 +43,7 @@ dev = [ [tool.setuptools] zip-safe = false package-data = {"clr_loader.ffi" = ["dlls/x86/*.dll", "dlls/amd64/*.dll"]} +license-files = [] [tool.setuptools.packages.find] include = ["clr_loader*"] From cbe765c35ecf8c1885be4f2eef4e9aea7d8ce406 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 12 Dec 2024 21:33:46 +0100 Subject: [PATCH 13/19] Use dependency group instead of optional deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c3ed5df..2c643e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ email = "filmor@gmail.com" Sources = "https://github.com/pythonnet/clr-loader" Documentation = "https://pythonnet.github.io/clr-loader/" -[optional-dependencies] +[dependency-groups] dev = [ "pytest" ] From 80e2385f5e1ba3ed2292948422514878b951793f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 24 Jul 2025 19:55:34 +0200 Subject: [PATCH 14/19] Update CI actions (#79) * Update CI actions * Fix format --- .github/workflows/ci-arm.yml | 5 ++--- .github/workflows/ci.yml | 22 +++++++++++----------- .github/workflows/docs.yml | 2 +- clr_loader/types.py | 2 +- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci-arm.yml b/.github/workflows/ci-arm.yml index c451f0f..8f46757 100644 --- a/.github/workflows/ci-arm.yml +++ b/.github/workflows/ci-arm.yml @@ -2,7 +2,7 @@ name: ARM64 Tests on: push: - branches: master + branches: [master] pull_request: jobs: @@ -10,13 +10,12 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v1 with: dotnet-version: | - 3.1.x 6.0.x - name: Create virtualenv diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae40be9..b83433d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,22 +2,22 @@ name: Python Tests on: push: - branches: master + branches: [master] pull_request: jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-dotnet@v1 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v6 - name: Build run: uv build - name: Upload source distribution - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: build-output path: "dist/*" @@ -26,10 +26,10 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depths: 0 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v6 - name: Install Ruff run: uv tool install ruff - name: Check format @@ -46,7 +46,7 @@ jobs: python: ['3.13', '3.12', '3.11', '3.10', '3.9', '3.8'] # pypy3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v1 @@ -54,13 +54,13 @@ jobs: dotnet-version: '6.0.x' - name: Set up Python ${{ matrix.python }} - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python }} - name: Cache Mono if: runner.os == 'Windows' - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ env.TEMP }}\chocolatey key: ${{ runner.os }}-chocolatey-${{ matrix.python == 'pypy3' && '32' || '64' }} @@ -76,7 +76,7 @@ jobs: uv pip install pytest - name: Download wheel - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build-output path: dist/ @@ -96,7 +96,7 @@ jobs: steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build-output path: dist/ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bce19d4..1fe8fee 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,7 +14,7 @@ jobs: - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: doc/_build/html/ diff --git a/clr_loader/types.py b/clr_loader/types.py index 15c1e30..6b54030 100644 --- a/clr_loader/types.py +++ b/clr_loader/types.py @@ -141,6 +141,6 @@ def _truncate(string: str, length: int) -> str: if length <= 1: raise TypeError("length must be > 1") if len(string) > length - 1: - return f"{string[:length-1]}…" + return f"{string[: length - 1]}…" else: return string From f9e0544ff02c6fced35e47a3593d477dd17a4cb2 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Mon, 28 Jul 2025 16:20:14 -0500 Subject: [PATCH 15/19] add trace options (#67) Co-authored-by: Mohamed Koubaa --- clr_loader/__init__.py | 2 ++ clr_loader/ffi/mono.py | 3 +++ clr_loader/mono.py | 12 ++++++++++++ tests/test_common.py | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/clr_loader/__init__.py b/clr_loader/__init__.py index 71eb09d..52ce603 100644 --- a/clr_loader/__init__.py +++ b/clr_loader/__init__.py @@ -33,6 +33,8 @@ def get_mono( assembly_dir: Optional[str] = None, config_dir: Optional[str] = None, set_signal_chaining: bool = False, + trace_mask: Optional[str] = None, + trace_level: Optional[str] = None, ) -> Runtime: """Get a Mono runtime instance diff --git a/clr_loader/ffi/mono.py b/clr_loader/ffi/mono.py index c194393..a3249a1 100644 --- a/clr_loader/ffi/mono.py +++ b/clr_loader/ffi/mono.py @@ -44,5 +44,8 @@ void mono_set_signal_chaining(bool chain_signals); +void mono_trace_set_level_string(const char* value); +void mono_trace_set_mask_string(const char* value); + """ ) diff --git a/clr_loader/mono.py b/clr_loader/mono.py index 1899ea3..f7f90e5 100644 --- a/clr_loader/mono.py +++ b/clr_loader/mono.py @@ -27,6 +27,8 @@ def __init__( assembly_dir: Optional[str] = None, config_dir: Optional[str] = None, set_signal_chaining: bool = False, + trace_mask: Optional[str] = None, + trace_level: Optional[str] = None, ): self._assemblies: Dict[Path, Any] = {} @@ -39,6 +41,8 @@ def __init__( assembly_dir=assembly_dir, config_dir=config_dir, set_signal_chaining=set_signal_chaining, + trace_mask=trace_mask, + trace_level=trace_level, ) if domain is None: @@ -131,11 +135,19 @@ def initialize( assembly_dir: Optional[str] = None, config_dir: Optional[str] = None, set_signal_chaining: bool = False, + trace_mask: Optional[str] = None, + trace_level: Optional[str] = None, ) -> str: global _MONO, _ROOT_DOMAIN if _MONO is None: _MONO = load_mono(libmono) + if trace_mask is not None: + _MONO.mono_trace_set_mask_string(trace_mask.encode("utf8")) + + if trace_level is not None: + _MONO.mono_trace_set_level_string(trace_level.encode("utf8")) + if assembly_dir is not None and config_dir is not None: _MONO.mono_set_dirs(assembly_dir.encode("utf8"), config_dir.encode("utf8")) diff --git a/tests/test_common.py b/tests/test_common.py index 139f192..3eaf215 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -56,6 +56,24 @@ def test_mono_signal_chaining(example_netstandard: Path): run_tests(asm) +def test_mono_trace_mask(example_netstandard: Path): + from clr_loader import get_mono + + mono = get_mono(trace_mask="all") + asm = mono.get_assembly(example_netstandard / "example.dll") + + run_tests(asm) + + +def test_mono_trace_level(example_netstandard: Path): + from clr_loader import get_mono + + mono = get_mono(trace_level="message") + asm = mono.get_assembly(example_netstandard / "example.dll") + + run_tests(asm) + + def test_mono_set_dir(example_netstandard: Path): from clr_loader import get_mono From 5dadf4cedbe8e68075d50e626f26a8fc556591af Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Thu, 14 Aug 2025 16:44:40 -0500 Subject: [PATCH 16/19] Support pythonnet for AppDomain (#78) * load the assembly and get function address inside the domain When using domain.Load for an assembly, the assembly resolution rules are awkward Even if the full path is given to the AssemblyName, when the domain tries to load the assembly, it does not use that context and tries to resolve the assembly using normal domain resolution rules, which would require an assembly resolver to be installed. However, the assembly resolver that is actually used at runtime is the one installed to the main appdomain. This prevents a library like Python.Runtime.dll (used by pythonnet) which is not installed to the application base directory to be loaded by clr_loader. To fix this issue, the assembly resolver of the main appdomain is lazily extending to include paths needed for libraries passed into GetFunction, and GetFunction internally uses AppDomain.DoCallBack() to marshal the function pointer inside the target app domain, using global domain data to access the function pointer and return it to the user of clr_loader. * Add comment * PR review feedback --------- Co-authored-by: Mohamed Koubaa --- netfx_loader/ClrLoader.cs | 25 ++++++++-- netfx_loader/DomainData.cs | 99 ++++++++++++++++++++++++++++++-------- 2 files changed, 100 insertions(+), 24 deletions(-) diff --git a/netfx_loader/ClrLoader.cs b/netfx_loader/ClrLoader.cs index 32b4c01..2a065bb 100644 --- a/netfx_loader/ClrLoader.cs +++ b/netfx_loader/ClrLoader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Runtime.InteropServices; using NXPorts.Attributes; @@ -21,6 +22,19 @@ public static void Initialize() } } + private static string AssemblyDirectory + { + get + { + // This is needed in case the DLL was shadow-copied + // (Otherwise .Location would work) + string codeBase = Assembly.GetExecutingAssembly().CodeBase; + UriBuilder uri = new UriBuilder(codeBase); + string path = Uri.UnescapeDataString(uri.Path); + return Path.GetDirectoryName(path); + } + } + [DllExport("pyclr_create_appdomain", CallingConvention.Cdecl)] public static IntPtr CreateAppDomain( [MarshalAs(UnmanagedType.LPUTF8Str)] string name, @@ -28,16 +42,17 @@ public static IntPtr CreateAppDomain( ) { Print($"Creating AppDomain {name} with {configFile}"); + + var clrLoaderDir = AssemblyDirectory; if (!string.IsNullOrEmpty(name)) { var setup = new AppDomainSetup { - ApplicationBase = AppDomain.CurrentDomain.BaseDirectory, + ApplicationBase = clrLoaderDir, ConfigurationFile = configFile }; - Print($"Base: {AppDomain.CurrentDomain.BaseDirectory}"); + Print($"Base: {clrLoaderDir}"); var domain = AppDomain.CreateDomain(name, null, setup); - Print($"Located domain {domain}"); var domainData = new DomainData(domain); @@ -61,8 +76,8 @@ public static IntPtr GetFunction( try { var domainData = _domains[(int)domain]; - var deleg = domainData.GetEntryPoint(assemblyPath, typeName, function); - return Marshal.GetFunctionPointerForDelegate(deleg); + Print($"Getting functor for function {function} of type {typeName} in assembly {assemblyPath}"); + return domainData.GetFunctor(assemblyPath, typeName, function); } catch (Exception exc) { diff --git a/netfx_loader/DomainData.cs b/netfx_loader/DomainData.cs index 3a17d7a..35c3363 100644 --- a/netfx_loader/DomainData.cs +++ b/netfx_loader/DomainData.cs @@ -1,54 +1,115 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Runtime.InteropServices; namespace ClrLoader { using static ClrLoader; - class DomainData : IDisposable + public static class DomainSetup { public delegate int EntryPoint(IntPtr buffer, int size); + public static void StoreFunctorFromDomainData() + { + var domain = AppDomain.CurrentDomain; + var assemblyPath = (string)domain.GetData("_assemblyPath"); + var typeName = (string)domain.GetData("_typeName"); + var function = (string)domain.GetData("_function"); + var deleg = GetDelegate(domain, assemblyPath, typeName, function); + var functor = Marshal.GetFunctionPointerForDelegate(deleg); + domain.SetData("_thisDelegate", deleg); + domain.SetData("_thisFunctor", functor); + } + + private static Delegate GetDelegate(AppDomain domain, string assemblyPath, string typeName, string function) + { + var assemblyName = AssemblyName.GetAssemblyName(assemblyPath); + var assembly = domain.Load(assemblyName); + var type = assembly.GetType(typeName, throwOnError: true); + var deleg = Delegate.CreateDelegate(typeof(EntryPoint), type, function); + return deleg; + } + } + + class DomainData : IDisposable + { bool _disposed = false; public AppDomain Domain { get; } - public Dictionary<(string, string, string), EntryPoint> _delegates; + public Dictionary<(string, string, string), IntPtr> _functors; + public HashSet _resolvedAssemblies; public DomainData(AppDomain domain) { Domain = domain; - _delegates = new Dictionary<(string, string, string), EntryPoint>(); + _functors = new Dictionary<(string, string, string), IntPtr>(); + _resolvedAssemblies = new HashSet(); } - public EntryPoint GetEntryPoint(string assemblyPath, string typeName, string function) + private void installResolver(string assemblyPath) { - if (_disposed) - throw new InvalidOperationException("Domain is already disposed"); - - var key = (assemblyPath, typeName, function); - - EntryPoint result; + var assemblyName = AssemblyName.GetAssemblyName(assemblyPath).Name; + if (_resolvedAssemblies.Contains(assemblyName)) + return; + _resolvedAssemblies.Add(assemblyName); - if (!_delegates.TryGetValue(key, out result)) + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { - var assembly = Domain.Load(AssemblyName.GetAssemblyName(assemblyPath)); - var type = assembly.GetType(typeName, throwOnError: true); + if (args.Name.Contains(assemblyName)) + return Assembly.LoadFrom(assemblyPath); + return null; + }; + } - Print($"Loaded type {type}"); - result = (EntryPoint)Delegate.CreateDelegate(typeof(EntryPoint), type, function); + private static readonly object _lockObj = new object(); - _delegates[key] = result; - } + public IntPtr GetFunctor(string assemblyPath, string typeName, string function) + { + if (_disposed) + throw new InvalidOperationException("Domain is already disposed"); - return result; + // neither the domain data nor the _functors dictionary is threadsafe + lock (_lockObj) + { + installResolver(assemblyPath); + var assemblyName = AssemblyName.GetAssemblyName(assemblyPath).Name; + + var key = (assemblyName, typeName, function); + + IntPtr result; + if (!_functors.TryGetValue(key, out result)) + { + Domain.SetData("_assemblyPath", assemblyPath); + Domain.SetData("_typeName", typeName); + Domain.SetData("_function", function); + + Domain.DoCallBack(new CrossAppDomainDelegate(DomainSetup.StoreFunctorFromDomainData)); + result = (IntPtr)Domain.GetData("_thisFunctor"); + if (result == IntPtr.Zero) + throw new Exception($"Unable to get functor for {assemblyName}, {typeName}, {function}"); + + // set inputs to StoreFunctorFromDomainData to null. + // (There's no method to explicitly clear domain data) + Domain.SetData("_assemblyPath", null); + Domain.SetData("_typeName", null); + Domain.SetData("_function", null); + + // the result has to remain in the domain data because we don't know when the + // client of pyclr_get_function will actually invoke the functor, and if we + // remove it from the domain data after returning it may get collected too early. + _functors[key] = result; + } + return result; + } } public void Dispose() { if (!_disposed) { - _delegates.Clear(); + _functors.Clear(); if (Domain != AppDomain.CurrentDomain) AppDomain.Unload(Domain); From c3445ff0f5f7212154224717b29f64c48a81b88e Mon Sep 17 00:00:00 2001 From: Etienne Gaudrain Date: Thu, 14 Aug 2025 23:51:10 +0200 Subject: [PATCH 17/19] Added extra info about error when hostfxr not found or loaded properly. (#76) https://github.com/pythonnet/clr-loader/issues/75 --- clr_loader/ffi/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/clr_loader/ffi/__init__.py b/clr_loader/ffi/__init__.py index 23debae..289999d 100644 --- a/clr_loader/ffi/__init__.py +++ b/clr_loader/ffi/__init__.py @@ -23,18 +23,20 @@ def load_hostfxr(dotnet_root: Path): hostfxr_path = dotnet_root / "host" / "fxr" hostfxr_paths = hostfxr_path.glob(f"?.*/{hostfxr_name}") + error_report = list() + for hostfxr_path in reversed(sorted(hostfxr_paths, key=_path_to_version)): try: return ffi.dlopen(str(hostfxr_path)) - except Exception: - pass + except Exception as err: + error_report.append(f"Path {hostfxr_path} gave the following error:\n{err}") try: return ffi.dlopen(str(dotnet_root / hostfxr_name)) - except Exception: - pass + except Exception as err: + error_report.append(f"Path {hostfxr_path} gave the following error:\n{err}") - raise RuntimeError(f"Could not find a suitable hostfxr library in {dotnet_root}") + raise RuntimeError(f"Could not find a suitable hostfxr library in {dotnet_root}. The following paths were scanned:\n\n"+("\n\n".join(error_report))) def load_mono(path: Optional[Path] = None): From eeed6ec9ccc1be5222eb192beaef28570223a47e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 14 Aug 2025 23:53:35 +0200 Subject: [PATCH 18/19] Fix format --- clr_loader/ffi/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clr_loader/ffi/__init__.py b/clr_loader/ffi/__init__.py index 289999d..7917af9 100644 --- a/clr_loader/ffi/__init__.py +++ b/clr_loader/ffi/__init__.py @@ -36,7 +36,10 @@ def load_hostfxr(dotnet_root: Path): except Exception as err: error_report.append(f"Path {hostfxr_path} gave the following error:\n{err}") - raise RuntimeError(f"Could not find a suitable hostfxr library in {dotnet_root}. The following paths were scanned:\n\n"+("\n\n".join(error_report))) + raise RuntimeError( + f"Could not find a suitable hostfxr library in {dotnet_root}. The following paths were scanned:\n\n" + + ("\n\n".join(error_report)) + ) def load_mono(path: Optional[Path] = None): From 8c123c6147b6b7094cd6a61031db47156bff5522 Mon Sep 17 00:00:00 2001 From: Steven Lovelock Date: Thu, 14 Aug 2025 23:01:30 +0100 Subject: [PATCH 19/19] Add get_coreclr_command_line which uses hostfxr_initialize_for_dotnet_command_line (#66) 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 52ce603..2744cf0 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", @@ -152,6 +153,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 3eaf215..b9b0597 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -92,6 +92,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,