1 name: PyPI XCRS Release
2
3 # Windows arm64 wheels are intentionally not built here. GitHub's hosted
4 # Windows runners provide x64 Python, and maturin refuses to build an arm64
5 # wheel when the interpreter reports win-amd64.
6
7 on:
8 workflow_dispatch:
9 inputs:
10 tag:
11 description: "Release tag (e.g. v0.3.39)"
12 required: true
13
14 permissions:
15 id-token: write
16 contents: read
17
18 env:
19 CARGO_TERM_COLOR: always
20
21 jobs:
22 build-wheels:
23 name: Build PyPI wheels
24 runs-on: ${{ matrix.build.OS }}
25 strategy:
26 fail-fast: false
27 matrix:
28 build:
29 - {
30 NAME: linux-x64-glibc,
31 OS: ubuntu-latest,
32 TARGET: x86_64-unknown-linux-gnu,
33 MANYLINUX: "2014",
34 }
35 - {
36 NAME: linux-arm64-glibc,
37 OS: ubuntu-24.04-arm,
38 TARGET: aarch64-unknown-linux-gnu,
39 MANYLINUX: "2014",
40 }
41 - {
42 NAME: win32-x64-msvc,
43 OS: windows-2022,
44 TARGET: x86_64-pc-windows-msvc,
45 }
46
47 - { NAME: darwin-x64, OS: macos-latest, TARGET: x86_64-apple-darwin }
48 - {
49 NAME: darwin-arm64,
50 OS: macos-latest,
51 TARGET: aarch64-apple-darwin,
52 }
53 steps:
54 - name: Checkout
55 uses: actions/checkout@v6
56 with:
57 ref: ${{ github.event.inputs.tag || github.ref }}
58
59 - name: Set the release version
60 shell: bash
61 run: |
62 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
63 release_version="${GITHUB_REF_NAME#v}"
64 else
65 release_version="${{ github.event.inputs.tag }}"
66 release_version="${release_version#v}"
67 fi
68
69 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
70
71 - name: Read Rust toolchain
72 shell: bash
73 run: |
74 rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
75 if [ -z "$rust_toolchain" ]; then
76 echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2
77 exit 1
78 fi
79
80 echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV"
81
82 - name: Install toolkit
83 uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
84 with:
85 toolchain: ${{ env.RUST_TOOLCHAIN }}
86
87 - name: Install Rust target
88 shell: bash
89 run: |
90 rustup target add ${{ matrix.build.TARGET }} --toolchain ${{ env.RUST_TOOLCHAIN }}
91 rustup target list --installed
92
93 - name: Build wheel
94 uses: PyO3/maturin-action@v1
95 with:
96 target: ${{ matrix.build.TARGET }}
97 manylinux: ${{ matrix.build.MANYLINUX || 'off' }}
98 working-directory: pypi-xcrs
99 args: --release --locked --compatibility pypi --out dist
100
101 - name: Upload wheel artifact
102 uses: actions/upload-artifact@v7
103 with:
104 name: wheels-${{ matrix.build.NAME }}
105 path: pypi-xcrs/dist/*
106
107 build-sdist:
108 name: Build source distribution
109 runs-on: ubuntu-latest
110 steps:
111 - name: Checkout
112 uses: actions/checkout@v6
113 with:
114 ref: ${{ github.event.inputs.tag || github.ref }}
115
116 - name: Set the release version
117 shell: bash
118 run: |
119 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
120 release_version="${GITHUB_REF_NAME#v}"
121 else
122 release_version="${{ github.event.inputs.tag }}"
123 release_version="${release_version#v}"
124 fi
125
126 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
127
128 - name: Read Rust toolchain
129 shell: bash
130 run: |
131 rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
132 if [ -z "$rust_toolchain" ]; then
133 echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2
134 exit 1
135 fi
136
137 echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV"
138
139 - name: Install toolkit
140 uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
141 with:
142 toolchain: ${{ env.RUST_TOOLCHAIN }}
143
144 - name: Build sdist
145 uses: PyO3/maturin-action@v1
146 with:
147 command: sdist
148 working-directory: pypi-xcrs
149 args: --out dist
150
151 - name: Upload sdist artifact
152 uses: actions/upload-artifact@v7
153 with:
154 name: sdist
155 path: pypi-xcrs/dist/*
156
157 publish-pypi:
158 name: Publish to PyPI
159 runs-on: ubuntu-latest
160 needs:
161 - build-wheels
162 - build-sdist
163
164 steps:
165 - name: Checkout
166 uses: actions/checkout@v6
167 with:
168 ref: ${{ github.event.inputs.tag || github.ref }}
169
170 - name: Set the release version
171 shell: bash
172 run: |
173 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
174 release_version="${GITHUB_REF_NAME#v}"
175 else
176 release_version="${{ github.event.inputs.tag }}"
177 release_version="${release_version#v}"
178 fi
179
180 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
181
182 - name: Download distribution artifacts
183 uses: actions/download-artifact@v7
184 with:
185 pattern: "*"
186 path: dist
187 merge-multiple: true
188
189 - name: Check whether release already exists on PyPI
190 id: pypi-check
191 shell: bash
192 run: |
193 if curl -fsS "https://pypi.org/pypi/xcrs/${RELEASE_VERSION}/json" >/dev/null 2>&1; then
194 echo "xcrs ${RELEASE_VERSION} already exists on PyPI, skipping publish"
195 echo "exists=true" >> "$GITHUB_OUTPUT"
196 else
197 echo "exists=false" >> "$GITHUB_OUTPUT"
198 fi
199
200 - name: Publish distribution
201 if: steps.pypi-check.outputs.exists != 'true'
202 uses: pypa/gh-action-pypi-publish@release/v1
203 with:
204 skip-existing: true