Skip to content

Commit 996e3d4

Browse files
authored
docs: build process and faq (#529)
Adding some useful sections. Signed-off-by: Henry Schreiner <[email protected]>
1 parent 9e29a3b commit 996e3d4

File tree

7 files changed

+232
-0
lines changed

7 files changed

+232
-0
lines changed

docs/build.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Build procedure
2+
3+
Modern Python build procedure is as follows:
4+
5+
## SDist
6+
7+
The SDist is a tarfile with all the code required to build the project, along
8+
with a little bit of metadata. To build an SDist, you use the `build` tool with
9+
the `--sdist` flag. For example, `pipx run build --sdist`. This:
10+
11+
1. Reads `pyproject.toml` to get the `build-system` table.
12+
2. Set up a new isolated environment with the packages listed in
13+
`build-system.requires`..
14+
3. Run `.get_requires_for_build_sdist(...)` inside the module listed in
15+
`build-system.build-backend`, if it exists. If this returns a list, install
16+
all the packages requested. This allows a backend to dynamically declare
17+
dependencies.
18+
4. Run `.build_sdist(...)` inside the module listed in
19+
`build-system.build-backend`. The backend produces an SDist file and returns
20+
the filename.
21+
22+
Details of the arguments are skipped above, but they allow arbitrary settings
23+
(called config-settings) to be passed to all the hook functions and handle
24+
directories. If you turn off isolated environment building (`--no-isolation` in
25+
`build`), then steps 2 and 3 are skipped. Note that pip cannot build SDists.
26+
27+
Without build isolation, you can build an SDist manually with
28+
`python -c "from scikit_build_core.build import build_sdist; build_sdist('dist')"`.
29+
This will produce an SDist in the `dist` directory. For any other backend,
30+
substitute the backend above.
31+
32+
## Wheel
33+
34+
The wheel is a zip file (ending in `.whl`) with the built code of the project,
35+
along with required metadata. There is no code that executes on install; it is a
36+
simple unpack with a few rules about directories. Wheels do not contain
37+
`pyproject.toml` or other configuration files. To build an wheel, you use the
38+
`build` tool with the `--wheel` flag. For example, `pipx run build --wheel`.
39+
This:
40+
41+
1. Reads `pyproject.toml` to get the `build-system` table.
42+
2. Set up a new isolated environment with the packages listed in
43+
`build-system.requires`..
44+
3. Run `.get_requires_for_build_wheel(...)` inside the module listed in
45+
`build-system.build-backend`, if it exists. If this returns a list, install
46+
all the packages requested. This allows a backend to dynamically declare
47+
dependencies.
48+
4. Run `.build_wheel(...)` inside the module listed in
49+
`build-system.build-backend`. The backend produces an wheel file and returns
50+
the filename.
51+
52+
Details of the arguments are skipped above, but they allow arbitrary settings
53+
(called config-settings) to be passed to all the hook functions and handle
54+
directories. If you turn off isolated environment building
55+
(`--no-build-isolation` in `pip` or `--no-isolation` in `build`), then steps 2
56+
and 3 are skipped.
57+
58+
:::{note}
59+
60+
If you run build without arguments, it will build an SDist first, then will
61+
build a wheel from the SDist. This will error if you do not have a valid SDist.
62+
If you pass `--sdist --wheel`, it will build both directly from the source
63+
instead.
64+
65+
:::
66+
67+
There are a few other hooks as well; one to allow metadata to be produced
68+
without building a wheel, and editable versions of the wheel build. Editable
69+
"wheels" are temporary wheels that are only produced to immediately install and
70+
discard, and are expected to provide mechanisms to link back to the source code.
71+
72+
## Installing
73+
74+
Installing simply unpacks a wheel into the target filesystem. No code is run, no
75+
configuration files are present. If pip tries to install a repo or an SDist, it
76+
will first build a wheel[^1] as shown above, then install that. `installer` is a
77+
standalone tool that is designed entirely to install wheels.
78+
79+
If you want to run code on install, you either have to use an SDist, or depend
80+
on a package that is SDist only. However, this is quite rarely required.
81+
82+
There are several directories supported, at least. Besides unpacking to the
83+
site-packages directory, wheels can also have folders that get unpacked to the
84+
root of the environment and the Python header locations. But these are generally
85+
discouraged, with including files in the package's site-package directory and
86+
using `importlib.resources` to access them is preferred. If someone is not
87+
working in a virtual environment, having items installed to `/` or `/usr/local`
88+
for example might be surprising!
89+
90+
## Binary wheels and distributing
91+
92+
A wheel filename has several components:
93+
94+
```
95+
scikit_build_core-0.1.2-py3-none-any.whl
96+
|_______________| |___| |_| |__| |_|
97+
| | | | \
98+
name version | | platform
99+
python |
100+
abi
101+
```
102+
103+
The three new items here (compared to SDists) are the [compatibility tags][]:
104+
105+
- `python tag`: The first version of Python the wheel is compatible with. Often
106+
`py3` for pure Python wheels, or `py312` (etc) for compiled wheels.
107+
- `abi tag`: The interpreter ABI this was built for. `none` for pure Python
108+
wheels or compiled wheels that don't use the Python API, `abi3` for stable ABI
109+
/ limited API wheels, and `cp312` (etc) for normal compiled wheels.
110+
- `platform tag`: This is the platform the wheel is valid on, such as `any`,
111+
`linux_x86_64`, or `manylinux_2_17_x86_64`.
112+
113+
The wheels produced by default are not designed to be redistributable. Making
114+
them redistributable depends on platform:
115+
116+
- Linux: The `linux_*` tags cannot be uploaded to PyPI. You have to build the
117+
wheels in a restricted environment (like the manylinux images) and run the
118+
wheels through `auditwheel` to produce redistributable wheels. This will
119+
verify you are only using the correct GLibC and restricted set of system
120+
libraries, and will bundle external libraries into the wheel with mangled
121+
symbols to avoid conflicts. These will have a `manylinux_*` or `musllinux_*`
122+
tag, and can be uploaded to PyPI.
123+
- macOS: The wheels should be build with the official CPython releases, and
124+
target a reasonable `MACOSX_DEPLOYMENT_TARGET` value (10.9 or newer). You
125+
should run the wheels through `develwheel` to bundle external dependencies.
126+
You'll also want to (carefully) cross compile for Apple Silicon.
127+
- Windows: this is the easiest, usually, as the wheels don't have special rules
128+
on what Python or OS is being used. However, if you want to bundle
129+
dependencies, you'll need `delvewheel`, which is a bit younger than the other
130+
two packages, and has to do a few more intrusive workarounds, but otherwise
131+
works like those packages.
132+
133+
The easiest way to handle all the above for all Python versions, OSs,
134+
architectures, including testing, is to use [cibuildwheel][].
135+
136+
<!-- prettier-ignore-start -->
137+
138+
[^1]: This is the modern build mechanism. If no `pyproject.toml` is present,
139+
pip/build will trigger a legacy build/install that either pretends a basic
140+
`pyproject.toml` is present (build) or using legacy `setup.py ...` commands
141+
(pip). If **both** `pyproject.toml` is not provide and `wheel` is not
142+
present, `pip` will even fall back on using `setup.py install` instead of
143+
`setup.py bdist_wheel`! You can avoid this whole mess with
144+
scikit-build-core.
145+
146+
[cibuildwheel]: https://cibuildwheel.readthedocs.io
147+
[compatibility tags]: https://packaging.python.org/en/latest/specifications/binary-distribution-format
148+
149+
<!-- prettier-ignore-end -->
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
cmake_minimum_required(VERSION 3.15...3.26)
2+
project(${SKBUILD_PROJECT_NAME} LANGUAGES C)
3+
4+
add_library(example MODULE example.c)
5+
6+
install(TARGETS example DESTINATION example)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
float square(float x) { return x * x; }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[build-system]
2+
requires = ["scikit-build-core"]
3+
build-backend = "scikit_build_core.build"
4+
5+
[project]
6+
name = "example"
7+
version = "0.0.1"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# libexample = ctypes.CDLL(files("example") / "libexample.so")
2+
3+
# square = libexample.square
4+
# square.argtypes = [ctypes.c_float]
5+
# square.restype = ctypes.c_float
6+
7+
# __all__ = ["square"]

docs/faqs.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# FAQs
2+
3+
This section covers common needs.
4+
5+
## Starting a new project
6+
7+
The easiest way to get started is to use the [Scientific Python cookie][], which
8+
makes a new project following the [Scientific Python Development Guidelines][].
9+
Scikit-build-core is one of the backends you can select. The project will have a
10+
lot of tooling prepared for you as well, including pre-commit checks and a
11+
noxfile; be sure to read the guidelines to see what is there and how it works.
12+
13+
Another option is the [pybind11 example][].
14+
15+
In the future, a CLI interface with a new project generator is planned.
16+
17+
## Multithreaded builds
18+
19+
For most generators, you can control the parallelization via a CMake define:
20+
21+
```bash
22+
pip install -Ccmake.define.CMAKE_BUILD_PARALLEL_LEVEL=8 .
23+
```
24+
25+
or an environment variable:
26+
27+
```bash
28+
CMAKE_BUILD_PARALLEL_LEVEL=8 pip install .
29+
```
30+
31+
The default generator on Unix-like platforms is Ninja, which automatically tries
32+
to run in parallel with the number of cores on your machine.
33+
34+
## Dynamic setup.py options
35+
36+
While we will eventually have some dynamic options, most common needs can be
37+
moved into your `CMakeLists.txt`. For example, if you had a custom `setup.py`
38+
option (which setuptools has deprecated as well), you can make it a CMake option
39+
and then pass it with `-Ccmake.define.<OPTION_NAME>=<value>`. If you need to
40+
customize configuration options, try `[[tool.scikit-build.overrides]]`. If that
41+
is missing some value you need, please open an issue and let us know.
42+
43+
## Finding Python
44+
45+
One common mistake when using FindPython is to forget to only request the
46+
`Development.Module` component. If you request `Development`, you will also
47+
require the `Development.Embed` component, which will require the Python
48+
libraries to be found for linking. When building a module on Unix, you do not
49+
link to Python - the Python symbols are already loaded in the interpreter.
50+
What's more, the manylinux image (which is used to make redistributable Linux
51+
wheels) does not have the Python libraries, both to avoid this mistake, and to
52+
reduce size.
53+
54+
<!-- prettier-ignore-start -->
55+
56+
[scientific python cookie]: https://github.com/scientific-python/cookie
57+
[scientific python development guide]: https://learn.scientific-python.org/development
58+
[pybind11 example]: https://github.com/pybind/scikit_build_example
59+
60+
<!-- prettier-ignore-end -->

docs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ configuration
2323
cmakelists
2424
crosscompile
2525
migration_guide
26+
build
27+
faqs
2628
changelog
2729
```
2830

0 commit comments

Comments
 (0)