Skip to content

Commit 5da44c1

Browse files
committed
Refactor HostFxr a bit to allow setting properties
1 parent 23b1a09 commit 5da44c1

File tree

5 files changed

+159
-139
lines changed

5 files changed

+159
-139
lines changed

clr_loader/__init__.py

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,10 @@
1-
from os.path import basename
2-
from typing import Optional, Any
3-
from .ffi import ffi
1+
from typing import Dict, Optional
42

3+
from .wrappers import Runtime
54

65
__all__ = ["get_mono", "get_netfx", "get_coreclr"]
76

87

9-
RuntimeImpl = Any
10-
11-
12-
class ClrFunction:
13-
def __init__(
14-
self, runtime: RuntimeImpl, assembly: str, typename: str, func_name: str
15-
):
16-
self._assembly = assembly
17-
self._class = typename
18-
self._name = func_name
19-
20-
self._callable = runtime.get_callable(assembly, typename, func_name)
21-
22-
def __call__(self, buffer: bytes) -> int:
23-
buf_arr = ffi.from_buffer("char[]", buffer)
24-
return self._callable(ffi.cast("void*", buf_arr), len(buf_arr))
25-
26-
def __repr__(self) -> str:
27-
return f"<ClrFunction {self._class}.{self._name} in {basename(self._assembly)}>"
28-
29-
30-
class Assembly:
31-
def __init__(self, runtime: RuntimeImpl, path: str):
32-
self._runtime = runtime
33-
self._path = path
34-
35-
def get_function(self, name: str, func: Optional[str] = None) -> ClrFunction:
36-
if func is None:
37-
name, func = name.rsplit(".", 1)
38-
39-
return ClrFunction(self._runtime, self._path, name, func)
40-
41-
def __getitem__(self, name: str) -> ClrFunction:
42-
return self.get_function(name)
43-
44-
def __repr__(self) -> str:
45-
return f"<Assembly {self._path} in {self._runtime}>"
46-
47-
48-
class Runtime:
49-
def __init__(self, impl: RuntimeImpl):
50-
self._impl = impl
51-
52-
def get_assembly(self, path: str) -> Assembly:
53-
return Assembly(self._impl, path)
54-
55-
def __getitem__(self, path: str) -> Assembly:
56-
return self.get_assembly(path)
57-
58-
598
def get_mono(
609
domain: Optional[str] = None,
6110
config_file: Optional[str] = None,
@@ -68,10 +17,17 @@ def get_mono(
6817
return Runtime(impl)
6918

7019

71-
def get_coreclr(runtime_config: str, dotnet_root: Optional[str] = None) -> Runtime:
72-
from .hostfxr import HostFxr
20+
def get_coreclr(
21+
runtime_config: str, dotnet_root: Optional[str] = None,
22+
properties: Optional[Dict[str, str]] = None
23+
) -> Runtime:
24+
from .hostfxr import DotnetCoreRuntime
25+
26+
impl = DotnetCoreRuntime(runtime_config=runtime_config, dotnet_root=dotnet_root)
27+
if properties:
28+
for key, value in properties:
29+
impl[key] = value
7330

74-
impl = HostFxr(runtime_config=runtime_config, dotnet_root=dotnet_root)
7531
return Runtime(impl)
7632

7733

clr_loader/hostfxr.py

Lines changed: 48 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,69 @@
11
import os
2-
import shutil
32
import sys
3+
from typing import Optional
44

55
from .ffi import ffi, load_hostfxr
6-
from .util import check_result
6+
from .util import check_result, find_dotnet_root
77

8-
__all__ = ["HostFxr"]
8+
__all__ = ["DotnetCoreRuntime"]
99

1010

11-
class HostFxr:
12-
# TODO: Allow generating runtime_config
13-
def __init__(self, runtime_config, dotnet_root=None):
14-
self._handle = None
11+
class DotnetCoreRuntime:
12+
def __init__(self, runtime_config: str, dotnet_root: Optional[str] = None):
13+
self._dotnet_root = dotnet_root or find_dotnet_root()
14+
self._dll = load_hostfxr(self._dotnet_root)
15+
self._is_finalized = False
16+
self._handle = _get_handle(self._dll, self._dotnet_root, runtime_config)
17+
self._load_func = _get_load_func(self._dll, self._handle)
1518

16-
if not dotnet_root:
17-
dotnet_root = os.environ.get("DOTNET_ROOT", None)
19+
@property
20+
def dotnet_root(self):
21+
return self._dotnet_root
1822

19-
if not dotnet_root and sys.platform == "win32":
20-
# On Windows, the host library is stored separately from dotnet.exe for x86
21-
if sys.maxsize > 2 ** 32:
22-
possible_root = os.path.join(os.environ.get("ProgramFiles"), "dotnet")
23-
else:
24-
possible_root = os.path.join(
25-
os.environ.get("ProgramFiles(x86)"), "dotnet"
26-
)
23+
@property
24+
def is_finalized(self):
25+
return self._is_finalized
2726

28-
if os.path.isdir(possible_root):
29-
dotnet_root = possible_root
27+
def __getitem__(self, key: str) -> str:
28+
buf = ffi.new("char_t**")
29+
res = self._dll.hostfxr_get_runtime_property_value(
30+
self._handle, encode(key), buf
31+
)
32+
if res != 0:
33+
raise KeyError(key)
3034

31-
if not dotnet_root:
32-
dotnet_path = shutil.which("dotnet")
33-
if not dotnet_path:
34-
raise RuntimeError("Can not determine dotnet root")
35+
return decode(buf[0])
3536

36-
try:
37-
# Pypy does not provide os.readlink right now
38-
if hasattr(os, "readlink"):
39-
dotnet_tmp_path = os.readlink(dotnet_path)
40-
else:
41-
dotnet_tmp_path = dotnet_path
37+
def __setitem__(self, key: str, value: str) -> None:
38+
if self.is_finalized:
39+
raise RuntimeError("Already finalized")
4240

43-
if os.path.isabs(dotnet_tmp_path):
44-
dotnet_path = dotnet_tmp_path
45-
else:
46-
dotnet_path = os.path.abspath(
47-
os.path.join(os.path.dirname(dotnet_path), dotnet_tmp_path)
48-
)
49-
except OSError:
50-
pass
41+
res = self._dll.hostfxr_set_runtime_property_value(
42+
self._handle, encode(key), encode(value)
43+
)
44+
check_result(res)
5145

52-
dotnet_root = os.path.dirname(dotnet_path)
46+
def __iter__(self):
47+
max_size = 100
48+
size_ptr = ffi.new("size_t*")
49+
size_ptr[0] = max_size
5350

54-
self._dll = load_hostfxr(dotnet_root)
55-
self._dotnet_root = dotnet_root
51+
keys_ptr = ffi.new("char_t*[]", max_size)
52+
values_ptr = ffi.new("char_t*[]", max_size)
5653

57-
self._handle = _get_handle(self._dll, self._dotnet_root, runtime_config)
58-
self._load_func = _get_load_func(self._dll, self._handle)
54+
res = self._fxr._dll.hostfxr_get_runtime_properties(
55+
self._fxr._handle, size_ptr, keys_ptr, values_ptr
56+
)
57+
check_result(res)
58+
59+
for i in range(size_ptr[0]):
60+
yield (decode(keys_ptr[i]), decode(values_ptr[i]))
5961

6062
def get_callable(self, assembly_path, typename, function):
63+
# TODO: Maybe use coreclr_get_delegate as well, supported with newer API
64+
# versions of hostfxr
65+
self._is_finalized = True
66+
6167
# Append assembly name to typename
6268
assembly_name, _ = os.path.splitext(os.path.basename(assembly_path))
6369
typename = f"{typename}, {assembly_name}"
@@ -82,47 +88,6 @@ def shutdown(self):
8288
def __del__(self):
8389
self.shutdown()
8490

85-
@property
86-
def props(self):
87-
return HostFxrProps(self)
88-
89-
90-
class HostFxrProps:
91-
def __init__(self, fxr):
92-
self._fxr = fxr
93-
94-
def __getitem__(self, key):
95-
buf = ffi.new("char_t**")
96-
res = self._fxr._dll.hostfxr_get_runtime_property_value(
97-
self._fxr._handle, encode(key), buf
98-
)
99-
if res != 0:
100-
raise KeyError(key)
101-
102-
return decode(buf[0])
103-
104-
def __setitem__(self, key, value):
105-
res = self._fxr._dll.hostfxr_set_runtime_property_value(
106-
self._fxr._handle, encode(key), encode(value)
107-
)
108-
check_result(res)
109-
110-
def __iter__(self):
111-
max_size = 100
112-
size_ptr = ffi.new("size_t*")
113-
size_ptr[0] = max_size
114-
115-
keys_ptr = ffi.new("char_t*[]", max_size)
116-
values_ptr = ffi.new("char_t*[]", max_size)
117-
118-
res = self._fxr._dll.hostfxr_get_runtime_properties(
119-
self._fxr._handle, size_ptr, keys_ptr, values_ptr
120-
)
121-
check_result(res)
122-
123-
for i in range(size_ptr[0]):
124-
yield (decode(keys_ptr[i]), decode(values_ptr[i]))
125-
12691

12792
def _get_handle(dll, dotnet_root, runtime_config):
12893
params = ffi.new("hostfxr_initialize_parameters*")

clr_loader/util/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from .clr_error import ClrError
22
from .coreclr_errors import get_coreclr_error
3+
from .find import find_dotnet_root
34
from .hostfxr_errors import get_hostfxr_error
45

6+
__all__ = ["check_result", "find_dotnet_root"]
7+
58

69
def check_result(err_code: int) -> None:
710
"""Check the error code of a .NET hosting API function and raise a

clr_loader/util/find.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
import os.path
3+
import shutil
4+
import sys
5+
6+
7+
def find_dotnet_root() -> str:
8+
dotnet_root = os.environ.get("DOTNET_ROOT", None)
9+
if dotnet_root is not None:
10+
return dotnet_root
11+
12+
if sys.platform == "win32":
13+
# On Windows, the host library is stored separately from dotnet.exe for x86
14+
if sys.maxsize > 2 ** 32:
15+
prog_files = os.environ.get("ProgramFiles")
16+
else:
17+
prog_files = os.environ.get("ProgramFiles(x86)")
18+
19+
dotnet_root = os.path.join(prog_files, "dotnet")
20+
if os.path.isdir(dotnet_root):
21+
return dotnet_root
22+
23+
# Try to discover dotnet from PATH otherwise
24+
dotnet_path = shutil.which("dotnet")
25+
if not dotnet_path:
26+
raise RuntimeError("Can not determine dotnet root")
27+
28+
try:
29+
# Pypy does not provide os.readlink right now
30+
if hasattr(os, "readlink"):
31+
dotnet_tmp_path = os.readlink(dotnet_path)
32+
else:
33+
dotnet_tmp_path = dotnet_path
34+
35+
if os.path.isabs(dotnet_tmp_path):
36+
dotnet_path = dotnet_tmp_path
37+
else:
38+
dotnet_path = os.path.abspath(
39+
os.path.join(os.path.dirname(dotnet_path), dotnet_tmp_path)
40+
)
41+
except OSError:
42+
pass
43+
44+
return os.path.dirname(dotnet_path)

clr_loader/wrappers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from typing import Any, Optional
2+
from .ffi import ffi
3+
from os.path import basename
4+
5+
RuntimeImpl = Any
6+
7+
8+
class ClrFunction:
9+
def __init__(
10+
self, runtime: RuntimeImpl, assembly: str, typename: str, func_name: str
11+
):
12+
self._assembly = assembly
13+
self._class = typename
14+
self._name = func_name
15+
16+
self._callable = runtime.get_callable(assembly, typename, func_name)
17+
18+
def __call__(self, buffer: bytes) -> int:
19+
buf_arr = ffi.from_buffer("char[]", buffer)
20+
return self._callable(ffi.cast("void*", buf_arr), len(buf_arr))
21+
22+
def __repr__(self) -> str:
23+
return f"<ClrFunction {self._class}.{self._name} in {basename(self._assembly)}>"
24+
25+
26+
class Assembly:
27+
def __init__(self, runtime: RuntimeImpl, path: str):
28+
self._runtime = runtime
29+
self._path = path
30+
31+
def get_function(self, name: str, func: Optional[str] = None) -> ClrFunction:
32+
if func is None:
33+
name, func = name.rsplit(".", 1)
34+
35+
return ClrFunction(self._runtime, self._path, name, func)
36+
37+
def __getitem__(self, name: str) -> ClrFunction:
38+
return self.get_function(name)
39+
40+
def __repr__(self) -> str:
41+
return f"<Assembly {self._path} in {self._runtime}>"
42+
43+
44+
class Runtime:
45+
def __init__(self, impl: RuntimeImpl):
46+
self._impl = impl
47+
48+
def get_assembly(self, path: str) -> Assembly:
49+
return Assembly(self._impl, path)
50+
51+
def __getitem__(self, path: str) -> Assembly:
52+
return self.get_assembly(path)

0 commit comments

Comments
 (0)