Skip to content

Commit 5c510ab

Browse files
committed
Defer resolving configurations until inserted into dict
1 parent cdc5199 commit 5c510ab

File tree

7 files changed

+215
-90
lines changed

7 files changed

+215
-90
lines changed

docs/src/python/charonload.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
charonload/CMakeConfigureError
1414
charonload/CommandNotFoundError
1515
charonload/Config
16+
charonload/ConfigDict
1617
charonload/JITCompileFinder
1718
charonload/JITCompileError
19+
charonload/ResolvedConfig
1820
charonload/StubGenerationError
1921
charonload/extension_finder
2022
charonload/module_config
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ConfigDict
2+
==========
3+
4+
.. currentmodule:: charonload
5+
6+
.. autoclass:: ConfigDict
7+
:special-members: __setitem__
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ResolvedConfig
2+
==============
3+
4+
.. currentmodule:: charonload
5+
6+
.. autoclass:: ResolvedConfig

src/charonload/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import email.utils
3333
import importlib.metadata
3434

35-
from ._config import Config
35+
from ._config import Config, ConfigDict, ResolvedConfig
3636
from ._errors import (
3737
BuildError,
3838
CMakeConfigureError,
@@ -57,9 +57,11 @@
5757
"CMakeConfigureError",
5858
"CommandNotFoundError",
5959
"Config",
60+
"ConfigDict",
6061
"extension_finder",
6162
"JITCompileError",
6263
"JITCompileFinder",
6364
"module_config",
65+
"ResolvedConfig",
6466
"StubGenerationError",
6567
]

src/charonload/_config.py

Lines changed: 147 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import sys
1010
import sysconfig
1111
import tempfile
12+
from collections import UserDict
1213
from dataclasses import dataclass
1314
from typing import TYPE_CHECKING
1415

@@ -20,9 +21,85 @@
2021
colorama.just_fix_windows_console()
2122

2223

23-
@dataclass(init=False)
24+
@dataclass(init=False) # Python 3.10+: Use "_: KW_ONLY"
2425
class Config:
25-
"""The set of configuration options required for the import logic of the :class:`JITCompileFinder`."""
26+
"""
27+
Set of user-specified configuration options required for the import logic of the :class:`JITCompileFinder`.
28+
29+
This will be resolved into :class:`ResolvedConfig`.
30+
"""
31+
32+
project_directory: pathlib.Path | str
33+
"""The absolute path to the root directory of the C++/CUDA extension containing the root ``CMakeLists.txt`` file."""
34+
35+
build_directory: pathlib.Path | str | None
36+
"""
37+
An optional absolute path to a build directory.
38+
39+
If not specified, the build will be placed in the temporary directory of the operating system.
40+
"""
41+
42+
clean_build: bool
43+
"""
44+
Whether to remove all cached files of previous builds from the build directory.
45+
46+
This is useful to ensure consistent behavior after major changes in the CMake files of the project.
47+
"""
48+
49+
build_type: str
50+
"""The build type passed to CMake to compile the extension."""
51+
52+
cmake_options: dict[str, str] | None
53+
"""Additional CMake options to pass to the project when JIT compiling."""
54+
55+
stubs_directory: pathlib.Path | str | None
56+
"""
57+
An optional absolute path to the directory where stub files of the extension should be generated.
58+
59+
This is useful for IDEs to get syntax highlighting and auto-completion for the extension content. For VS Code, the
60+
respective (default) directory to specify here is ``<project root directory>/typings``. Stub generation is disabled
61+
if set to ``None``.
62+
"""
63+
64+
stubs_invalid_ok: bool
65+
"""Whether to accept invalid stubs and skip raising an error."""
66+
67+
verbose: bool
68+
"""
69+
Whether to enable printing the full log of the JIT compilation.
70+
71+
This is useful for debugging.
72+
"""
73+
74+
def __init__(
75+
self: Self,
76+
project_directory: pathlib.Path | str,
77+
build_directory: pathlib.Path | str | None = None,
78+
*,
79+
clean_build: bool = False,
80+
build_type: str = "RelWithDebInfo",
81+
cmake_options: dict[str, str] | None = None,
82+
stubs_directory: pathlib.Path | str | None = None,
83+
stubs_invalid_ok: bool = False,
84+
verbose: bool = False,
85+
) -> None:
86+
self.project_directory = project_directory
87+
self.build_directory = build_directory
88+
self.clean_build = clean_build
89+
self.build_type = build_type
90+
self.cmake_options = cmake_options
91+
self.stubs_directory = stubs_directory
92+
self.stubs_invalid_ok = stubs_invalid_ok
93+
self.verbose = verbose
94+
95+
96+
@dataclass(init=False) # Python 3.10+: Use "kw_only=True"
97+
class ResolvedConfig:
98+
"""
99+
Set of resolved configuration options that are actually used for the import logic of the :class:`JITCompileFinder`.
100+
101+
This has been resolved from :class:`Config`.
102+
"""
26103

