Skip to content

Commit 203035d

Browse files
authored
docs: add build info (#878)
Adding a few things, mostly a docs update with more help debugging builds. Signed-off-by: Henry Schreiner <[email protected]>
1 parent a6f954a commit 203035d

24 files changed

+210
-123
lines changed

.pre-commit-config.yaml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ repos:
2525
exclude: "^tests"
2626

2727
- repo: https://github.com/astral-sh/ruff-pre-commit
28-
rev: v0.5.6
28+
rev: v0.6.2
2929
hooks:
3030
- id: ruff
3131
args: ["--fix", "--show-fixes"]
@@ -50,8 +50,8 @@ repos:
5050
- id: cmake-format
5151
exclude: ^src/scikit_build_core/resources/find_python
5252

53-
- repo: https://github.com/pre-commit/mirrors-prettier
54-
rev: "v4.0.0-alpha.8"
53+
- repo: https://github.com/rbubley/mirrors-prettier
54+
rev: "v3.3.3"
5555
hooks:
5656
- id: prettier
5757
types_or: [yaml, markdown, html, css, scss, javascript, json]
@@ -60,7 +60,7 @@ repos:
6060
stages: [manual]
6161

6262
- repo: https://github.com/pre-commit/mirrors-mypy
63-
rev: v1.11.1
63+
rev: v1.11.2
6464
hooks:
6565
- id: mypy
6666
exclude: |
@@ -130,15 +130,20 @@ repos:
130130
additional_dependencies: [cogapp]
131131

132132
- repo: https://github.com/henryiii/validate-pyproject-schema-store
133-
rev: 2024.07.29
133+
rev: 2024.08.26
134134
hooks:
135135
- id: validate-pyproject
136136

137137
- repo: https://github.com/python-jsonschema/check-jsonschema
138-
rev: 0.29.1
138+
rev: 0.29.2
139139
hooks:
140140
- id: check-dependabot
141141
- id: check-github-workflows
142142
- id: check-readthedocs
143143
- id: check-metaschema
144144
files: \.schema\.json
145+
146+
- repo: https://github.com/scientific-python/cookie
147+
rev: 2024.08.19
148+
hooks:
149+
- id: sp-repo-review

docs/build.md

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,53 @@
11
# Build procedure
22

3+
## Quickstart
4+
5+
For any backend, you can make a SDist and then build a wheel from it with one
6+
command (choose your favorite way to run apps):
7+
8+
````{tab} pipx
9+
10+
```bash
11+
pipx run build
12+
```
13+
14+
````
15+
16+
````{tab} uv
17+
18+
```bash
19+
uvx --from build pyproject-build --installer=uv
20+
```
21+
22+
````
23+
24+
````{tab} pip
25+
26+
```bash
27+
pip install build
28+
python -m build
29+
```
30+
31+
````
32+
33+
You can then check the file contents:
34+
35+
```bash
36+
tar -tf dist/*.tar.gz
37+
unzip -l dist/*.whl
38+
```
39+
40+
The SDist should contain a copy of the repo with all the files you'll need (CI
41+
files and such are not required). And the wheel should look like the installed
42+
project with a few helper files.
43+
44+
You can inspect any SDist or wheel on PyPI at <https://inspector.pypi.io>.
45+
46+
## In-depth
47+
348
Modern Python build procedure is as follows:
449

5-
## SDist
50+
### SDist
651

752
The SDist is a tarfile with all the code required to build the project, along
853
with a little bit of metadata. To build an SDist, you use the `build` tool with
@@ -29,7 +74,16 @@ Without build isolation, you can build an SDist manually with
2974
This will produce an SDist in the `dist` directory. For any other backend,
3075
substitute the backend above.
3176

32-
## Wheel
77+
#### File structure in the SDist
78+
79+
Since you can build a wheel from the source or from the SDist, the structure
80+
should be identical to the source, though some files (like CI files) may be
81+
omitted. Files from git submodules should be included. It is best if the SDist
82+
can be installed without internet connection, but that's not always the case.
83+
84+
There also is a `PKG-INFO` file with metadata in SDists.
85+
86+
### Wheel
3387

3488
The wheel is a zip file (ending in `.whl`) with the built code of the project,
3589
along with required metadata. There is no code that executes on install; it is a
@@ -69,7 +123,36 @@ without building a wheel, and editable versions of the wheel build. Editable
69123
"wheels" are temporary wheels that are only produced to immediately install and
70124
discard, and are expected to provide mechanisms to link back to the source code.
71125

