Skip to content

Commit 80b1732

Browse files
authored
Merge pull request #32 from pythonnet/documentation
Documentation
2 parents 46143d4 + 4cbd29f commit 80b1732

File tree

16 files changed

+464
-15
lines changed

16 files changed

+464
-15
lines changed

.github/workflows/docs.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Documentation
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v1
10+
- uses: ammaraskar/sphinx-action@master
11+
with:
12+
docs-folder: doc/
13+
- name: Upload artifact
14+
# Automatically uploads an artifact from the './_site' directory by default
15+
uses: actions/upload-pages-artifact@v1
16+
with:
17+
path: doc/_build/html/
18+
19+
deploy:
20+
if: github.ref == 'refs/heads/master'
21+
runs-on: ubuntu-latest
22+
environment:
23+
name: github-pages
24+
url: ${{ steps.deployment.outputs.page_url }}
25+
needs: build
26+
steps:
27+
- name: Deploy to GitHub Pages
28+
id: deployment
29+
uses: actions/deploy-pages@v1

clr_loader/__init__.py

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,46 @@
1717
"Runtime",
1818
"Assembly",
1919
"RuntimeInfo",
20+
"DotnetCoreRuntimeSpec",
2021
]
2122

2223

2324
def get_mono(
2425
*,
25-
domain: Optional[str] = None,
26+
# domain: Optional[str] = None,
2627
config_file: Optional[StrOrPath] = None,
2728
global_config_file: Optional[StrOrPath] = None,
2829
libmono: Optional[StrOrPath] = None,
2930
sgen: bool = True,
3031
debug: bool = False,
3132
jit_options: Optional[Sequence[str]] = None,
3233
) -> Runtime:
34+
"""Get a Mono runtime instance
35+
36+
:param config_file:
37+
Path to the domain configuration file
38+
:param global_config_file:
39+
Path to the global configuration file to load (defaults to, e.g.,
40+
``/etc/mono/config``)
41+
:param libmono:
42+
Path to the Mono runtime dll/so/dylib. If this is not specified, we try
43+
to discover a globally installed instance using :py:func:`find_libmono`
44+
:param sgen:
45+
If ``libmono`` is not specified, this is passed to
46+
:py:func:`find_libmono`
47+
:param debug:
48+
Whether to initialise Mono debugging
49+
:param jit_options:
50+
"Command line options" passed to Mono's ``mono_jit_parse_options``
51+
"""
3352
from .mono import Mono
3453

3554
libmono = _maybe_path(libmono)
3655
if libmono is None:
37-
libmono = find_libmono(sgen)
56+
libmono = find_libmono(sgen=sgen)
3857

