Repository navigation
255 lines (237 loc) · 10.2 KB
/
Copy pathwheels.yml
File metadata and controls
255 lines (237 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
name: Build wheels
# Publishing is deliberately never automatic. A PyPI filename can only be
# uploaded once -- deleting or yanking a release does NOT free the version
# number -- so a botched 0.4.0 would burn 0.4.0 permanently. Every trigger here
# only produces downloadable artifacts; uploading is a separate manual run with
# an explicit target, and the recommended first target is TestPyPI.
on:
# Branch pushes and pull requests both give a build without touching master.
# Note workflow_dispatch below only appears in the Actions UI once this file
# is on the default branch -- that is a GitHub restriction, not a choice
# here -- so until then, pushing to one of these branches is how you run it.
push:
branches:
- master
- "feature/**"
pull_request:
workflow_dispatch:
inputs:
publish:
description: "Upload the built distributions where?"
type: choice
default: none
options: [none, testpypi, pypi]
permissions:
contents: read
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Check package version and publication target
env:
RELEASE_TARGET: ${{ github.event_name == 'workflow_dispatch' && inputs.publish || 'none' }}
run: python tools/check_release.py --target "$RELEASE_TARGET"
sdist:
needs: plan
name: sdist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- run: pipx run build --sdist
# The sdist shipped no C++ at all from the initial release commit until
# MANIFEST.in was added: CMakeExtension declares sources=[], so setuptools
# cannot infer the build tree. It is invisible in a wheel-only world and
# only bites whoever falls back to building from source, so assert it here
# rather than find out from a bug report.
- name: Verify the sdist can actually build
run: |
set -euo pipefail
tar tzf dist/*.tar.gz > files.txt
for pattern in \
'CMakeLists\.txt$' \
'lib/pybind11/include/pybind11/pybind11\.h$' \
'lib/ace/ace-evaluator/CMakeLists\.txt$' \
'src/pyace/maxvol/_maxvol\.pyx$'
do
grep -qE "$pattern" files.txt || { echo "sdist is missing: $pattern"; exit 1; }
done
cpp=$(grep -cE '\.cpp$' files.txt)
echo "sdist carries $cpp .cpp files"
[ "$cpp" -ge 60 ] || { echo "expected >=60 .cpp files, got $cpp"; exit 1; }
- uses: actions/upload-artifact@v7
with:
name: release-sdist
path: dist/*.tar.gz
wheels:
needs: plan
name: wheels (${{ matrix.platform }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- platform: linux-x86_64
os: ubuntu-latest
arch: x86_64
- platform: macos-arm64
os: macos-15
arch: arm64
- platform: macos-x86_64
os: macos-15-intel
arch: x86_64
steps:
- uses: actions/checkout@v7
- name: Build wheels
uses: pypa/cibuildwheel@v4.2.0
env:
CIBW_SKIP: "*-musllinux_*"
CIBW_ARCHS_LINUX: ${{ matrix.arch }}
CIBW_ARCHS_MACOS: ${{ matrix.arch }}
MACOSX_DEPLOYMENT_TARGET: "11.0"
# Routine commits build one interpreter; all five are built only when
# asked for, so a normal push costs one build rather than five. Ask
# either with a manual run, or -- since workflow_dispatch is invisible
# until this file reaches the default branch -- by putting
# [full-matrix] in the pushed commit's message.
#
# Upper bound is pybind11 3.1, which supports 3.9 through 3.14; the
# floor matches requires-python. head_commit is absent on
# pull_request events, where contains() is simply false.
CIBW_BUILD: >-
${{ (github.event_name == 'workflow_dispatch'
|| contains(github.event.head_commit.message, '[full-matrix]'))
&& 'cp39-* cp310-* cp311-* cp312-* cp313-*'
|| 'cp312-*' }}
# glibc 2.28 -- RHEL/Alma/Rocky 8+, Ubuntu 20.04+, Debian 10+. This is
# the floor the dependency set allows, not a compromise.
#
# An earlier revision tried manylinux2014 (glibc 2.17) on the theory
# that pip would resolve back to an older numpy/scipy that publishes
# 2_17 wheels. It does not: pip prefers the newest *version* and falls
# back to its sdist, so it downloaded numpy 2.5.2.tar.gz and died on
# "NumPy requires GCC >= 10.3". Pinning around that would not help
# either, because cibuildwheel installs the runtime dependencies in
# the same container to run the tests, and current pandas and
# scikit-learn publish x86_64 wheels only for 2_28 -- as would any
# actual glibc-2.17 user, who would get our wheel followed by a source
# build of pandas. There is no reach to be had below 2_28 while
# numpy, scipy, pandas and scikit-learn all stop there.
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
# PYACE_PORTABLE_MATH is deliberately NOT set: 2_28 whitelists
# libmvec and permits GLIBC_2.22, so -Ofast costs nothing here. The
# option stays in CMakeLists.txt for whenever a lower floor is worth
# chasing again.
# Each of these has actually been broken in this project at some point,
# so the wheel is not accepted until all four hold.
# 1. importlib.metadata looks up the DISTRIBUTION name (python-ace),
# not the import package (pyace) -- "unknown" means that broke.
# 2. all seven pybind11 modules load (pybind11 2.11 -> 3.1 upgrade).
# 3. the compiled maxvol kernel is present, not the slow scipy
# fallback -- it silently vanished from wheels before.
# 4. a console entry point resolves, since bin/ scripts became
# [project.scripts].
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: >-
python -c "import pyace; print('version:', pyace.__version__); assert pyace.__version__ != 'unknown', 'distribution-name lookup failed'"
&& python -c "import pyace.sharmonics, pyace.coupling, pyace.basis, pyace.evaluator, pyace.catomicenvironment, pyace.calculator, pyace.grace_fs; print('7 pybind11 modules ok')"
&& python -c "from pyace.maxvol import c_maxvol; import numpy as np; i, c = c_maxvol(np.random.rand(64, 8), 1.05, 100, -1, 0); print('compiled maxvol ok, selected', len(i), 'rows')"
&& pace_info --help > /dev/null && echo 'entry points ok'
&& cd "{project}" && python -m pytest tests/test_PyACECalculator.py -q
# MANIFEST.in names the C++ sources so the sdist can build, and they sit
# inside the package tree -- so a lapse in include-package-data silently
# installs all of them into every wheel. It happened once already.
- name: Verify the wheels ship no source
run: |
set -euo pipefail
for whl in wheelhouse/*.whl; do
bad=$(unzip -Z1 "$whl" | grep -E '\.(cpp|h|pyx|c)$' || true)
if [ -n "$bad" ]; then
echo "$(basename "$whl") ships source files:"; echo "$bad"; exit 1
fi
n=$(unzip -Z1 "$whl" | grep -cE '\.so$')
echo "$(basename "$whl"): $n compiled modules, no source"
[ "$n" -eq 8 ] || { echo "expected 8 .so (7 pybind11 + _maxvol), got $n"; exit 1; }
done
- uses: actions/upload-artifact@v7
with:
name: release-wheels-${{ matrix.platform }}
path: wheelhouse/*.whl
validate:
needs: [sdist, wheels]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- uses: actions/download-artifact@v8
with:
pattern: release-*
path: dist
merge-multiple: true
- run: python -m pip install twine
- name: Validate distribution metadata and wheel coverage
env:
FULL_MATRIX: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[full-matrix]') }}
run: |
python -m twine check --strict dist/*
if [ "$FULL_MATRIX" = "true" ]; then
python tools/check_release.py --artifacts dist --full-matrix
else
python tools/check_release.py --artifacts dist
fi
publish-testpypi:
name: Publish to TestPyPI
needs: validate
if: github.event_name == 'workflow_dispatch' && inputs.publish == 'testpypi'
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/project/python-ace/
concurrency:
group: publish-python-ace-testpypi
cancel-in-progress: false
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v8
with:
pattern: release-*
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
attestations: ${{ secrets.TEST_PYPI_API_TOKEN == '' }}
repository-url: https://test.pypi.org/legacy/
publish-pypi:
name: Publish to PyPI
needs: validate
if: github.event_name == 'workflow_dispatch' && inputs.publish == 'pypi'
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/python-ace/
concurrency:
group: publish-python-ace-pypi
cancel-in-progress: false
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v8
with:
pattern: release-*
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
user: ${{ secrets.PYPI_USERNAME || '__token__' }}
password: ${{ secrets.PYPI_PASSWORD }}
attestations: ${{ secrets.PYPI_PASSWORD == '' }}