99import sys
1010import sysconfig
1111import tempfile
12+ from collections import UserDict
1213from dataclasses import dataclass
1314from typing import TYPE_CHECKING
1415
2021colorama .just_fix_windows_console ()
2122
2223
23- @dataclass (init = False )
24+ @dataclass (init = False ) # Python 3.10+: Use "_: KW_ONLY"
2425class 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