3958
impl = Mono(
40-
domain=domain,
59+
# domain=domain,
4160
debug=debug,
4261
jit_options=jit_options,
4362
config_file=_maybe_path(config_file),
@@ -54,6 +73,27 @@ def get_coreclr(
5473
properties: Optional[Dict[str, str]] = None,
5574
runtime_spec: Optional[DotnetCoreRuntimeSpec] = None,
5675
) -> Runtime:
76+
"""Get a CoreCLR (.NET Core) runtime instance
77+
78+
The returned ``DotnetCoreRuntime`` also acts as a mapping of the config
79+
properties. They can be retrieved using the index operator and can be
80+
written until the runtime is initialized. The runtime is initialized when
81+
the first function object is retrieved.
82+
83+
:param runtime_config:
84+
Pass to a ``runtimeconfig.json`` as generated by
85+
``dotnet publish``. If this parameter is not given, a temporary runtime
86+
config will be generated.
87+
:param dotnet_root:
88+
The root directory of the .NET Core installation. If this is not
89+
specified, we try to discover it using :py:func:`find_dotnet_root`.
90+
:param properties:
91+
Additional runtime properties. These can also be passed using the
92+
``configProperties`` section in the runtime config.
93+
:param runtime_spec:
94+
If the ``runtime_config`` is not specified, the concrete runtime to use
95+
can be controlled by passing this parameter. Possible values can be
96+
retrieved using :py:func:`find_runtimes`."""
5797
from .hostfxr import DotnetCoreRuntime
5898

5999
dotnet_root = _maybe_path(dotnet_root)
@@ -91,11 +131,21 @@ def get_coreclr(
91131

92132

93133
def get_netfx(
94-
*, name: Optional[str] = None, config_file: Optional[StrOrPath] = None
134+
*, domain: Optional[str] = None, config_file: Optional[StrOrPath] = None
95135
) -> Runtime:
136+
"""Get a .NET Framework runtime instance
137+
138+
:param domain:
139+
Name of the domain to create. If no value is passed, assemblies will be
140+
loaded into the root domain.
141+
:param config_file:
142+
Configuration file to use to initialize the ``AppDomain``. This will
143+
only be used for non-root-domains as we can not control the
144+
configuration of the implicitly loaded root domain.
145+
"""
96146
from .netfx import NetFx
97147

98-
impl = NetFx(name=name, config_file=_maybe_path(config_file))
148+
impl = NetFx(domain=domain, config_file=_maybe_path(config_file))
99149
return impl
100150

101151

clr_loader/hostfxr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def __iter__(self) -> Generator[Tuple[str, str], None, None]:
7979
for i in range(size_ptr[0]):
8080
yield (decode(keys_ptr[i]), decode(values_ptr[i]))
8181

82-
def get_callable(self, assembly_path: StrOrPath, typename: str, function: str):
82+
def _get_callable(self, assembly_path: StrOrPath, typename: str, function: str):
8383
# TODO: Maybe use coreclr_get_delegate as well, supported with newer API
8484
# versions of hostfxr
8585
self._is_initialized = True

clr_loader/mono.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def __init__(
4141
else:
4242
raise NotImplementedError
4343

44-
def get_callable(self, assembly_path, typename, function):
44+
def _get_callable(self, assembly_path, typename, function):
4545
assembly_path = Path(assembly_path)
4646
assembly = self._assemblies.get(assembly_path)
4747
if not assembly:

clr_loader/netfx.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,31 @@
99

1010

1111
class NetFx(Runtime):
12-
def __init__(self, name: Optional[str] = None, config_file: Optional[Path] = None):
12+
def __init__(
13+
self, domain: Optional[str] = None, config_file: Optional[Path] = None
14+
):
1315
initialize()
1416
if config_file is not None:
1517
config_file_s = str(config_file)
1618
else:
1719
config_file_s = ffi.NULL
1820

19-
self._name = name
21+
self._domain = domain
2022
self._config_file = config_file
21-
self._domain = _FW.pyclr_create_appdomain(name or ffi.NULL, config_file_s)
23+
self._domain = _FW.pyclr_create_appdomain(domain or ffi.NULL, config_file_s)
2224

2325
def info(self) -> RuntimeInfo:
2426
return RuntimeInfo(
2527
kind=".NET Framework",
2628
version="<undefined>",
2729
initialized=True,
2830
shutdown=_FW is None,
29-
properties={},
31+
properties=dict(
32+
domain=self._domain or "", config_file=str(self._config_file)
33+
),
3034
)
3135

32-
def get_callable(self, assembly_path: StrOrPath, typename: str, function: str):
36+
def _get_callable(self, assembly_path: StrOrPath, typename: str, function: str):
3337
func = _FW.pyclr_get_function(
3438
self._domain,
3539
str(Path(assembly_path)).encode("utf8"),

clr_loader/types.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@
1010

1111
@dataclass
1212
class RuntimeInfo:
13+
"""Information on a Runtime instance
14+
15+
An informative text can be retrieved from this by converting it to a
16+
``str``, in particular the following results in readable debug information:
17+
18+
>>> ri = RuntimeInfo()
19+
>>> print(ri)
20+
6.12.0.122 (tarball)
21+
Runtime: Mono
22+
=============
23+
Version: 6.12.0.122 (tarball)
24+
Initialized: True
25+
Shut down: False
26+
Properties:
27+
"""
28+
1329
kind: str
1430
version: str
1531
initialized: bool
@@ -39,7 +55,7 @@ def __init__(
3955
self._class = typename
4056
self._name = func_name
4157

42-
self._callable = runtime.get_callable(assembly, typename, func_name)
58+
self._callable = runtime._get_callable(assembly, typename, func_name)
4359

4460
def __call__(self, buffer: bytes) -> int:
4561
from .ffi import ffi
@@ -57,6 +73,21 @@ def __init__(self, runtime: "Runtime", path: StrOrPath):
5773
self._path = path
5874

5975
def get_function(self, name: str, func: Optional[str] = None) -> ClrFunction:
76+
"""Get a wrapped .NET function instance
77+
78+
The function must be ``static``, and it must have the signature
79+
``int Func(IntPtr ptr, int size)``. The returned wrapped instance will
80+
take a ``binary`` and call the .NET function with a pointer to that
81+
buffer and the buffer length. The buffer is reflected using CFFI's
82+
`from_buffer`.
83+
84+
:param name: If ``func`` is not given, this is the fully qualified name
85+
of the function. If ``func`` is given, this is the fully
86+
qualified name of the containing class
87+
:param func: Name of the function
88+
:return: A function object that takes a single ``binary`` parameter
89+
and returns an ``int``
90+
"""
6091
if func is None:
6192
name, func = name.rsplit(".", 1)
6293

@@ -67,21 +98,39 @@ def __repr__(self) -> str:
6798

6899

69100
class Runtime(metaclass=ABCMeta):
101+
"""CLR Runtime
102+
103+
Encapsulates the lifetime of a CLR (.NET) runtime. If the instance is
104+
deleted, the runtime will be shut down.
105+
"""
106+
70107
@abstractmethod
71108
def info(self) -> RuntimeInfo:
109+
"""Get configuration and version information"""
72110
pass
73111

74112
def get_assembly(self, assembly_path: StrOrPath) -> Assembly:
113+
"""Get an assembly wrapper
114+
115+
This function does not guarantee that the respective assembly is or can
116+
be loaded. Due to the design of the different hosting APIs, loading only
117+
happens when the first function is referenced, and only then potential
118+
errors will be raised."""
75119
return Assembly(self, assembly_path)
76120

77121
@abstractmethod
78-
def get_callable(
122+
def _get_callable(
79123
self, assembly_path: StrOrPath, typename: str, function: str
80124
) -> Callable[[Any, int], Any]:
125+
"""Private function to retrieve a low-level callable object"""
81126
pass
82127

83128
@abstractmethod
84129
def shutdown(self) -> None:
130+
"""Shut down the runtime as much as possible
131+
132+
Implementations should still be able to "reinitialize", thus the final
133+
cleanup will usually happen in an ``atexit`` handler."""
85134
pass
86135

87136
def __del__(self) -> None:

clr_loader/util/find.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ def find_dotnet_cli() -> Optional[Path]:
1717

1818

1919
def find_dotnet_root() -> Path:
20+
"""Try to discover the .NET Core root directory
21+
22+
If the environment variable ``DOTNET_ROOT`` is defined, we will use that.
23+
Otherwise, we probe the default installation paths on Windows and macOS.
24+
25+
If none of these lead to a result, we try to discover the ``dotnet`` CLI
26+
tool and use its (real) parent directory.
27+
28+
Otherwise, this function raises an exception.
29+
30+
:return: Path to the .NET Core root
31+
"""
32+
2033
dotnet_root = os.environ.get("DOTNET_ROOT", None)
2134
if dotnet_root is not None:
2235
return Path(dotnet_root)
@@ -69,6 +82,17 @@ def find_runtimes_in_root(dotnet_root: Path) -> Iterator[DotnetCoreRuntimeSpec]:
6982

7083

7184
def find_runtimes() -> Iterator[DotnetCoreRuntimeSpec]:
85+
"""Find installed .NET Core runtimes
86+
87+
If the ``dotnet`` CLI can be found, we will call it as ``dotnet
88+
--list-runtimes`` and parse the result.
89+
90+
If it is not available, we try to discover the dotnet root directory using
91+
:py:func:`find_dotnet_root` and enumerate the runtimes installed in the
92+
``shared`` subdirectory.
93+
94+
:return: Iterable of :py:class:`DotnetCoreRuntimeSpec` objects
95+
"""
7296
dotnet_cli = find_dotnet_cli()
7397
if dotnet_cli is not None:
7498
return find_runtimes_using_cli(dotnet_cli)
@@ -77,7 +101,18 @@ def find_runtimes() -> Iterator[DotnetCoreRuntimeSpec]:
77101
return find_runtimes_in_root(dotnet_root)
78102

79103

80-
def find_libmono(sgen: bool = True) -> Path:
104+
def find_libmono(*, sgen: bool = True) -> Path:
105+
"""Find a suitable libmono dynamic library
106+
107+
On Windows and macOS, we check the default installation directories.
108+
109+
:param sgen:
110+
Whether to look for an SGen or Boehm GC instance. This parameter is
111+
ignored on Windows, as only ``sgen`` is installed with the default
112+
installer
113+
:return:
114+
Path to usable ``libmono``
115+
"""
81116
unix_name = f"mono{'sgen' if sgen else ''}-2.0"
82117
if sys.platform == "win32":
83118
if sys.maxsize > 2**32:

clr_loader/util/runtime_spec.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
@dataclass
88
class DotnetCoreRuntimeSpec:
9+
"""Specification of an installed .NET Core runtime"""
10+
911
name: str
1012
version: str
1113
path: Path

doc/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
_build/

doc/Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line, and also
5+
# from the environment for the first two.
6+
SPHINXOPTS ?=
7+
SPHINXBUILD ?= sphinx-build
8+
SOURCEDIR = .
9+
BUILDDIR = _build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

0 commit comments

Comments
 (0)