|
7 | 7 | tags: |
8 | 8 | - "[0-9]+.[0-9]+.[0-9]+" |
9 | 9 | - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+" |
10 | | - - "[0-9]+.[0-9]+.[0-9]+dev[0-9]+" |
| 10 | + - "[0-9]+.[0-9]+.[0-9]+.dev[0-9]+" |
11 | 11 |
|
12 | 12 | jobs: |
13 | 13 | test_linux: |
@@ -283,3 +283,171 @@ jobs: |
283 | 283 | with: |
284 | 284 | name: dist-${{ matrix.os }}-${{ matrix.arch }} |
285 | 285 | path: dist/*.whl |
| 286 | + |
| 287 | + release: |
| 288 | + name: Publish GitHub release |
| 289 | + runs-on: ubuntu-latest |
| 290 | + needs: |
| 291 | + - sdist |
| 292 | + - bdist_wheel |
| 293 | + if: startsWith(github.ref, 'refs/tags/') |
| 294 | + permissions: |
| 295 | + contents: write |
| 296 | + steps: |
| 297 | + - name: Checkout code |
| 298 | + uses: actions/checkout@v6 |
| 299 | + |
| 300 | + - name: Resolve version and prerelease status |
| 301 | + id: meta |
| 302 | + run: | |
| 303 | + TAG="${GITHUB_REF_NAME}" |
| 304 | + VERSION="${TAG}" |
| 305 | + BASE_VERSION="$(echo "$VERSION" | sed -E 's/\.?(dev|rc)[0-9]+$//')" |
| 306 | + ANCHOR="version-$(echo "$BASE_VERSION" | tr '.' '-')" |
| 307 | + if [[ "$VERSION" == *dev* || "$VERSION" == *rc* ]]; then |
| 308 | + PRERELEASE="true" |
| 309 | + else |
| 310 | + PRERELEASE="false" |
| 311 | + fi |
| 312 | + echo "tag=$TAG" >> "$GITHUB_OUTPUT" |
| 313 | + echo "version=$VERSION" >> "$GITHUB_OUTPUT" |
| 314 | + echo "base_version=$BASE_VERSION" >> "$GITHUB_OUTPUT" |
| 315 | + echo "anchor=$ANCHOR" >> "$GITHUB_OUTPUT" |
| 316 | + echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT" |
| 317 | +
|
| 318 | + - name: Verify version matches tag |
| 319 | + run: | |
| 320 | + EXPECTED="${{ steps.meta.outputs.version }}" |
| 321 | + ACTUAL="$(python -c "import ast, re; src=open('src/wrapt/__init__.py').read(); m=re.search(r'__version_info__\s*=\s*(\([^)]+\))', src); parts=ast.literal_eval(m.group(1)); print(''.join(p if p.startswith('.') else '.'+p for p in parts).lstrip('.'))")" |
| 322 | + if [ "$ACTUAL" != "$EXPECTED" ]; then |
| 323 | + echo "Tag version ($EXPECTED) does not match __version__ ($ACTUAL) in src/wrapt/__init__.py" >&2 |
| 324 | + exit 1 |
| 325 | + fi |
| 326 | + echo "Version match: $ACTUAL" |
| 327 | +
|
| 328 | + - name: Download all built artifacts |
| 329 | + uses: actions/download-artifact@v8 |
| 330 | + with: |
| 331 | + path: dist |
| 332 | + merge-multiple: true |
| 333 | + |
| 334 | + - name: List collected artifacts |
| 335 | + run: ls -las dist |
| 336 | + |
| 337 | + - name: Generate SHA256SUMS |
| 338 | + run: | |
| 339 | + cd dist |
| 340 | + sha256sum * > SHA256SUMS |
| 341 | + cat SHA256SUMS |
| 342 | +
|
| 343 | + - name: Write release notes body |
| 344 | + run: | |
| 345 | + VERSION="${{ steps.meta.outputs.version }}" |
| 346 | + BASE_VERSION="${{ steps.meta.outputs.base_version }}" |
| 347 | + ANCHOR="${{ steps.meta.outputs.anchor }}" |
| 348 | + DOCS_URL="https://wrapt.readthedocs.io/en/latest/changes.html#${ANCHOR}" |
| 349 | + if [[ "$VERSION" == *dev* ]]; then |
| 350 | + cat > body.md <<EOF |
| 351 | + Development snapshot. Not normally published to PyPi |
| 352 | + (occasionally uploaded to exercise the release pipeline). |
| 353 | +
|
| 354 | + Release notes for the upcoming ${BASE_VERSION} final (work in |
| 355 | + progress): ${DOCS_URL} |
| 356 | +
|
| 357 | + Platform wheels and the source distribution are attached. To |
| 358 | + test against your Python install, either grab a matching wheel |
| 359 | + or build from the source distribution: |
| 360 | +
|
| 361 | + pip install wrapt-${VERSION}-<python>-<abi>-<platform>.whl |
| 362 | +
|
| 363 | + or |
| 364 | +
|
| 365 | + tar xf wrapt-${VERSION}.tar.gz |
| 366 | + cd wrapt-${VERSION} |
| 367 | + pip install . |
| 368 | +
|
| 369 | + \`SHA256SUMS\` is attached for verification of the archives. |
| 370 | + EOF |
| 371 | + elif [[ "$VERSION" == *rc* ]]; then |
| 372 | + cat > body.md <<EOF |
| 373 | + Release candidate. Release notes for the upcoming |
| 374 | + ${BASE_VERSION} final (work in progress): ${DOCS_URL} |
| 375 | +
|
| 376 | + May be installable from PyPi: |
| 377 | +
|
| 378 | + pip install wrapt==${VERSION} |
| 379 | +
|
| 380 | + If \`pip\` reports the version is unavailable, this candidate |
| 381 | + either has not been uploaded yet or is not being published to |
| 382 | + PyPi. Use the attached wheels or build from the source |
| 383 | + distribution instead: |
| 384 | +
|
| 385 | + tar xf wrapt-${VERSION}.tar.gz |
| 386 | + cd wrapt-${VERSION} |
| 387 | + pip install . |
| 388 | +
|
| 389 | + \`SHA256SUMS\` is attached for verification of the archives. |
| 390 | + EOF |
| 391 | + else |
| 392 | + cat > body.md <<EOF |
| 393 | + Full release notes: ${DOCS_URL} |
| 394 | +
|
| 395 | + Install from PyPi (recommended): |
| 396 | +
|
| 397 | + pip install wrapt==${VERSION} |
| 398 | +
|
| 399 | + PyPi uploads follow each GitHub release; if \`pip\` reports the |
| 400 | + version is unavailable, the matching PyPi upload may not have |
| 401 | + happened yet. |
| 402 | +
|
| 403 | + Pre-built wheels are provided for a range of Python versions |
| 404 | + and platforms (Linux x86_64/aarch64/riscv64, macOS x86_64 and |
| 405 | + arm64, Windows x86_64 and arm64, plus PyPy and free-threaded |
| 406 | + builds). The source distribution is also attached together |
| 407 | + with \`SHA256SUMS\` for verification. |
| 408 | + EOF |
| 409 | + fi |
| 410 | + cat body.md |
| 411 | +
|
| 412 | + - name: Create GitHub release |
| 413 | + env: |
| 414 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 415 | + GH_REPO: ${{ github.repository }} |
| 416 | + run: | |
| 417 | + FLAGS=() |
| 418 | + if [[ "${{ steps.meta.outputs.prerelease }}" == "true" ]]; then |
| 419 | + FLAGS+=("--prerelease") |
| 420 | + fi |
| 421 | + gh release create "${{ steps.meta.outputs.tag }}" \ |
| 422 | + --title "wrapt ${{ steps.meta.outputs.version }}" \ |
| 423 | + --notes-file body.md \ |
| 424 | + "${FLAGS[@]}" \ |
| 425 | + dist/* |
| 426 | +
|
| 427 | + publish-wrapt: |
| 428 | + name: Publish wrapt to PyPi |
| 429 | + runs-on: ubuntu-latest |
| 430 | + needs: |
| 431 | + - release |
| 432 | + if: startsWith(github.ref, 'refs/tags/') |
| 433 | + environment: pypi-wrapt |
| 434 | + permissions: |
| 435 | + id-token: write |
| 436 | + steps: |
| 437 | + - name: Download all built artifacts |
| 438 | + uses: actions/download-artifact@v8 |
| 439 | + with: |
| 440 | + path: dist |
| 441 | + merge-multiple: true |
| 442 | + |
| 443 | + - name: Stage wrapt sdist and wheels |
| 444 | + run: | |
| 445 | + mkdir -p stage |
| 446 | + cp dist/wrapt-[0-9]*.tar.gz stage/ |
| 447 | + cp dist/wrapt-[0-9]*.whl stage/ |
| 448 | + ls -las stage |
| 449 | +
|
| 450 | + - name: Publish wrapt to PyPi |
| 451 | + uses: pypa/gh-action-pypi-publish@release/v1 |
| 452 | + with: |
| 453 | + packages-dir: stage/ |
0 commit comments