72-
## Installing
126+
#### File structure in the wheel
127+
128+
The basic structure of the wheel is what will be extracted to site-packages.
129+
This means most of the files are usually in `<package-name>/...`, though if a
130+
top-level extension is present, then that could be something like
131+
`<package-name>.<platform-tag>.so`. There's also a
132+
`<package-name>-<package-version>.dist-info/` directory with various metadata
133+
files in it (`METADATA`, `WHEEL`, and `RECORD`), along with license files. There
134+
are a few other metadata files that could be here too, like `entry_points.txt`.
135+
136+
There are also several directories that installers can extract to different locations,
137+
namely:
138+
139+
* `<package-name>.data/scripts`: Goes to the `/bin` or `/Scripts` directory in
140+
the environment. Any file starting with `#!python` will get the correct path
141+
injected by the installer. Most build-backends (like setuptools and
142+
scikit-build-core) will convert normal Python shabang lines like
143+
`#!/usr/bin/env python` into `#!python` for you. Though if you are writing Python
144+
and placing them here, it's usually better to use entry points and let the installer
145+
generate the entire file.
146+
* `<package-name>.data/headers`: Goes to the include directory for the current
147+
version of Python in the environment.
148+
* `<package-name>.data/data`: Goes to the root of the environment.
149+
150+
Note that if a user is not in a virtual environment, these folders install
151+
directly to the Python install's location, which could be `/` or `/usr`! In
152+
general, it's best to put data inside the package's folder in site-packages and
153+
then use `importlib.resources` to access it.
154+
155+
### Installing
73156

74157
Installing simply unpacks a wheel into the target filesystem. No code is run, no
75158
configuration files are present. If pip tries to install a repo or an SDist, it

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,6 @@ ignore = ["W002"] # Triggers on __init__.py's
217217

218218

219219
[tool.ruff]
220-
src = ["src"]
221220
exclude = ["src/scikit_build_core/_vendor"] # Required due to "build" module
222221

223222
[tool.ruff.lint]

tests/conftest.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,13 @@ def install(self, *args: str, isolated: bool = True) -> None:
157157
self.module("pip", "install", *isolated_flags, *args)
158158

159159

160-
@pytest.fixture()
160+
@pytest.fixture
161161
def isolated(tmp_path: Path, pep518_wheelhouse: Path) -> VEnv:
162162
path = tmp_path / "venv"
163163
return VEnv(path, wheelhouse=pep518_wheelhouse)
164164

165165

