Skip to content

Commit 53fa643

Browse files
committed
feat: v0.5.0 -- multi-platform PyPI release, encrypted Rust core, OIDC publish
- CI/CD: build wheels for Linux x86_64 (manylinux), Windows x64, macOS Intel and Apple Silicon for CPython 3.9-3.13 - Rust core injected at build time via RUST_SRC_B64_1/2 secrets (proprietary) - Publish to PyPI via OIDC Trusted Publisher (no stored token) - Lint: ruff + mypy on every push, Docker smoke test - src/lib.rs: placeholder stub (real source is private) - pyproject.toml: maturin 1.14+, module-name aligned, exclude src/** - Cargo.toml: publish = true - .gitattributes: LF normalization, binary markers - OIDC_SETUP.md: step-by-step browser setup guide
0 parents  commit 53fa643

264 files changed

Lines changed: 73369 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Normalize line endings -- enforce LF for all text files regardless of OS
2+
* text=auto eol=lf
3+
4+
# Binaries
5+
*.whl binary
6+
*.pyd binary
7+
*.so binary
8+
*.dll binary
9+
*.dylib binary
10+
*.lib binary
11+
*.exp binary
12+
*.a binary
13+
*.gz binary
14+
*.zip binary
15+
*.png binary
16+
*.jpg binary
17+
*.jpeg binary
18+
*.gif binary
19+
*.ico binary
20+
*.pdf binary

.github/workflows/CI.yml

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
# CI -- Build wheels on Linux/Windows/macOS, publish to PyPI on tag.
2+
#
3+
# Rust source is injected at build time from two GitHub Actions secrets
4+
# (RUST_SRC_B64_1 + RUST_SRC_B64_2) to keep the proprietary core private.
5+
# On forks / external PRs the secrets will be empty and wheel builds are skipped.
6+
#
7+
# Trigger: push to main/master, any tag v*, pull_request, manual dispatch.
8+
9+
name: CI
10+
11+
on:
12+
push:
13+
branches: [main, master]
14+
tags: ["v*"]
15+
pull_request:
16+
workflow_dispatch:
17+
18+
permissions:
19+
contents: read
20+
21+
env:
22+
# Build without OpenCL (SDK not on runners). CUDA is runtime-loaded via
23+
# libloading so no CUDA toolkit is needed at build time.
24+
MATURIN_ARGS: --release --no-default-features --features cuda --out dist
25+
26+
jobs:
27+
# ------------------------------------------------------------------
28+
# Linux x86_64 manylinux wheels
29+
# ------------------------------------------------------------------
30+
linux-x86_64:
31+
name: linux-x86_64-py${{ matrix.python-version }}
32+
runs-on: ubuntu-22.04
33+
strategy:
34+
fail-fast: false
35+
matrix:
36+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
37+
steps:
38+
- uses: actions/checkout@v4
39+
40+
- name: Inject Rust source
41+
env:
42+
PART1: ${{ secrets.RUST_SRC_B64_1 }}
43+
PART2: ${{ secrets.RUST_SRC_B64_2 }}
44+
run: |
45+
if [ -z "$PART1" ]; then
46+
echo "::warning::RUST_SRC_B64_1 secret not set -- skipping wheel build"
47+
echo "SKIP_BUILD=1" >> "$GITHUB_ENV"
48+
else
49+
echo "${PART1}${PART2}" | base64 -d | tar -xzf - -C .
50+
echo "SKIP_BUILD=0" >> "$GITHUB_ENV"
51+
fi
52+
53+
- uses: actions/setup-python@v5
54+
if: env.SKIP_BUILD == '0'
55+
with:
56+
python-version: ${{ matrix.python-version }}
57+
58+
- name: Build manylinux wheel
59+
if: env.SKIP_BUILD == '0'
60+
uses: PyO3/maturin-action@v1
61+
with:
62+
target: x86_64
63+
manylinux: auto
64+
args: ${{ env.MATURIN_ARGS }} -i python${{ matrix.python-version }}
65+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
66+
67+
- uses: actions/upload-artifact@v4
68+
if: env.SKIP_BUILD == '0'
69+
with:
70+
name: wheels-linux-x86_64-py${{ matrix.python-version }}
71+
path: dist
72+
73+
# ------------------------------------------------------------------
74+
# Windows x64 wheels
75+
# ------------------------------------------------------------------
76+
windows-x64:
77+
name: windows-x64-py${{ matrix.python-version }}
78+
runs-on: windows-latest
79+
strategy:
80+
fail-fast: false
81+
matrix:
82+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
83+
steps:
84+
- uses: actions/checkout@v4
85+
86+
- name: Inject Rust source
87+
env:
88+
PART1: ${{ secrets.RUST_SRC_B64_1 }}
89+
PART2: ${{ secrets.RUST_SRC_B64_2 }}
90+
shell: bash
91+
run: |
92+
if [ -z "$PART1" ]; then
93+
echo "::warning::RUST_SRC_B64_1 secret not set -- skipping wheel build"
94+
echo "SKIP_BUILD=1" >> "$GITHUB_ENV"
95+
else
96+
echo "${PART1}${PART2}" | base64 -d | tar -xzf - -C .
97+
echo "SKIP_BUILD=0" >> "$GITHUB_ENV"
98+
fi
99+
100+
- uses: actions/setup-python@v5
101+
if: env.SKIP_BUILD == '0'
102+
with:
103+
python-version: ${{ matrix.python-version }}
104+
architecture: x64
105+
106+
- name: Build Windows wheel
107+
if: env.SKIP_BUILD == '0'
108+
uses: PyO3/maturin-action@v1
109+
with:
110+
target: x64
111+
args: ${{ env.MATURIN_ARGS }} -i python
112+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
113+
114+
- uses: actions/upload-artifact@v4
115+
if: env.SKIP_BUILD == '0'
116+
with:
117+
name: wheels-windows-x64-py${{ matrix.python-version }}
118+
path: dist
119+
120+
# ------------------------------------------------------------------
121+
# macOS Intel (x86_64) wheels
122+
# ------------------------------------------------------------------
123+
macos-intel:
124+
name: macos-x86_64-py${{ matrix.python-version }}
125+
runs-on: macos-13
126+
strategy:
127+
fail-fast: false
128+
matrix:
129+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
130+
steps:
131+
- uses: actions/checkout@v4
132+
133+
- name: Inject Rust source
134+
env:
135+
PART1: ${{ secrets.RUST_SRC_B64_1 }}
136+
PART2: ${{ secrets.RUST_SRC_B64_2 }}
137+
run: |
138+
if [ -z "$PART1" ]; then
139+
echo "::warning::RUST_SRC_B64_1 secret not set -- skipping wheel build"
140+
echo "SKIP_BUILD=1" >> "$GITHUB_ENV"
141+
else
142+
echo "${PART1}${PART2}" | base64 -d | tar -xzf - -C .
143+
echo "SKIP_BUILD=0" >> "$GITHUB_ENV"
144+
fi
145+
146+
- uses: actions/setup-python@v5
147+
if: env.SKIP_BUILD == '0'
148+
with:
149+
python-version: ${{ matrix.python-version }}
150+
151+
- name: Build macOS Intel wheel
152+
if: env.SKIP_BUILD == '0'
153+
uses: PyO3/maturin-action@v1
154+
with:
155+
target: x86_64
156+
args: ${{ env.MATURIN_ARGS }} -i python
157+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
158+
159+
- uses: actions/upload-artifact@v4
160+
if: env.SKIP_BUILD == '0'
161+
with:
162+
name: wheels-macos-x86_64-py${{ matrix.python-version }}
163+
path: dist
164+
165+
# ------------------------------------------------------------------
166+
# macOS Apple Silicon (aarch64) wheels
167+
# ------------------------------------------------------------------
168+
macos-arm:
169+
name: macos-aarch64-py${{ matrix.python-version }}
170+
runs-on: macos-14
171+
strategy:
172+
fail-fast: false
173+
matrix:
174+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
175+
steps:
176+
- uses: actions/checkout@v4
177+
178+
- name: Inject Rust source
179+
env:
180+
PART1: ${{ secrets.RUST_SRC_B64_1 }}
181+
PART2: ${{ secrets.RUST_SRC_B64_2 }}
182+
run: |
183+
if [ -z "$PART1" ]; then
184+
echo "::warning::RUST_SRC_B64_1 secret not set -- skipping wheel build"
185+
echo "SKIP_BUILD=1" >> "$GITHUB_ENV"
186+
else
187+
echo "${PART1}${PART2}" | base64 -d | tar -xzf - -C .
188+
echo "SKIP_BUILD=0" >> "$GITHUB_ENV"
189+
fi
190+
191+
- uses: actions/setup-python@v5
192+
if: env.SKIP_BUILD == '0'
193+
with:
194+
python-version: ${{ matrix.python-version }}
195+
196+
- name: Build macOS ARM wheel
197+
if: env.SKIP_BUILD == '0'
198+
uses: PyO3/maturin-action@v1
199+
with:
200+
target: aarch64
201+
args: ${{ env.MATURIN_ARGS }} -i python
202+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
203+
204+
- uses: actions/upload-artifact@v4
205+
if: env.SKIP_BUILD == '0'
206+
with:
207+
name: wheels-macos-aarch64-py${{ matrix.python-version }}
208+
path: dist
209+
210+
# ------------------------------------------------------------------
211+
# Source distribution (sdist) -- pure Python, no Rust needed
212+
# ------------------------------------------------------------------
213+
sdist:
214+
name: sdist
215+
runs-on: ubuntu-latest
216+
steps:
217+
- uses: actions/checkout@v4
218+
219+
- name: Inject Rust source (needed by maturin sdist to embed Cargo.toml)
220+
env:
221+
PART1: ${{ secrets.RUST_SRC_B64_1 }}
222+
PART2: ${{ secrets.RUST_SRC_B64_2 }}
223+
run: |
224+
if [ -z "$PART1" ]; then
225+
echo "SKIP_BUILD=1" >> "$GITHUB_ENV"
226+
else
227+
echo "${PART1}${PART2}" | base64 -d | tar -xzf - -C .
228+
echo "SKIP_BUILD=0" >> "$GITHUB_ENV"
229+
fi
230+
231+
- name: Build sdist
232+
if: env.SKIP_BUILD == '0'
233+
uses: PyO3/maturin-action@v1
234+
with:
235+
command: sdist
236+
args: --out dist
237+
238+
- uses: actions/upload-artifact@v4
239+
if: env.SKIP_BUILD == '0'
240+
with:
241+
name: wheels-sdist
242+
path: dist
243+
244+
# ------------------------------------------------------------------
245+
# Publish to PyPI via OIDC Trusted Publisher (no token required)
246+
# Runs only on version tags pushed to the main repo (not forks)
247+
# ------------------------------------------------------------------
248+
release:
249+
name: publish-to-pypi
250+
runs-on: ubuntu-latest
251+
if: >
252+
startsWith(github.ref, 'refs/tags/v') &&
253+
github.repository == 'qectorlab/qector-decoder' &&
254+
github.event_name != 'pull_request'
255+
needs: [linux-x86_64, windows-x64, macos-intel, macos-arm, sdist]
256+
environment: pypi
257+
permissions:
258+
id-token: write
259+
contents: write
260+
attestations: write
261+
steps:
262+
- uses: actions/download-artifact@v4
263+
with:
264+
path: dist
265+
merge-multiple: true
266+
267+
- name: Generate artifact attestations
268+
uses: actions/attest-build-provenance@v2
269+
with:
270+
subject-path: "dist/*"
271+
272+
- name: Publish to PyPI
273+
uses: pypa/gh-action-pypi-publish@release/v1
274+
with:
275+
packages-dir: dist/
276+
skip-existing: true

.github/workflows/tests.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Lint and static analysis -- runs on every push/PR, no Rust build needed.
2+
# Uses the installed wheel from PyPI (or a local sdist) to run the Python layer.
3+
4+
name: tests
5+
6+
on:
7+
push:
8+
branches: [main, master]
9+
pull_request:
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
# ------------------------------------------------------------------
17+
# Ruff lint + MyPy type-check
18+
# ------------------------------------------------------------------
19+
lint:
20+
name: ruff-and-mypy
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.12"
27+
- name: Install linting tools
28+
run: pip install ruff mypy numpy
29+
- name: Ruff lint
30+
run: ruff check python/
31+
- name: Ruff format check
32+
run: ruff format --check python/
33+
- name: MyPy
34+
run: mypy python/qector_decoder_v3/ --ignore-missing-imports
35+
36+
# ------------------------------------------------------------------
37+
# Docker image build smoke test
38+
# ------------------------------------------------------------------
39+
docker:
40+
name: docker-build
41+
runs-on: ubuntu-latest
42+
steps:
43+
- uses: actions/checkout@v4
44+
- name: Build Docker image
45+
run: docker build -t qector-decoder-test:ci .
46+
47+
# ------------------------------------------------------------------
48+
# Python layer import smoke test (installs from PyPI)
49+
# ------------------------------------------------------------------
50+
smoke-import:
51+
name: smoke-import-py${{ matrix.python-version }}
52+
runs-on: ubuntu-22.04
53+
strategy:
54+
fail-fast: false
55+
matrix:
56+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
57+
steps:
58+
- uses: actions/checkout@v4
59+
- uses: actions/setup-python@v5
60+
with:
61+
python-version: ${{ matrix.python-version }}
62+
- name: Install from PyPI
63+
run: pip install qector-decoder-v3 --pre || echo "PyPI package not yet available -- skipping import test"
64+
- name: Import smoke test
65+
run: |
66+
python -c "
67+
try:
68+
import qector_decoder_v3
69+
print(f'qector_decoder_v3 {getattr(qector_decoder_v3, \"__version__\", \"unknown\")} imported OK')
70+
except ImportError as e:
71+
print(f'ImportError (expected if not on PyPI yet): {e}')
72+
"

0 commit comments

Comments
 (0)