Skip to content

Commit 92213ce

Browse files
authored
Merge pull request #21 from openscm/cibuildwheel-matplotlib
Add cibuildwheel drawing inspiration from matplotlib and scipy's approach
2 parents 3a3a1ad + 1127181 commit 92213ce

File tree

7 files changed

+180
-43
lines changed

7 files changed

+180
-43
lines changed

.github/workflows/ci.yaml

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ jobs:
8080
os: [ "ubuntu-latest" ]
8181
# Test against all security and bugfix versions: https://devguide.python.org/versions/
8282
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
83+
include:
84+
# Test other OS's for a 'very stable' Python version too
85+
- os: "windows-latest"
86+
python-version: "3.11"
87+
- os: "macos-latest"
88+
python-version: "3.11"
89+
# Include other OS for latest Python
90+
# because these seem to be the flakiest from experience
91+
# so are worth the extra testing
92+
- os: "windows-latest"
93+
python-version: "3.13"
94+
- os: "macos-latest"
95+
python-version: "3.13"
8396
runs-on: "${{ matrix.os }}"
8497
defaults:
8598
run:
@@ -91,6 +104,15 @@ jobs:
91104
steps:
92105
- name: Check out repository
93106
uses: actions/checkout@v4
107+
- name: Install Fortran compiler - MacOS
108+
# When building on Mac, we need to have a Fortran compiler
109+
if: ${{ startsWith(matrix.os, 'macos') }}
110+
uses: fortran-lang/setup-fortran@v1
111+
id: setup-fortran-macos
112+
with:
113+
# TODO: figure out whether we need/want to use other compilers too
114+
compiler: "gcc"
115+
version: "13"
94116
- uses: ./.github/actions/setup
95117
with:
96118
python-version: ${{ matrix.python-version }}
@@ -100,10 +122,9 @@ jobs:
100122
# when people try to run without installing optional dependencies,
101123
# we should add a CI step that runs the tests without optional dependencies too.
102124
# We don't have that right now, because we're not sure this pain point exists.
103-
uv-dependency-install-flags: "--all-extras --group tests"
125+
uv-dependency-install-flags: "-v --reinstall-package example-fgen-basic -Ccompile-args='-v' --all-extras --group tests "
104126
- name: Run tests
105127
run: |
106-
107128
COV_DIR=`uv run --no-sync python -c 'from pathlib import Path; import example_fgen_basic; print(Path(example_fgen_basic.__file__).parent)'`
108129
uv run --no-sync pytest -r a -v tests src --doctest-modules --doctest-report ndiff --cov=${COV_DIR} --cov-report=term-missing --cov-report=xml
109130
uv run --no-sync coverage report
@@ -222,6 +243,11 @@ jobs:
222243
check-build:
223244
strategy:
224245
matrix:
246+
# The tests are also effectively a test of the build (at least the Fortran compilation).
247+
# This test is really just about checking what is packaged
248+
# and that we have all the required pieces.
249+
# Hence, for now, only test on ubuntu
250+
# (little value testing on other OS's because this test is not testing compilation directly).
225251
os: [ "ubuntu-latest" ]
226252
python-version: [ "3.11" ]
227253
runs-on: "${{ matrix.os }}"
@@ -248,11 +274,16 @@ jobs:
248274
cd dist
249275
ls
250276
python3 -m venv venv
251-
# TODO: alter for windows
252277
source venv/bin/activate
253278
pip install example_fgen_basic*
254279
python -c 'from example_fgen_basic.get_wavelength import get_wavelength_plain; print(get_wavelength_plain(23.4))'
255280
281+
# check-build-wheel
282+
# For now, decide not to do this as odds of breaking the build are low
283+
# (and we already implicitly test this when running the tests).
284+
# If we find we are breaking the wheel build inadvertently often,
285+
# we can always add this step in.
286+
256287
check-dependency-licences:
257288
strategy:
258289
matrix:

.github/workflows/deploy.yaml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,22 @@ jobs:
2424
# this permission is mandatory for trusted publishing with PyPI
2525
id-token: write
2626
steps:
27-
- name: Check out repository
27+
- name: Checkout repository
2828
uses: actions/checkout@v4
2929
with:
3030
fetch-depth: 0
31-
- uses: ./.github/actions/setup
31+
- name: Download artifacts
32+
env:
33+
GH_TOKEN: "${{ secrets.PERSONAL_ACCESS_TOKEN }}"
34+
run: |
35+
gh release download --pattern "*.whl" --dir dist
36+
gh release download --pattern "*.tar.gz" --dir dist
37+
# Check what we have
38+
tree dist
39+
- name: Setup uv
40+
id: setup-uv
41+
uses: astral-sh/setup-uv@v4
3242
with:
33-
python-version: ${{ matrix.python-version }}
34-
uv-dependency-install-flags: "--all-extras --group dev"
43+
version: "0.8.8"
3544
- name: Publish to PyPI
36-
run: |
37-
# TODO: move to using cibuildwheel so we have wheels for multiple platforms and python versions
38-
# starting docs: https://cibuildwheel.pypa.io/en/stable/
39-
uv build --sdist
40-
uv publish
41-
# Just in case, undo the changes to `pyproject.toml`
42-
git restore --staged . && git restore .
45+
run: uv publish

