|
| 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 --> |
0 commit comments