166-
@pytest.fixture()
166+
@pytest.fixture
167167
def virtualenv(tmp_path: Path) -> VEnv:
168168
path = tmp_path / "venv"
169169
return VEnv(path)
@@ -205,7 +205,7 @@ def process_package(
205205
shutil.rmtree("dist")
206206

207207

208-
@pytest.fixture()
208+
@pytest.fixture
209209
def package_simple_pyproject_ext(
210210
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
211211
) -> PackageInfo:
@@ -220,7 +220,7 @@ def package_simple_pyproject_ext(
220220
return package
221221

222222

223-
@pytest.fixture()
223+
@pytest.fixture
224224
def package_simple_pyproject_script_with_flags(
225225
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
226226
) -> PackageInfo:
@@ -231,7 +231,7 @@ def package_simple_pyproject_script_with_flags(
231231
return package
232232

233233

234-
@pytest.fixture()
234+
@pytest.fixture
235235
def package_simple_pyproject_source_dir(
236236
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
237237
) -> PackageInfo:
@@ -242,7 +242,7 @@ def package_simple_pyproject_source_dir(
242242
return package
243243

244244

245-
@pytest.fixture()
245+
@pytest.fixture
246246
def package_simple_setuptools_ext(
247247
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
248248
) -> PackageInfo:
@@ -251,7 +251,7 @@ def package_simple_setuptools_ext(
251251
return package
252252

253253

254-
@pytest.fixture()
254+
@pytest.fixture
255255
def package_mixed_setuptools(
256256
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
257257
) -> PackageInfo:
@@ -260,7 +260,7 @@ def package_mixed_setuptools(
260260
return package
261261

262262

263-
@pytest.fixture()
263+
@pytest.fixture
264264
def package_filepath_pure(
265265
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
266266
) -> PackageInfo:
@@ -269,7 +269,7 @@ def package_filepath_pure(
269269
return package
270270

271271

272-
@pytest.fixture()
272+
@pytest.fixture
273273
def package_dynamic_metadata(
274274
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
275275
) -> PackageInfo:
@@ -278,14 +278,14 @@ def package_dynamic_metadata(
278278
return package
279279

280280

281-
@pytest.fixture()
281+
@pytest.fixture
282282
def package_hatchling(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo:
283283
package = PackageInfo("hatchling")
284284
process_package(package, tmp_path, monkeypatch)
285285
return package
286286

287287

288-
@pytest.fixture()
288+
@pytest.fixture
289289
def package_simplest_c(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo:
290290
package = PackageInfo(
291291
"simplest_c",
@@ -294,7 +294,7 @@ def package_simplest_c(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Packa
294294
return package
295295

296296

297-
@pytest.fixture()
297+
@pytest.fixture
298298
def navigate_editable(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo:
299299
package = PackageInfo(
300300
"navigate_editable",
@@ -303,7 +303,7 @@ def navigate_editable(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Packag
303303
return package
304304

305305

306-
@pytest.fixture()
306+
@pytest.fixture
307307
def broken_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageInfo:
308308
package = PackageInfo(
309309
"broken_fallback",
@@ -312,7 +312,7 @@ def broken_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> PackageI
312312
return package
313313

314314

315-
@pytest.fixture()
315+
@pytest.fixture
316316
def package_sdist_config(
317317
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
318318
) -> PackageInfo:
@@ -323,7 +323,7 @@ def package_sdist_config(
323323
return package
324324

325325

326-
@pytest.fixture()
326+
@pytest.fixture
327327
def package_simple_purelib_package(
328328
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
329329
) -> PackageInfo:
@@ -342,7 +342,7 @@ def which_mock(name: str) -> str | None:
342342
return None
343343

344344

345-
@pytest.fixture()
345+
@pytest.fixture
346346
def protect_get_requires(fp, monkeypatch):
347347
"""
348348
Protect get_requires from actually calling anything variable during tests.

tests/test_broken_fallback.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
)
1010

1111

12-
@pytest.mark.compile()
13-
@pytest.mark.configure()
12+
@pytest.mark.compile
13+
@pytest.mark.configure
1414
@pytest.mark.usefixtures("broken_fallback")
1515
@pytest.mark.parametrize("broken_define", ["BROKEN_CMAKE", "BROKEN_CODE"])
1616
def test_broken_code(broken_define: str, capfd: pytest.CaptureFixture[str]):

tests/test_cmake_config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def configure_args(
7171
yield f"-C{cmake_init}"
7272

7373

74-
@pytest.mark.configure()
74+
@pytest.mark.configure
7575
def test_init_cache(
7676
generator: str,
7777
tmp_path: Path,
@@ -116,7 +116,7 @@ def test_init_cache(
116116
)
117117

118118

119-
@pytest.mark.configure()
119+
@pytest.mark.configure
120120
def test_too_old(fp, monkeypatch):
121121
monkeypatch.setattr(shutil, "which", lambda _: None)
122122
fp.register(
@@ -133,7 +133,7 @@ def test_too_old(fp, monkeypatch):
133133
assert "Could not find CMake with version >=3.15" in excinfo.value.args[0]
134134

135135

136-
@pytest.mark.configure()
136+
@pytest.mark.configure
137137
def test_cmake_args(
138138
generator: str,
139139
tmp_path: Path,
@@ -167,7 +167,7 @@ def test_cmake_args(
167167
assert len(fp.calls) == 2
168168

169169

170-
@pytest.mark.configure()
170+
@pytest.mark.configure
171171
def test_cmake_paths(
172172
generator: str,
173173
tmp_path: Path,

tests/test_dynamic_metadata.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def special_loader(name: str, *args: Any, **kwargs: Any) -> Any:
9191
return original_loader(name, *args, **kwargs)
9292

9393

94-
@pytest.fixture()
94+
@pytest.fixture
9595
def mock_entry_points(monkeypatch):
9696
monkeypatch.setattr(importlib, "import_module", special_loader)
9797

@@ -229,8 +229,8 @@ def test_dual_metadata():
229229
get_standard_metadata(pyproject, settings)
230230

231231

232-
@pytest.mark.compile()
233-
@pytest.mark.configure()
232+
@pytest.mark.compile
233+
@pytest.mark.configure
234234
@pytest.mark.usefixtures("mock_entry_points", "package_dynamic_metadata")
235235
def test_pep517_wheel(virtualenv):
236236
dist = Path("dist")

0 commit comments

Comments
 (0)