Skip to content

Commit 038d20e

Browse files
authored
scikit-build-core Support (#133)
1 parent 63abec1 commit 038d20e

File tree

27 files changed

+1331
-120
lines changed

27 files changed

+1331
-120
lines changed

.vscode/extensions.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"recommendations": [
3-
"asciidoctor.asciidoctor-vscode",
43
"charliermarsh.ruff",
54
"tamasfe.even-better-toml",
65
"meta.pyrefly"

.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@
1515
},
1616
"editor.defaultFormatter": "charliermarsh.ruff"
1717
},
18-
"cmake.ignoreCMakeListsMissing": true
18+
"cmake.ignoreCMakeListsMissing": true,
19+
"files.associations": {
20+
"*.md": "markdown",
21+
"xstring": "cpp"
22+
}
1923
}

cppython/build/__init__.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""CPPython build backend wrapping scikit-build-core.
2+
3+
This module provides PEP 517/518 build backend hooks that wrap scikit-build-core,
4+
automatically running CPPython's provider workflow before building
5+
to inject the generated toolchain file into the CMake configuration.
6+
7+
Usage in pyproject.toml:
8+
[build-system]
9+
requires = ["cppython[conan, cmake]"]
10+
build-backend = "cppython.build"
11+
"""
12+
13+
from cppython.build.backend import (
14+
build_editable,
15+
build_sdist,
16+
build_wheel,
17+
get_requires_for_build_editable,
18+
get_requires_for_build_sdist,
19+
get_requires_for_build_wheel,
20+
prepare_metadata_for_build_editable,
21+
prepare_metadata_for_build_wheel,
22+
)
23+
24+
__all__ = [
25+
'build_editable',
26+
'build_sdist',
27+
'build_wheel',
28+
'get_requires_for_build_editable',
29+
'get_requires_for_build_sdist',
30+
'get_requires_for_build_wheel',
31+
'prepare_metadata_for_build_editable',
32+
'prepare_metadata_for_build_wheel',
33+
]

cppython/build/backend.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
"""PEP 517 build backend implementation wrapping scikit-build-core.
2+
3+
This module provides the actual build hooks that delegate to scikit-build-core
4+
after running CPPython's preparation workflow.
5+
"""
6+
7+
import logging
8+
from pathlib import Path
9+
from typing import Any
10+
11+
from scikit_build_core import build as skbuild
12+
13+
from cppython.build.prepare import prepare_build
14+
15+
logger = logging.getLogger('cppython.build')
16+
17+
18+
def _inject_toolchain(config_settings: dict[str, Any] | None, toolchain_file: Path | None) -> dict[str, Any]:
19+
"""Inject the toolchain file into config settings for scikit-build-core.
20+
21+
Args:
22+
config_settings: The original config settings (may be None)
23+
toolchain_file: Path to the toolchain file to inject
24+
25+
Returns:
26+
Updated config settings with toolchain file injected
27+
"""
28+
settings = dict(config_settings) if config_settings else {}
29+
30+
if toolchain_file and toolchain_file.exists():
31+
# scikit-build-core accepts cmake.args for passing CMake arguments
32+
# Using cmake.args passes the toolchain via -DCMAKE_TOOLCHAIN_FILE=...
33+
args_key = 'cmake.args'
34+
toolchain_arg = f'-DCMAKE_TOOLCHAIN_FILE={toolchain_file.absolute()}'
35+
36+
# Append to existing args or create new
37+
if args_key in settings:
38+
existing = settings[args_key]
39+
# Check if toolchain is already specified
40+
if 'CMAKE_TOOLCHAIN_FILE' not in existing:
41+
settings[args_key] = f'{existing};{toolchain_arg}'
42+
logger.info('CPPython: Appended CMAKE_TOOLCHAIN_FILE to cmake.args')
43+
else:
44+
logger.info('CPPython: User-specified toolchain file takes precedence')
45+
else:
46+
settings[args_key] = toolchain_arg
47+
logger.info('CPPython: Injected CMAKE_TOOLCHAIN_FILE=%s', toolchain_file)
48+
49+
return settings
50+
51+
52+
def _prepare_and_get_settings(
53+
config_settings: dict[str, Any] | None,
54+
) -> dict[str, Any]:
55+
"""Run CPPython preparation and merge toolchain into config settings.
56+
57+
Args:
58+
config_settings: The original config settings
59+
60+
Returns:
61+
Config settings with CPPython toolchain injected
62+
"""
63+
# Determine source directory (current working directory during build)
64+
source_dir = Path.cwd()
65+
66+
# Run CPPython preparation
67+
toolchain_file = prepare_build(source_dir)
68+
69+
# Inject toolchain into config settings
70+
return _inject_toolchain(config_settings, toolchain_file)
71+
72+
73+
# PEP 517 Hooks - delegating to scikit-build-core after preparation
74+
75+
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.
80+
81+
Args:
82+
config_settings: Build configuration settings
83+
84+
Returns:
85+
List of additional requirements
86+
"""
87+
return skbuild.get_requires_for_build_wheel(config_settings)
88+
89+
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.
94+
95+
Args:
96+
config_settings: Build configuration settings
97+
98+
Returns:
99+
List of additional requirements
100+
"""
101+
return skbuild.get_requires_for_build_sdist(config_settings)
102+
103+
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.
108+
109+
Args:
110+
config_settings: Build configuration settings
111+
112+
Returns:
113+
List of additional requirements
114+
"""
115+
return skbuild.get_requires_for_build_editable(config_settings)
116+
117+
118+
def build_wheel(
119+
wheel_directory: str,
120+
config_settings: dict[str, Any] | None = None,
121+
metadata_directory: str | None = None,
122+
) -> 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+
"""
137+
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)
144+
145+
146+
def build_sdist(
147+
sdist_directory: str,
148+
config_settings: dict[str, Any] | None = None,
149+
) -> 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+
"""
162+
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)
166+
167+
168+
def build_editable(
169+
wheel_directory: str,
170+
config_settings: dict[str, Any] | None = None,
171+
metadata_directory: str | None = None,
172+
) -> 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+
"""
185+
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)
192+
193+
194+
def prepare_metadata_for_build_wheel(
195+
metadata_directory: str,
196+
config_settings: dict[str, Any] | None = None,
197+
) -> 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)
208+
209+
210+
def prepare_metadata_for_build_editable(
211+
metadata_directory: str,
212+
config_settings: dict[str, Any] | None = None,
213+
) -> 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)

0 commit comments

Comments
 (0)