|
1 | | -"""PEP 517 build backend implementation wrapping scikit-build-core. |
| 1 | +"""PEP 517 build backend implementation wrapping scikit-build-core and meson-python. |
2 | 2 |
|
3 | | -This module provides the actual build hooks that delegate to scikit-build-core |
| 3 | +This module provides the actual build hooks that delegate to the appropriate |
| 4 | +underlying build backend (scikit-build-core for CMake, meson-python for Meson) |
4 | 5 | after running CPPython's preparation workflow. |
5 | 6 | """ |
6 | 7 |
|
7 | 8 | import logging |
| 9 | +import tomllib |
8 | 10 | from pathlib import Path |
| 11 | +from types import ModuleType |
9 | 12 | from typing import Any |
10 | 13 |
|
| 14 | +import mesonpy |
11 | 15 | from scikit_build_core import build as skbuild |
12 | 16 |
|
13 | | -from cppython.build.prepare import prepare_build |
| 17 | +from cppython.build.prepare import BuildPreparationResult, prepare_build |
| 18 | +from cppython.plugins.cmake.schema import CMakeSyncData |
| 19 | +from cppython.plugins.meson.schema import MesonSyncData |
14 | 20 |
|
15 | 21 | logger = logging.getLogger('cppython.build') |
16 | 22 |
|
17 | 23 |
|
18 | | -def _inject_toolchain(config_settings: dict[str, Any] | None, toolchain_file: Path | None) -> dict[str, Any]: |
| 24 | +def _is_meson_project() -> bool: |
| 25 | + """Detect if the current project uses Meson by checking pyproject.toml. |
| 26 | +
|
| 27 | + Looks for ``[tool.cppython.generator]`` containing "meson" or the |
| 28 | + presence of a ``meson.build`` file in the source directory. |
| 29 | +
|
| 30 | + Returns: |
| 31 | + True if the project appears to be Meson-based |
| 32 | + """ |
| 33 | + source_dir = Path.cwd() |
| 34 | + |
| 35 | + # Check pyproject.toml for cppython generator configuration |
| 36 | + pyproject_path = source_dir / 'pyproject.toml' |
| 37 | + if pyproject_path.exists(): |
| 38 | + with open(pyproject_path, 'rb') as f: |
| 39 | + data = tomllib.load(f) |
| 40 | + generator = data.get('tool', {}).get('cppython', {}).get('generator', '') |
| 41 | + if isinstance(generator, str) and 'meson' in generator.lower(): |
| 42 | + return True |
| 43 | + |
| 44 | + # Fallback: check for meson.build file |
| 45 | + return (source_dir / 'meson.build').exists() |
| 46 | + |
| 47 | + |
| 48 | +def _get_backend(is_meson: bool) -> ModuleType: |
| 49 | + """Get the appropriate backend module. |
| 50 | +
|
| 51 | + Args: |
| 52 | + is_meson: Whether to use meson-python instead of scikit-build-core |
| 53 | +
|
| 54 | + Returns: |
| 55 | + The backend module (mesonpy or scikit_build_core.build) |
| 56 | + """ |
| 57 | + if is_meson: |
| 58 | + return mesonpy |
| 59 | + return skbuild |
| 60 | + |
| 61 | + |
| 62 | +def _inject_cmake_toolchain(config_settings: dict[str, Any] | None, toolchain_file: Path | None) -> dict[str, Any]: |
19 | 63 | """Inject the toolchain file into config settings for scikit-build-core. |
20 | 64 |
|
21 | 65 | Args: |
@@ -49,175 +93,154 @@ def _inject_toolchain(config_settings: dict[str, Any] | None, toolchain_file: Pa |
49 | 93 | return settings |
50 | 94 |
|
51 | 95 |
|
52 | | -def _prepare_and_get_settings( |
| 96 | +def _inject_meson_files( |
53 | 97 | config_settings: dict[str, Any] | None, |
| 98 | + native_file: Path | None, |
| 99 | + cross_file: Path | None, |
54 | 100 | ) -> dict[str, Any]: |
55 | | - """Run CPPython preparation and merge toolchain into config settings. |
| 101 | + """Inject native/cross files into config settings for meson-python. |
| 102 | +
|
| 103 | + Args: |
| 104 | + config_settings: The original config settings (may be None) |
| 105 | + native_file: Path to the Meson native file to inject |
| 106 | + cross_file: Path to the Meson cross file to inject |
| 107 | +
|
| 108 | + Returns: |
| 109 | + Updated config settings with Meson files injected |
| 110 | + """ |
| 111 | + settings = dict(config_settings) if config_settings else {} |
| 112 | + |
| 113 | + setup_args_key = 'setup-args' |
| 114 | + existing_args = settings.get(setup_args_key, '') |
| 115 | + |
| 116 | + args_to_add: list[str] = [] |
| 117 | + |
| 118 | + if native_file and native_file.exists(): |
| 119 | + native_arg = f'--native-file={native_file.absolute()}' |
| 120 | + if '--native-file' not in existing_args: |
| 121 | + args_to_add.append(native_arg) |
| 122 | + logger.info('CPPython: Injected --native-file=%s', native_file) |
| 123 | + else: |
| 124 | + logger.info('CPPython: User-specified native file takes precedence') |
| 125 | + |
| 126 | + if cross_file and cross_file.exists(): |
| 127 | + cross_arg = f'--cross-file={cross_file.absolute()}' |
| 128 | + if '--cross-file' not in existing_args: |
| 129 | + args_to_add.append(cross_arg) |
| 130 | + logger.info('CPPython: Injected --cross-file=%s', cross_file) |
| 131 | + else: |
| 132 | + logger.info('CPPython: User-specified cross file takes precedence') |
| 133 | + |
| 134 | + if args_to_add: |
| 135 | + if existing_args: |
| 136 | + settings[setup_args_key] = f'{existing_args};' + ';'.join(args_to_add) |
| 137 | + else: |
| 138 | + settings[setup_args_key] = ';'.join(args_to_add) |
| 139 | + |
| 140 | + return settings |
| 141 | + |
| 142 | + |
| 143 | +def _prepare_and_get_result( |
| 144 | + config_settings: dict[str, Any] | None, |
| 145 | +) -> tuple[BuildPreparationResult, dict[str, Any]]: |
| 146 | + """Run CPPython preparation and merge config into settings. |
56 | 147 |
|
57 | 148 | Args: |
58 | 149 | config_settings: The original config settings |
59 | 150 |
|
60 | 151 | Returns: |
61 | | - Config settings with CPPython toolchain injected |
| 152 | + Tuple of (preparation result, updated config settings) |
62 | 153 | """ |
63 | 154 | # Determine source directory (current working directory during build) |
64 | 155 | source_dir = Path.cwd() |
65 | 156 |
|
66 | 157 | # Run CPPython preparation |
67 | | - toolchain_file = prepare_build(source_dir) |
| 158 | + result = prepare_build(source_dir) |
68 | 159 |
|
69 | | - # Inject toolchain into config settings |
70 | | - return _inject_toolchain(config_settings, toolchain_file) |
| 160 | + # Inject settings based on sync data type |
| 161 | + settings = dict(config_settings) if config_settings else {} |
71 | 162 |
|
| 163 | + if result.sync_data is not None: |
| 164 | + if isinstance(result.sync_data, CMakeSyncData): |
| 165 | + settings = _inject_cmake_toolchain(settings, result.sync_data.toolchain_file) |
| 166 | + elif isinstance(result.sync_data, MesonSyncData): |
| 167 | + settings = _inject_meson_files(settings, result.sync_data.native_file, result.sync_data.cross_file) |
72 | 168 |
|
73 | | -# PEP 517 Hooks - delegating to scikit-build-core after preparation |
| 169 | + return result, settings |
74 | 170 |
|
75 | 171 |
|
76 | | -def get_requires_for_build_wheel( |
77 | | - config_settings: dict[str, Any] | None = None, |
78 | | -) -> list[str]: |
79 | | - """Get additional requirements for building a wheel. |
| 172 | +def _is_meson_build(result: BuildPreparationResult) -> bool: |
| 173 | + """Determine if the build should use meson-python based on sync data. |
80 | 174 |
|
81 | 175 | Args: |
82 | | - config_settings: Build configuration settings |
| 176 | + result: The build preparation result |
83 | 177 |
|
84 | 178 | Returns: |
85 | | - List of additional requirements |
| 179 | + True if meson-python should be used, False for scikit-build-core |
86 | 180 | """ |
87 | | - return skbuild.get_requires_for_build_wheel(config_settings) |
| 181 | + return isinstance(result.sync_data, MesonSyncData) |
88 | 182 |
|
89 | 183 |
|
90 | | -def get_requires_for_build_sdist( |
91 | | - config_settings: dict[str, Any] | None = None, |
92 | | -) -> list[str]: |
93 | | - """Get additional requirements for building an sdist. |
| 184 | +# PEP 517 Hooks - dispatching to the appropriate backend after preparation |
94 | 185 |
|
95 | | - Args: |
96 | | - config_settings: Build configuration settings |
97 | 186 |
|
98 | | - Returns: |
99 | | - List of additional requirements |
100 | | - """ |
101 | | - return skbuild.get_requires_for_build_sdist(config_settings) |
| 187 | +def get_requires_for_build_wheel(config_settings: dict[str, Any] | None = None) -> list[str]: |
| 188 | + """Get additional requirements for building a wheel.""" |
| 189 | + return _get_backend(_is_meson_project()).get_requires_for_build_wheel(config_settings) |
102 | 190 |
|
103 | 191 |
|
104 | | -def get_requires_for_build_editable( |
105 | | - config_settings: dict[str, Any] | None = None, |
106 | | -) -> list[str]: |
107 | | - """Get additional requirements for building an editable install. |
| 192 | +def get_requires_for_build_sdist(config_settings: dict[str, Any] | None = None) -> list[str]: |
| 193 | + """Get additional requirements for building an sdist.""" |
| 194 | + return _get_backend(_is_meson_project()).get_requires_for_build_sdist(config_settings) |
108 | 195 |
|
109 | | - Args: |
110 | | - config_settings: Build configuration settings |
111 | 196 |
|
112 | | - Returns: |
113 | | - List of additional requirements |
114 | | - """ |
115 | | - return skbuild.get_requires_for_build_editable(config_settings) |
| 197 | +def get_requires_for_build_editable(config_settings: dict[str, Any] | None = None) -> list[str]: |
| 198 | + """Get additional requirements for building an editable install.""" |
| 199 | + return _get_backend(_is_meson_project()).get_requires_for_build_editable(config_settings) |
116 | 200 |
|
117 | 201 |
|
118 | 202 | def build_wheel( |
119 | 203 | wheel_directory: str, |
120 | 204 | config_settings: dict[str, Any] | None = None, |
121 | 205 | metadata_directory: str | None = None, |
122 | 206 | ) -> str: |
123 | | - """Build a wheel from the source distribution. |
124 | | -
|
125 | | - This runs CPPython's provider workflow first to ensure C++ dependencies |
126 | | - are installed and the toolchain file is generated, then delegates to |
127 | | - scikit-build-core for the actual wheel build. |
128 | | -
|
129 | | - Args: |
130 | | - wheel_directory: Directory to place the built wheel |
131 | | - config_settings: Build configuration settings |
132 | | - metadata_directory: Directory containing wheel metadata |
133 | | -
|
134 | | - Returns: |
135 | | - The basename of the built wheel |
136 | | - """ |
| 207 | + """Build a wheel, running CPPython preparation first.""" |
137 | 208 | logger.info('CPPython: Starting wheel build') |
138 | | - |
139 | | - # Prepare CPPython and get updated settings |
140 | | - settings = _prepare_and_get_settings(config_settings) |
141 | | - |
142 | | - # Delegate to scikit-build-core |
143 | | - return skbuild.build_wheel(wheel_directory, settings, metadata_directory) |
| 209 | + result, settings = _prepare_and_get_result(config_settings) |
| 210 | + return _get_backend(_is_meson_build(result)).build_wheel(wheel_directory, settings, metadata_directory) |
144 | 211 |
|
145 | 212 |
|
146 | 213 | def build_sdist( |
147 | 214 | sdist_directory: str, |
148 | 215 | config_settings: dict[str, Any] | None = None, |
149 | 216 | ) -> str: |
150 | | - """Build a source distribution. |
151 | | -
|
152 | | - For sdist, we don't run the full CPPython workflow since the C++ dependencies |
153 | | - should be resolved at wheel build time, not sdist creation time. |
154 | | -
|
155 | | - Args: |
156 | | - sdist_directory: Directory to place the built sdist |
157 | | - config_settings: Build configuration settings |
158 | | -
|
159 | | - Returns: |
160 | | - The basename of the built sdist |
161 | | - """ |
| 217 | + """Build a source distribution (no CPPython workflow needed).""" |
162 | 218 | logger.info('CPPython: Starting sdist build') |
163 | | - |
164 | | - # Delegate directly to scikit-build-core (no preparation needed for sdist) |
165 | | - return skbuild.build_sdist(sdist_directory, config_settings) |
| 219 | + return _get_backend(_is_meson_project()).build_sdist(sdist_directory, config_settings) |
166 | 220 |
|
167 | 221 |
|
168 | 222 | def build_editable( |
169 | 223 | wheel_directory: str, |
170 | 224 | config_settings: dict[str, Any] | None = None, |
171 | 225 | metadata_directory: str | None = None, |
172 | 226 | ) -> str: |
173 | | - """Build an editable wheel. |
174 | | -
|
175 | | - This runs CPPython's provider workflow first, similar to build_wheel. |
176 | | -
|
177 | | - Args: |
178 | | - wheel_directory: Directory to place the built wheel |
179 | | - config_settings: Build configuration settings |
180 | | - metadata_directory: Directory containing wheel metadata |
181 | | -
|
182 | | - Returns: |
183 | | - The basename of the built wheel |
184 | | - """ |
| 227 | + """Build an editable wheel, running CPPython preparation first.""" |
185 | 228 | logger.info('CPPython: Starting editable build') |
186 | | - |
187 | | - # Prepare CPPython and get updated settings |
188 | | - settings = _prepare_and_get_settings(config_settings) |
189 | | - |
190 | | - # Delegate to scikit-build-core |
191 | | - return skbuild.build_editable(wheel_directory, settings, metadata_directory) |
| 229 | + result, settings = _prepare_and_get_result(config_settings) |
| 230 | + return _get_backend(_is_meson_build(result)).build_editable(wheel_directory, settings, metadata_directory) |
192 | 231 |
|
193 | 232 |
|
194 | 233 | def prepare_metadata_for_build_wheel( |
195 | 234 | metadata_directory: str, |
196 | 235 | config_settings: dict[str, Any] | None = None, |
197 | 236 | ) -> str: |
198 | | - """Prepare metadata for wheel build. |
199 | | -
|
200 | | - Args: |
201 | | - metadata_directory: Directory to place the metadata |
202 | | - config_settings: Build configuration settings |
203 | | -
|
204 | | - Returns: |
205 | | - The basename of the metadata directory |
206 | | - """ |
207 | | - return skbuild.prepare_metadata_for_build_wheel(metadata_directory, config_settings) |
| 237 | + """Prepare metadata for wheel build.""" |
| 238 | + return _get_backend(_is_meson_project()).prepare_metadata_for_build_wheel(metadata_directory, config_settings) |
208 | 239 |
|
209 | 240 |
|
210 | 241 | def prepare_metadata_for_build_editable( |
211 | 242 | metadata_directory: str, |
212 | 243 | config_settings: dict[str, Any] | None = None, |
213 | 244 | ) -> str: |
214 | | - """Prepare metadata for editable build. |
215 | | -
|
216 | | - Args: |
217 | | - metadata_directory: Directory to place the metadata |
218 | | - config_settings: Build configuration settings |
219 | | -
|
220 | | - Returns: |
221 | | - The basename of the metadata directory |
222 | | - """ |
223 | | - return skbuild.prepare_metadata_for_build_editable(metadata_directory, config_settings) |
| 245 | + """Prepare metadata for editable build.""" |
| 246 | + return _get_backend(_is_meson_project()).prepare_metadata_for_build_editable(metadata_directory, config_settings) |
0 commit comments