Skip to content

Commit fd61f50

Browse files
authored
feat: setup.py redesign and helpers (#2433)
* feat: setup.py redesign and helpers * refactor: simpler design with two outputs * refactor: helper file update and Windows support * fix: review points from @YannickJadoul * refactor: fixes to naming and more docs * feat: more customization points * feat: add entry point pybind11-config * refactor: Try Extension-focused method * refactor: rename alt/inplace to global * fix: allow usage with git modules, better docs * feat: global as an extra (@YannickJadoul's suggestion) * feat: single version location * fix: remove the requirement that setuptools must be imported first * fix: some review points from @wjacob * fix: use .in, add procedure to docs * refactor: avoid monkeypatch copy * docs: minor typos corrected * fix: minor points from @YannickJadoul * fix: typo on Windows C++ mode * fix: MSVC 15 update 3+ have c++14 flag See <https://docs.microsoft.com/en-us/cpp/build/reference/std-specify-language-standard-version?view=vs-2019> * docs: discuss making SDists by hand * ci: use pep517.build instead of manual setup.py * refactor: more comments from @YannickJadoul * docs: updates from @ktbarrett * fix: change to newly recommended tool instead of pep517.build This was intended as a proof of concept; build seems to be the correct replacement. See pypa/pyproject-hooks#83 * docs: updates from @wjakob * refactor: dual version locations * docs: typo spotted by @wjakob
1 parent 41aa926 commit fd61f50

31 files changed

+1391
-169
lines changed

.github/CONTRIBUTING.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,108 @@ cmake -S pybind11/ -B build
210210
cmake --build build
211211
```
212212

213+
### Explanation of the SDist/wheel building design
214+
215+
> These details below are _only_ for packaging the Python sources from git. The
216+
> SDists and wheels created do not have any extra requirements at all and are
217+
> completely normal.
218+
219+
The main objective of the packaging system is to create SDists (Python's source
220+
distribution packages) and wheels (Python's binary distribution packages) that
221+
include everything that is needed to work with pybind11, and which can be
222+
installed without any additional dependencies. This is more complex than it
223+
appears: in order to support CMake as a first class language even when using
224+
the PyPI package, they must include the _generated_ CMake files (so as not to
225+
require CMake when installing the `pybind11` package itself). They should also
226+
provide the option to install to the "standard" location
227+
(`<ENVROOT>/include/pybind11` and `<ENVROOT>/share/cmake/pybind11`) so they are
228+
easy to find with CMake, but this can cause problems if you are not an
229+
environment or using ``pyproject.toml`` requirements. This was solved by having
230+
two packages; the "nice" pybind11 package that stores the includes and CMake
231+
files inside the package, that you get access to via functions in the package,
232+
and a `pybind11-global` package that can be included via `pybind11[global]` if
233+
you want the more invasive but discoverable file locations.
234+
235+
If you want to install or package the GitHub source, it is best to have Pip 10
236+
or newer on Windows, macOS, or Linux (manylinux1 compatible, includes most
237+
distributions). You can then build the SDists, or run any procedure that makes
238+
SDists internally, like making wheels or installing.
239+
240+
241+
```bash
242+
# Editable development install example
243+
python3 -m pip install -e .
244+
```
245+
246+
Since Pip itself does not have an `sdist` command (it does have `wheel` and
247+
`install`), you may want to use the upcoming `build` package:
248+
249+
```bash
250+
python3 -m pip install build
251+
252+
# Normal package
253+
python3 -m build -s .
254+
255+
# Global extra
256+
PYBIND11_GLOBAL_SDIST=1 python3 -m build -s .
257+
```
258+
259+
If you want to use the classic "direct" usage of `python setup.py`, you will
260+
need CMake 3.15+ and either `make` or `ninja` preinstalled (possibly via `pip
261+
install cmake ninja`), since directly running Python on `setup.py` cannot pick
262+
up and install `pyproject.toml` requirements. As long as you have those two
263+
things, though, everything works the way you would expect:
264+
265+
```bash
266+
# Normal package
267+
python3 setup.py sdist
268+
269+
# Global extra
270+
PYBIND11_GLOBAL_SDIST=1 python3 setup.py sdist
271+
```
272+
273+
A detailed explanation of the build procedure design for developers wanting to
274+
work on or maintain the packaging system is as follows:
275+
276+
#### 1. Building from the source directory
277+
278+
When you invoke any `setup.py` command from the source directory, including
279+
`pip wheel .` and `pip install .`, you will activate a full source build. This
280+
is made of the following steps:
281+
282+
1. If the tool is PEP 518 compliant, like Pip 10+, it will create a temporary
283+
virtual environment and install the build requirements (mostly CMake) into
284+
it. (if you are not on Windows, macOS, or a manylinux compliant system, you
285+
can disable this with `--no-build-isolation` as long as you have CMake 3.15+
286+
installed)
287+
2. The environment variable `PYBIND11_GLOBAL_SDIST` is checked - if it is set
288+
and truthy, this will be make the accessory `pybind11-global` package,
289+
instead of the normal `pybind11` package. This package is used for
290+
installing the files directly to your environment root directory, using
291+
`pybind11[global]`.
292+
2. `setup.py` reads the version from `pybind11/_version.py` and verifies it
293+
matches `includes/pybind11/detail/common.h`.
294+
3. CMake is run with `-DCMAKE_INSTALL_PREIFX=pybind11`. Since the CMake install
295+
procedure uses only relative paths and is identical on all platforms, these
296+
files are valid as long as they stay in the correct relative position to the
297+
includes. `pybind11/share/cmake/pybind11` has the CMake files, and
298+
`pybind11/include` has the includes. The build directory is discarded.
299+
4. Simpler files are placed in the SDist: `tools/setup_*.py.in`,
300+
`tools/pyproject.toml` (`main` or `global`)
301+
5. The package is created by running the setup function in the
302+
`tools/setup_*.py`. `setup_main.py` fills in Python packages, and
303+
`setup_global.py` fills in only the data/header slots.
304+
6. A context manager cleans up the temporary CMake install directory (even if
305+
an error is thrown).
306+
307+
### 2. Building from SDist
308+
309+
Since the SDist has the rendered template files in `tools` along with the
310+
includes and CMake files in the correct locations, the builds are completely
311+
trivial and simple. No extra requirements are required. You can even use Pip 9
312+
if you really want to.
313+
314+
213315
[pre-commit]: https://pre-commit.com
214316
[pybind11.readthedocs.org]: http://pybind11.readthedocs.org/en/latest
215317
[issue tracker]: https://github.com/pybind/pybind11/issues

.github/workflows/ci.yml

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ on:
1010
- v*
1111

1212
jobs:
13+
# This is the "main" test suite, which tests a large number of different
14+
# versions of default compilers and Python versions in GitHub Actions.
1315
standard:
1416
strategy:
1517
fail-fast: false
@@ -23,6 +25,12 @@ jobs:
2325
- pypy2
2426
- pypy3
2527

28+
# Items in here will either be added to the build matrix (if not
29+
# present), or add new keys to an existing matrix element if all the
30+
# existing keys match.
31+
#
32+
# We support three optional keys: args (both build), args1 (first
33+
# build), and args2 (second build).
2634
include:
2735
- runs-on: ubuntu-latest
2836
python: 3.6
@@ -52,6 +60,7 @@ jobs:
5260
args: >
5361
-DPYBIND11_FINDPYTHON=ON
5462
63+
# These items will be removed from the build matrix, keys must match.
5564
exclude:
5665
# Currently 32bit only, and we build 64bit
5766
- runs-on: windows-latest
@@ -102,10 +111,11 @@ jobs:
102111
- name: Prepare env
103112
run: python -m pip install -r tests/requirements.txt --prefer-binary
104113

105-
- name: Setup annotations
114+
- name: Setup annotations on Linux
106115
if: runner.os == 'Linux'
107116
run: python -m pip install pytest-github-actions-annotate-failures
108117

118+
# First build - C++11 mode and inplace
109119
- name: Configure C++11 ${{ matrix.args }}
110120
run: >
111121
cmake -S . -B .
@@ -130,6 +140,7 @@ jobs:
130140
- name: Clean directory
131141
run: git clean -fdx
132142

143+
# Second build - C++17 mode and in a build directory
133144
- name: Configure ${{ matrix.args2 }}
134145
run: >
135146
cmake -S . -B build2
@@ -152,6 +163,28 @@ jobs:
152163
- name: Interface test
153164
run: cmake --build build2 --target test_cmake_build
154165

166+
# Eventually Microsoft might have an action for setting up
167+
# MSVC, but for now, this action works:
168+
- name: Prepare compiler environment for Windows 🐍 2.7
169+
if: matrix.python == 2.7 && runner.os == 'Windows'
170+
uses: ilammy/msvc-dev-cmd@v1
171+
with:
172+
arch: x64
173+
174+
# This makes two environment variables available in the following step(s)
175+
- name: Set Windows 🐍 2.7 environment variables
176+
if: matrix.python == 2.7 && runner.os == 'Windows'
177+
run: |
178+
echo "::set-env name=DISTUTILS_USE_SDK::1"
179+
echo "::set-env name=MSSdk::1"
180+
181+
# This makes sure the setup_helpers module can build packages using
182+
# setuptools
183+
- name: Setuptools helpers test
184+
run: pytest tests/extra_setuptools
185+
186+
187+
# Testing on clang using the excellent silkeh clang docker images
155188
clang:
156189
runs-on: ubuntu-latest
157190
strategy:
@@ -196,6 +229,7 @@ jobs:
196229
run: cmake --build build --target test_cmake_build
197230

198231

232+
# Testing NVCC; forces sources to behave like .cu files
199233
cuda:
200234
runs-on: ubuntu-latest
201235
name: "🐍 3.8 • CUDA 11 • Ubuntu 20.04"
@@ -218,6 +252,7 @@ jobs:
218252
run: cmake --build build --target pytest
219253

220254

255+
# Testing CentOS 8 + PGI compilers
221256
centos-nvhpc8:
222257
runs-on: ubuntu-latest
223258
name: "🐍 3 • CentOS8 / PGI 20.7 • x64"
@@ -256,6 +291,8 @@ jobs:
256291
- name: Interface test
257292
run: cmake --build build --target test_cmake_build
258293

294+
295+
# Testing on CentOS 7 + PGI compilers, which seems to require more workarounds
259296
centos-nvhpc7:
260297
runs-on: ubuntu-latest
261298
name: "🐍 3 • CentOS7 / PGI 20.7 • x64"
@@ -303,6 +340,7 @@ jobs:
303340
- name: Interface test
304341
run: cmake3 --build build --target test_cmake_build
305342

343+
# Testing on GCC using the GCC docker images (only recent images supported)
306344
gcc:
307345
runs-on: ubuntu-latest
308346
strategy:
@@ -351,6 +389,8 @@ jobs:
351389
run: cmake --build build --target test_cmake_build
352390

353391

392+
# Testing on CentOS (manylinux uses a centos base, and this is an easy way
393+
# to get GCC 4.8, which is the manylinux1 compiler).
354394
centos:
355395
runs-on: ubuntu-latest
356396
strategy:
@@ -398,6 +438,7 @@ jobs:
398438
run: cmake --build build --target test_cmake_build
399439

400440

441+
# This tests an "install" with the CMake tools
401442
install-classic:
402443
name: "🐍 3.5 • Debian • x86 • Install"
403444
runs-on: ubuntu-latest
@@ -440,31 +481,39 @@ jobs:
440481
working-directory: /build-tests
441482

442483

484+
# This verifies that the documentation is not horribly broken, and does a
485+
# basic sanity check on the SDist.
443486
doxygen:
444487
name: "Documentation build test"
445488
runs-on: ubuntu-latest
446-
container: alpine:3.12
447489

448490
steps:
449491
- uses: actions/checkout@v2
450492

451-
- name: Install system requirements
452-
run: apk add doxygen python3-dev
493+
- uses: actions/setup-python@v2
453494

454-
- name: Ensure pip
455-
run: python3 -m ensurepip
495+
- name: Install Doxygen
496+
run: sudo apt install -y doxygen
456497

457498
- name: Install docs & setup requirements
458-
run: python3 -m pip install -r docs/requirements.txt pytest setuptools
499+
run: python3 -m pip install -r docs/requirements.txt
459500

460501
- name: Build docs
461502
run: python3 -m sphinx -W -b html docs docs/.build
462503

463504
- name: Make SDist
464505
run: python3 setup.py sdist
465506

507+
- run: git status --ignored
508+
509+
- name: Check local include dir
510+
run: >
511+
ls pybind11;
512+
python3 -c "import pybind11, pathlib; assert (a := pybind11.get_include()) == (b := str(pathlib.Path('include').resolve())), f'{a} != {b}'"
513+
466514
- name: Compare Dists (headers only)
515+
working-directory: include
467516
run: |
468-
python3 -m pip install --user -U ./dist/*
469-
installed=$(python3 -c "import pybind11; print(pybind11.get_include(True) + '/pybind11')")
470-
diff -rq $installed ./include/pybind11
517+
python3 -m pip install --user -U ../dist/*
518+
installed=$(python3 -c "import pybind11; print(pybind11.get_include() + '/pybind11')")
519+
diff -rq $installed ./pybind11

.github/workflows/configure.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ on:
1010
- v*
1111

1212
jobs:
13+
# This tests various versions of CMake in various combinations, to make sure
14+
# the configure step passes.
1315
cmake:
1416
strategy:
1517
fail-fast: false
@@ -50,11 +52,14 @@ jobs:
5052
- name: Prepare env
5153
run: python -m pip install -r tests/requirements.txt
5254

55+
# An action for adding a specific version of CMake:
56+
# https://github.com/jwlawson/actions-setup-cmake
5357
- name: Setup CMake ${{ matrix.cmake }}
5458
uses: jwlawson/actions-setup-cmake@v1.3
5559
with:
5660
cmake-version: ${{ matrix.cmake }}
5761

62+
# These steps use a directory with a space in it intentionally
5863
- name: Make build directories
5964
run: mkdir "build dir"
6065

@@ -67,6 +72,7 @@ jobs:
6772
-DDOWNLOAD_CATCH=ON
6873
-DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)")
6974
75+
# Only build and test if this was manually triggered in the GitHub UI
7076
- name: Build
7177
working-directory: build dir
7278
if: github.event_name == 'workflow_dispatch'
@@ -76,3 +82,57 @@ jobs:
7682
working-directory: build dir
7783
if: github.event_name == 'workflow_dispatch'
7884
run: cmake --build . --config Release --target check
85+
86+
# This builds the sdists and wheels and makes sure the files are exactly as
87+
# expected. Using Windows and Python 2.7, since that is often the most
88+
# challenging matrix element.
89+
test-packaging:
90+
name: 🐍 2.7 • 📦 tests • windows-latest
91+
runs-on: windows-latest
92+
93+
steps:
94+
- uses: actions/checkout@v2
95+
96+
- name: Setup 🐍 2.7
97+
uses: actions/setup-python@v2
98+
with:
99+
python-version: 2.7
100+
101+
- name: Prepare env
102+
run: python -m pip install -r tests/requirements.txt --prefer-binary
103+
104+
- name: Python Packaging tests
105+
run: pytest tests/extra_python_package/
106+
107+
108+
# This runs the packaging tests and also builds and saves the packages as
109+
# artifacts.
110+
packaging:
111+
name: 🐍 3.8 • 📦 & 📦 tests • ubuntu-latest
112+
runs-on: ubuntu-latest
113+
114+
steps:
115+
- uses: actions/checkout@v2
116+
117+
- name: Setup 🐍 3.8
118+
uses: actions/setup-python@v2
119+
with:
120+
python-version: 3.8
121+
122+
- name: Prepare env
123+
run: python -m pip install -r tests/requirements.txt build twine --prefer-binary
124+
125+
- name: Python Packaging tests
126+
run: pytest tests/extra_python_package/
127+
128+
- name: Build SDist and wheels
129+
run: |
130+
python -m build -s -w .
131+
PYBIND11_GLOBAL_SDIST=1 python -m build -s -w .
132+
133+
- name: Check metadata
134+
run: twine check dist/*
135+
136+
- uses: actions/upload-artifact@v2
137+
with:
138+
path: dist/*

.github/workflows/format.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# This is a format job. Pre-commit has a first-party GitHub action, so we use
2+
# that: https://github.com/pre-commit/action
3+
14
name: Format
25

36
on:
@@ -17,6 +20,9 @@ jobs:
1720
- uses: actions/checkout@v2
1821
- uses: actions/setup-python@v2
1922
- uses: pre-commit/action@v2.0.0
23+
with:
24+
# Slow hooks are marked with manual - slow is okay here, run them too
25+
extra_args: --hook-stage manual
2026

2127
clang-tidy:
2228
name: Clang-Tidy

0 commit comments

Comments
 (0)