.github/workflows/release.yaml

Lines changed: 104 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,104 @@ defaults:
99
shell: bash
1010

1111
jobs:
12+
build-wheels:
13+
name: Build wheels on ${{ matrix.os }} for ${{ matrix.cibw_archs }}
14+
runs-on: ${{ matrix.os }}
15+
env:
16+
# 3.9, 3.10 seem to not be happy on windows arm64
17+
# because you can't install numpy from a wheel for the build environment
18+
# (and we're not going to try recompiling from source)
19+
CIBW_SKIP: "cp39-win_arm64 cp310-win_arm64"
20+
# TODO: consider turning this on
21+
# CIBW_ENABLE: cpython-freethreading
22+
CIBW_BEFORE_BUILD_WINDOWS: >-
23+
pip install delvewheel
24+
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >-
25+
delvewheel repair -w {dest_dir} {wheel}
26+
CIBW_BEFORE_TEST: >-
27+
pip install -r {project}/requirements-only-tests-min-locked.txt
28+
CIBW_TEST_COMMAND: >-
29+
pytest {project}/tests
30+
MACOSX_DEPLOYMENT_TARGET: "13.0"
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
include:
35+
- os: ubuntu-latest
36+
cibw_archs: "x86_64"
37+
- os: ubuntu-24.04-arm
38+
cibw_archs: "aarch64"
39+
- os: windows-2025
40+
cibw_archs: "AMD64"
41+
- os: windows-11-arm
42+
cibw_archs: "ARM64"
43+
- os: macos-13
44+
cibw_archs: "x86_64"
45+
- os: macos-14
46+
cibw_archs: "arm64"
47+
48+
steps:
49+
- uses: actions/checkout@v5
50+
- name: Install Fortran compiler - MacOS
51+
# When building on Mac, we need to have a Fortran compiler
52+
if: ${{ startsWith(matrix.os, 'macos') }}
53+
uses: fortran-lang/setup-fortran@v1
54+
id: setup-fortran-macos
55+
with:
56+
# TODO: figure out whether we need/want to use other compilers too
57+
compiler: "gcc"
58+
version: "13"
59+
- name: Specify LLVM - windows-arm
60+
if: ${{ matrix.os == 'windows-11-arm' }}
61+
shell: pwsh
62+
run: |
63+
Invoke-WebRequest https://github.com/llvm/llvm-project/releases/download/llvmorg-20.1.8/LLVM-20.1.8-woa64.exe -OutFile LLVM-woa64.exe
64+
Start-Process -FilePath ".\LLVM-woa64.exe" -ArgumentList "/S" -Wait
65+
66+
$env:PATH = "C:\Program Files\LLVM\bin;" + $env:PATH
67+
echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
68+
echo "CC=clang-cl" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
69+
echo "CXX=clang-cl" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
70+
echo "FC=flang-new" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
71+
echo "AR=llvm-lib.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
72+
- name: Build wheels
73+
uses: pypa/cibuildwheel@v3.1.4 # matplotlib had v3.1.3
74+
with:
75+
package-dir: .
76+
output-dir: wheelhouse
77+
config-file: "{package}/pyproject.toml"
78+
env:
79+
CIBW_ARCHS: ${{ matrix.cibw_archs }}
80+
- name: Upload wheels
81+
uses: actions/upload-artifact@v4
82+
with:
83+
name: cibw-wheels-${{ matrix.os }}-${{ matrix.cibw_archs }}
84+
path: ./wheelhouse/*.whl
85+
if-no-files-found: error
86+
87+
make-sdist:
88+
name: Make source distribution
89+
runs-on: "ubuntu-latest"
90+
steps:
91+
- name: Check out repository
92+
uses: actions/checkout@v4
93+
with:
94+
fetch-depth: 0
95+
- uses: ./.github/actions/setup
96+
with:
97+
python-version: ${{ matrix.python-version }}
98+
uv-dependency-install-flags: "--all-extras --group dev"
99+
- name: Create source distribution
100+
run: uv build --sdist
101+
- name: Upload the source distribution artefact
102+
uses: actions/upload-artifact@v4
103+
with:
104+
name: cibw-sdist
105+
path: dist/*.tar.gz
106+
12107
draft-release:
13108
name: Create draft release
109+
needs: [ build-wheels, make-sdist ]
14110
strategy:
15111
matrix:
16112
os: [ "ubuntu-latest" ]
@@ -25,15 +121,6 @@ jobs:
25121
with:
26122
python-version: ${{ matrix.python-version }}
27123
uv-dependency-install-flags: "--all-extras --group dev"
28-
- name: Add version to environment
29-
run: |
30-
PROJECT_VERSION=`sed -ne 's/^version = "\([0-9\.]*\)"/\1/p' pyproject.toml`
31-
echo "PROJECT_VERSION=$PROJECT_VERSION" >> $GITHUB_ENV
32-
- name: Build package for PyPI
33-
run: |
34-
uv build
35-
# Just in case, undo the changes to `pyproject.toml`
36-
git restore --staged . && git restore .
37124
- name: Generate Release Notes
38125
run: |
39126
echo "" >> ".github/release_template.md"
@@ -44,12 +131,18 @@ jobs:
44131
echo "## Changes" >> ".github/release_template.md"
45132
echo "" >> ".github/release_template.md"
46133
git log $(git describe --tags --abbrev=0 HEAD^)..HEAD --pretty='format:* %h %s' --no-merges >> ".github/release_template.md"
134+
- name: Download artifacts
135+
uses: actions/download-artifact@v5
136+
with:
137+
pattern: cibw-*
138+
path: dist
139+
merge-multiple: true
47140
- name: Create Release Draft
48141
uses: softprops/action-gh-release@v2
49142
with:
50143
body_path: ".github/release_template.md"
51144
token: "${{ secrets.PERSONAL_ACCESS_TOKEN }}"
52145
draft: true
53146
files: |
54-
dist/example_fgen_basic-${{ env.PROJECT_VERSION }}-py3-none-any.whl
55-
dist/example_fgen_basic-${{ env.PROJECT_VERSION }}.tar.gz
147+
dist/example_fgen_basic-*.whl
148+
dist/example_fgen_basic-*.tar.gz

changelog/21.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added compilation and publication to PyPI of wheels for major OS's and supported Python versions

docs/development.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ that will be performed to be manually specified.
8080
See the `uv version` docs (specifically the `--bump` flag) for the
8181
[list of available bump rules](https://docs.astral.sh/uv/reference/cli/#uv-version).
8282

83+
Releasing also includes building of wheels for major operating systems and supported Python versions.
84+
This is done thanks to [cibuildwheel](https://cibuildwheel.pypa.io/), an incredible service.
85+
The wheel building can be quite tricky and fiddly.
86+
If there are issues, we recommend starting with the [cibuildwheel](https://cibuildwheel.pypa.io/) docs
87+
or raising an issue to discuss with the other maintainers.
88+
8389
### Standard process
8490

8591
The steps required are the following:
@@ -94,7 +100,8 @@ The steps required are the following:
94100
(see here:
95101
[project releases](https://github.com/openscm/example-fgen-basic/releases)).
96102
Once you are happy with the release
97-
(removed placeholders, added key announcements etc.)
103+
(removed placeholders, added key announcements,
104+
checked that all the expected wheels and source distribution is there etc.)
98105
then hit 'Publish release'.
99106
This triggers a release to PyPI
100107
(which you can then add to the release if you want).

meson.build

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,17 @@ if pyprojectwheelbuild_enabled
6868
'src/example_fgen_basic/runtime_helpers.py',
6969
)
7070

71-
# The ancillary library,
72-
# i.e. all the stuff for wrapping that isn't directly exposed to Python.
73-
ancillary_lib = library(
74-
'@0@-ancillary'.format(meson.project_name()),
75-
sources: srcs_ancillary_lib,
76-
version: meson.project_version(),
77-
dependencies: [],
78-
# any other dependencies which aren't in source e.g. fgen-core
79-
# e.g. dependencies: [fgen_core_dep],
80-
install: false,
81-
)
71+
# The ancillary library,
72+
# i.e. all the stuff for wrapping that isn't directly exposed to Python.
73+
ancillary_lib = library(
74+
'@0@-ancillary'.format(meson.project_name()),
75+
sources: srcs_ancillary_lib,
76+
version: meson.project_version(),
77+
dependencies: [],
78+
# any other dependencies which aren't in source e.g. fgen-core
79+
# e.g. dependencies: [fgen_core_dep],
80+
install: false,
81+
)
8282

8383
ancillary_dep = declare_dependency(link_with: ancillary_lib)
8484

pyproject.toml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,20 @@ all-dev = [
125125
[build-system]
126126
build-backend = "mesonpy"
127127
requires = [
128-
"meson-python>=0.15.0",
129-
"numpy",
128+
"meson-python>=0.15.0",
129+
# "numpy",
130+
"numpy>=1.26.0; python_version < '3.13'",
131+
"numpy>=2.1.0; python_version >= '3.13'",
130132
]
131133

132134
# https://mesonbuild.com/meson-python/how-to-guides/meson-args.html
133135
[tool.meson-python.args]
134136
setup = [
135-
'--default-library=static',
136-
'-Dpyprojectwheelbuild=enabled',
137-
]
137+
'--default-library=static',
138+
'-Dpyprojectwheelbuild=enabled',
139+
]
138140
install = [
139-
'--skip-subprojects',
141+
'--skip-subprojects',
140142
]
141143

142144
[tool.coverage.run]

0 commit comments

Comments
 (0)