27104
full_project_directory: pathlib.Path
28105
"""The full absolute path to the project directory."""
@@ -50,69 +127,76 @@ class Config:
50127

51128
def __init__(
52129
self: Self,
53-
project_directory: pathlib.Path | str,
54-
build_directory: pathlib.Path | str | None = None,
55130
*,
56-
clean_build: bool = False,
57-
build_type: str = "RelWithDebInfo",
58-
cmake_options: dict[str, str] | None = None,
59-
stubs_directory: pathlib.Path | str | None = None,
60-
stubs_invalid_ok: bool = False,
61-
verbose: bool = False,
131+
full_project_directory: pathlib.Path,
132+
full_build_directory: pathlib.Path,
133+
clean_build: bool,
134+
build_type: str,
135+
cmake_options: dict[str, str],
136+
full_stubs_directory: pathlib.Path | None,
137+
stubs_invalid_ok: bool,
138+
verbose: bool,
62139
) -> None:
140+
self.full_project_directory = full_project_directory
141+
self.full_build_directory = full_build_directory
142+
self.clean_build = clean_build
143+
self.build_type = build_type
144+
self.cmake_options = cmake_options
145+
self.full_stubs_directory = full_stubs_directory
146+
self.stubs_invalid_ok = stubs_invalid_ok
147+
self.verbose = verbose
148+
149+
150+
class ConfigDict(UserDict[str, ResolvedConfig]):
151+
"""
152+
A configuration dictionary for holding resolved :class:`Config` instances.
153+
154+
Configurations will be resolved during insertion into the dictionary.
155+
"""
156+
157+
def __setitem__(self: Self, key: str, value: Config | ResolvedConfig) -> None:
63158
"""
64-
Create the configuration options from the provided parameters.
159+
Resolve a user-specified configuration :class:`Config` into :class:`ResolvedConfig` and insert it.
65160
66161
Parameters
67162
----------
68-
project_directory
69-
The absolute path to the root directory of the C++/CUDA extension containing the root ``CMakeLists.txt``
70-
file.
71-
build_directory
72-
An optional absolute path to a build directory. If not specified, the build will be placed in the
73-
temporary directory of the operating system.
74-
clean_build
75-
Whether to remove all cached files of previous builds from the build directory. This is useful to ensure
76-
consistent behavior after major changes in the CMake files of the project.
77-
build_type
78-
The build type passed to CMake to compile the extension.
79-
cmake_options
80-
Additional CMake options to pass to the project when JIT compiling.
81-
stubs_directory
82-
An optional absolute path to the directory where stub files of the extension should be generated. This is
83-
useful for IDEs to get syntax highlighting and auto-completion for the extension content. For VS Code, the
84-
respective (default) directory to specify here is ``<project root directory>/typings``. Stub generation is
85-
disabled if set to ``None``.
86-
stubs_invalid_ok
87-
Whether to accept invalid stubs and skip raising an error.
88-
verbose
89-
Whether to enable printing the full log of the JIT compilation. This is useful for debugging.
163+
key
164+
The associated key of the configuration.
165+
value
166+
A user-specified or already resolved configuration.
90167
91168
Raises
92169
------
93170
ValueError
94-
If either:
95-
1) ``project_directory``,``build_directory``, or ``stubs_directory`` are not absolute paths,
96-
2) ``project_directory`` does not exists, or
97-
3) Prohibited options are inserted into ``cmake_options``.
171+
During resolution if:
172+
1) ``config.project_directory``, ``config.build_directory``, or ``config.stubs_directory`` are not
173+
absolute paths,
174+
2) ``config.project_directory`` does not exist, or
175+
3) ``config.cmake_options`` contains prohibited options.
98176
"""
99-
if not pathlib.Path(project_directory).is_absolute():
100-
msg = f'Expected absolute project directory, but got relative directory "{project_directory}"'
177+
super().__setitem__(
178+
key,
179+
self._resolve(value) if isinstance(value, Config) else value,
180+
)
181+
182+
def _resolve(self: Self, config: Config) -> ResolvedConfig:
183+
if not pathlib.Path(config.project_directory).is_absolute():
184+
msg = f'Expected absolute project directory, but got relative directory "{config.project_directory}"'
101185
raise ValueError(msg)
102186

103-
if not pathlib.Path(project_directory).resolve().exists():
104-
msg = f'Expected existing project directory, but got non-existing directory "{project_directory}"'
187+
if not pathlib.Path(config.project_directory).resolve().exists():
188+
msg = f'Expected existing project directory, but got non-existing directory "{config.project_directory}"'
105189
raise ValueError(msg)
106190

107-
if build_directory is not None and not pathlib.Path(build_directory).is_absolute():
108-
msg = f'Expected absolute build directory, but got relative directory "{build_directory}"'
191+
if config.build_directory is not None and not pathlib.Path(config.build_directory).is_absolute():
192+
msg = f'Expected absolute build directory, but got relative directory "{config.build_directory}"'
109193
raise ValueError(msg)
110194

111-
if stubs_directory is not None and not pathlib.Path(stubs_directory).is_absolute():
112-
msg = f'Expected absolute stub directory, but got relative directory "{stubs_directory}"'
195+
if config.stubs_directory is not None and not pathlib.Path(config.stubs_directory).is_absolute():
196+
msg = f'Expected absolute stub directory, but got relative directory "{config.stubs_directory}"'
113197
raise ValueError(msg)
114198

115-
if cmake_options is not None:
199+
if config.cmake_options is not None:
116200
prohibited_cmake_options = {
117201
"CHARONLOAD_.*",
118202
"CMAKE_CONFIGURATION_TYPES",
@@ -121,27 +205,29 @@ def __init__(
121205
"TORCH_EXTENSION_NAME",
122206
}
123207

124-
for k in cmake_options:
208+
for k in config.cmake_options:
125209
for pk in prohibited_cmake_options:
126210
if re.search(pk, k) is not None:
127211
msg = f'Found prohibited CMake option="{k}" which is not allowed or supported.'
128212
raise ValueError(msg)
129213

130-
self.full_project_directory = pathlib.Path(project_directory).resolve()
131-
self.full_build_directory = self._find_build_directory(
132-
build_directory=build_directory,
133-
project_directory=project_directory,
134-
verbose=verbose,
214+
return ResolvedConfig(
215+
full_project_directory=pathlib.Path(config.project_directory).resolve(),
216+
full_build_directory=self._find_build_directory(
217+
build_directory=config.build_directory,
218+
project_directory=config.project_directory,
219+
verbose=config.verbose,
220+
),
221+
clean_build=config.clean_build,
222+
build_type=config.build_type,
223+
cmake_options=config.cmake_options if config.cmake_options is not None else {},
224+
full_stubs_directory=self._find_stubs_directory(
225+
stubs_directory=config.stubs_directory,
226+
verbose=config.verbose,
227+
),
228+
stubs_invalid_ok=config.stubs_invalid_ok,
229+
verbose=config.verbose,
135230
)
136-
self.clean_build = clean_build
137-
self.build_type = build_type
138-
self.cmake_options = cmake_options if cmake_options is not None else {}
139-
self.full_stubs_directory = self._find_stubs_directory(
140-
stubs_directory=stubs_directory,
141-
verbose=verbose,
142-
)
143-
self.stubs_invalid_ok = stubs_invalid_ok
144-
self.verbose = verbose
145231

146232
def _find_build_directory(
147233
self: Self,

0 commit comments

Comments
 (0)