| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,62 +4,110 @@ on: | |||
| 4 | 4 | push: | |
| 5 | 5 | branches: [main] | |
| 6 | 6 | workflow_dispatch: | |
| 7 | + inputs: | ||
| 8 | + tag: | ||
| 9 | + description: Existing release tag to recover (leave empty for a new release) | ||
| 10 | + type: string | ||
| 11 | + required: false | ||
| 12 | + | ||
| 13 | + # Serialize the entire release, including manual recoveries and uploads. | ||
| 14 | + concurrency: | ||
| 15 | + group: release | ||
| 16 | + cancel-in-progress: false | ||
| 17 | + | ||
| 18 | + permissions: | ||
| 19 | + contents: read | ||
| 7 | 20 | ||
| 8 | 21 | jobs: | |
| 9 | 22 | release: | |
| 10 | - if: github.repository == 'pythonnative/pythonnative' | ||
| 11 | - name: Semantic Release | ||
| 12 | - runs-on: ubuntu-latest | ||
| 13 | - concurrency: | ||
| 14 | - group: release | ||
| 15 | - cancel-in-progress: false | ||
| 23 | + if: github.repository == 'pythonnative/pythonnative' && github.ref == 'refs/heads/main' | ||
| 24 | + name: Select release | ||
| 25 | + runs-on: ubuntu-24.04 | ||
| 16 | 26 | permissions: | |
| 17 | 27 | contents: write | |
| 18 | - id-token: write | ||
| 19 | - | ||
| 28 | + outputs: | ||
| 29 | + tag: ${{ steps.select.outputs.tag }} | ||
| 30 | + commit: ${{ steps.select.outputs.commit }} | ||
| 20 | 31 | steps: | |
| 21 | - - name: Checkout | ||
| 22 | - uses: actions/checkout@v4 | ||
| 32 | + - uses: actions/checkout@v4 | ||
| 23 | 33 | with: | |
| 24 | 34 | fetch-depth: 0 | |
| 25 | - | ||
| 26 | - - name: Set up uv | ||
| 27 | - uses: astral-sh/setup-uv@v10.0.1 | ||
| 35 | + - uses: astral-sh/setup-uv@v10.0.1 | ||
| 28 | 36 | with: | |
| 29 | - enable-cache: true | ||
| 30 | 37 | python-version: '3.13' | |
| 31 | - | ||
| 32 | - # TEMPORARY: inlined replacement for the | ||
| 33 | - # python-semantic-release/python-semantic-release@v9 action. That | ||
| 34 | - # action builds its Docker image at job time with GitPython | ||
| 35 | - # unpinned, and GitPython 3.1.60 (2026-08-25) removed | ||
| 36 | - # Actor.name_email_regex, which crashes every semantic-release | ||
| 37 | - # config load (python-semantic-release issue #1475). This step | ||
| 38 | - # mirrors the action: same git identity, same `version` command, | ||
| 39 | - # and a `released` output for the steps below. Restore the action | ||
| 40 | - # once the fix (python-semantic-release PR #1477) is released. | ||
| 41 | - - name: Python Semantic Release | ||
| 42 | - id: release | ||
| 38 | + - name: Create a version or select an existing release | ||
| 39 | + id: select | ||
| 43 | 40 | env: | |
| 44 | 41 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| 42 | + RECOVERY_TAG: ${{ inputs.tag }} | ||
| 45 | 43 | run: | | |
| 46 | - git config user.name "github-actions" | ||
| 47 | - git config user.email "actions@github.com" | ||
| 48 | - uv tool install "python-semantic-release==9.21.2" --with "gitpython<3.1.60" | ||
| 49 | - tags_before=$(git tag | wc -l) | ||
| 50 | - semantic-release -v version | ||
| 51 | - tags_after=$(git tag | wc -l) | ||
| 52 | - if [ "$tags_after" -gt "$tags_before" ]; then | ||
| 53 | - echo "released=true" >> "$GITHUB_OUTPUT" | ||
| 44 | + if [ -n "$RECOVERY_TAG" ]; then | ||
| 45 | + # Only published release tags from main can be recovered. | ||
| 46 | + [[ "$RECOVERY_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] | ||
| 47 | + commit=$(git rev-parse --verify "refs/tags/$RECOVERY_TAG^{commit}") | ||
| 48 | + git merge-base --is-ancestor "$commit" origin/main | ||
| 49 | + gh release view "$RECOVERY_TAG" --json isDraft --jq '.isDraft' | grep -qx false | ||
| 50 | + tag="$RECOVERY_TAG" | ||
| 54 | 51 | else | |
| 55 | - echo "released=false" >> "$GITHUB_OUTPUT" | ||
| 52 | + git config user.name "github-actions" | ||
| 53 | + git config user.email "actions@github.com" | ||
| 54 | + # GitPython 3.1.60 removed Actor.name_email_regex, which PSR | ||
| 55 | + # 9.21.2 still uses. Keep this constraint until PSR is upgraded. | ||
| 56 | + uv tool install "python-semantic-release==9.21.2" --with "gitpython<3.1.60" | ||
| 57 | + tags_before=$(git tag | wc -l) | ||
| 58 | + semantic-release -v version | ||
| 59 | + tags_after=$(git tag | wc -l) | ||
| 60 | + if [ "$tags_after" -eq "$tags_before" ]; then | ||
| 61 | + echo "No new release. To resume an upload, run this workflow with its existing tag." | ||
| 62 | + exit 0 | ||
| 63 | + fi | ||
| 64 | + tag=$(git describe --tags --exact-match HEAD) | ||
| 65 | + commit=$(git rev-parse HEAD) | ||
| 56 | 66 | fi | |
| 67 | + # Check the tag against package metadata without importing/building it. | ||
| 68 | + git show "$commit:pyproject.toml" > "$RUNNER_TEMP/release-project.toml" | ||
| 69 | + RELEASE_TAG="$tag" python -c 'import os, pathlib, tomllib; data = tomllib.loads((pathlib.Path(os.environ["RUNNER_TEMP"]) / "release-project.toml").read_text()); assert os.environ["RELEASE_TAG"] == "v" + data["project"]["version"]' | ||
| 70 | + echo "tag=$tag" >> "$GITHUB_OUTPUT" | ||
| 71 | + echo "commit=$commit" >> "$GITHUB_OUTPUT" | ||
| 72 | + | ||
| 73 | + distributions: | ||
| 74 | + name: Build release distributions | ||
| 75 | + needs: release | ||
| 76 | + if: needs.release.outputs.tag != '' | ||
| 77 | + uses: ./.github/workflows/wheels.yml | ||
| 78 | + with: | ||
| 79 | + source-ref: ${{ needs.release.outputs.commit }} | ||
| 57 | 80 | ||
| 58 | - # The distributions come from `build_command` in | ||
| 59 | - # [tool.semantic_release], which runs `uv build` after PSR stamps the | ||
| 60 | - # new version, so there is no separate build step here. | ||
| 81 | + publish: | ||
| 82 | + name: Publish distributions | ||
| 83 | + needs: [release, distributions] | ||
| 84 | + runs-on: ubuntu-24.04 | ||
| 85 | + permissions: | ||
| 86 | + contents: write | ||
| 87 | + id-token: write | ||
| 88 | + steps: | ||
| 89 | + - uses: actions/checkout@v4 | ||
| 90 | + with: | ||
| 91 | + persist-credentials: false | ||
| 92 | + - uses: actions/download-artifact@v4 | ||
| 93 | + with: | ||
| 94 | + name: release-distributions | ||
| 95 | + path: dist | ||
| 96 | + - uses: astral-sh/setup-uv@v10.0.1 | ||
| 97 | + with: | ||
| 98 | + python-version: '3.13' | ||
| 99 | + - name: Preserve release assets for resumable publishing | ||
| 100 | + env: | ||
| 101 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| 102 | + RELEASE_TAG: ${{ needs.release.outputs.tag }} | ||
| 103 | + run: uv run --no-project python scripts/release-assets.py "$RELEASE_TAG" dist | ||
| 104 | + - name: Validate the exact files to publish | ||
| 105 | + env: | ||
| 106 | + RELEASE_TAG: ${{ needs.release.outputs.tag }} | ||
| 107 | + run: | | ||
| 108 | + uv run --no-project --with packaging python scripts/check-distributions.py dist --version "${RELEASE_TAG#v}" | ||
| 109 | + uvx twine check --strict dist/* | ||
| 61 | 110 | - name: Publish to PyPI | |
| 62 | - if: steps.release.outputs.released == 'true' | ||
| 63 | 111 | uses: pypa/gh-action-pypi-publish@release/v1 | |
| 64 | 112 | with: | |
| 65 | 113 | skip-existing: true | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,106 @@ | |||
| 1 | + name: Distributions | ||
| 2 | + | ||
| 3 | + on: | ||
| 4 | + pull_request: | ||
| 5 | + branches: [main] | ||
| 6 | + workflow_dispatch: | ||
| 7 | + inputs: | ||
| 8 | + source-ref: | ||
| 9 | + description: Commit or tag to build and test (no publishing) | ||
| 10 | + type: string | ||
| 11 | + required: true | ||
| 12 | + default: main | ||
| 13 | + workflow_call: | ||
| 14 | + inputs: | ||
| 15 | + source-ref: | ||
| 16 | + description: Immutable source commit selected by the release workflow | ||
| 17 | + type: string | ||
| 18 | + required: true | ||
| 19 | + | ||
| 20 | + permissions: | ||
| 21 | + contents: read | ||
| 22 | + | ||
| 23 | + jobs: | ||
| 24 | + sdist: | ||
| 25 | + name: Source distribution | ||
| 26 | + runs-on: ubuntu-24.04 | ||
| 27 | + outputs: | ||
| 28 | + filename: ${{ steps.build.outputs.filename }} | ||
| 29 | + version: ${{ steps.build.outputs.version }} | ||
| 30 | + steps: | ||
| 31 | + - uses: actions/checkout@v4 | ||
| 32 | + with: | ||
| 33 | + ref: ${{ inputs.source-ref || github.sha }} | ||
| 34 | + persist-credentials: false | ||
| 35 | + - uses: astral-sh/setup-uv@v10.0.1 | ||
| 36 | + with: | ||
| 37 | + python-version: '3.13' | ||
| 38 | + - name: Build source distribution | ||
| 39 | + id: build | ||
| 40 | + run: | | ||
| 41 | + uv build --sdist --python 3.13 | ||
| 42 | + echo "filename=$(basename dist/*.tar.gz)" >> "$GITHUB_OUTPUT" | ||
| 43 | + python -c 'import tomllib; print("version=" + tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])' >> "$GITHUB_OUTPUT" | ||
| 44 | + - uses: actions/upload-artifact@v4 | ||
| 45 | + with: | ||
| 46 | + name: distribution-sdist | ||
| 47 | + path: dist/*.tar.gz | ||
| 48 | + if-no-files-found: error | ||
| 49 | + | ||
| 50 | + wheels: | ||
| 51 | + name: Wheels (${{ matrix.runner }}) | ||
| 52 | + needs: sdist | ||
| 53 | + runs-on: ${{ matrix.runner }} | ||
| 54 | + strategy: | ||
| 55 | + fail-fast: false | ||
| 56 | + matrix: | ||
| 57 | + runner: [ubuntu-24.04, ubuntu-24.04-arm, macos-15-intel, macos-14, windows-2022] | ||
| 58 | + steps: | ||
| 59 | + # Build policy comes from the workflow revision, source from the sdist. | ||
| 60 | + # Recovery uses fixed tooling with an existing tag's unmodified source. | ||
| 61 | + - uses: actions/checkout@v4 | ||
| 62 | + with: | ||
| 63 | + persist-credentials: false | ||
| 64 | + - uses: actions/download-artifact@v4 | ||
| 65 | + with: | ||
| 66 | + name: distribution-sdist | ||
| 67 | + path: dist | ||
| 68 | + - name: Build, repair, install, and test wheels | ||
| 69 | + uses: pypa/cibuildwheel@v4.2.1 | ||
| 70 | + with: | ||
| 71 | + package-dir: dist/${{ needs.sdist.outputs.filename }} | ||
| 72 | + config-file: ${{ github.workspace }}/scripts/cibuildwheel.toml | ||
| 73 | + output-dir: wheelhouse | ||
| 74 | + - uses: actions/upload-artifact@v4 | ||
| 75 | + with: | ||
| 76 | + name: distribution-${{ matrix.runner }} | ||
| 77 | + path: wheelhouse/*.whl | ||
| 78 | + if-no-files-found: error | ||
| 79 | + | ||
| 80 | + validate: | ||
| 81 | + name: Validate release distributions | ||
| 82 | + needs: [sdist, wheels] | ||
| 83 | + runs-on: ubuntu-24.04 | ||
| 84 | + steps: | ||
| 85 | + - uses: actions/checkout@v4 | ||
| 86 | + with: | ||
| 87 | + persist-credentials: false | ||
| 88 | + - uses: actions/download-artifact@v4 | ||
| 89 | + with: | ||
| 90 | + pattern: distribution-* | ||
| 91 | + merge-multiple: true | ||
| 92 | + path: dist | ||
| 93 | + - uses: astral-sh/setup-uv@v10.0.1 | ||
| 94 | + with: | ||
| 95 | + python-version: '3.13' | ||
| 96 | + - name: Check metadata, platform tags, and complete wheel matrix | ||
| 97 | + env: | ||
| 98 | + RELEASE_VERSION: ${{ needs.sdist.outputs.version }} | ||
| 99 | + run: | | ||
| 100 | + uv run --no-project --with packaging python scripts/check-distributions.py dist --version "$RELEASE_VERSION" | ||
| 101 | + uvx twine check --strict dist/* | ||
| 102 | + - uses: actions/upload-artifact@v4 | ||
| 103 | + with: | ||
| 104 | + name: release-distributions | ||
| 105 | + path: dist/* | ||
| 106 | + if-no-files-found: error | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -336,15 +336,47 @@ Co-authored-by: Name <email> | |||
| 336 | 336 | ||
| 337 | 337 | - The version is tracked in `pyproject.toml` (`project.version`) and mirrored in `src/pythonnative/__init__.py` as `__version__`. Both files are updated automatically by [python-semantic-release](https://python-semantic-release.readthedocs.io/). | |
| 338 | 338 | - **Automated release pipeline** (on every merge to `main`): | |
| 339 | - 1. `python-semantic-release` scans Conventional Commit messages since the last tag. | ||
| 340 | - 2. It determines the next SemVer bump: `feat` → **minor**, `fix`/`perf` → **patch**, `BREAKING CHANGE` → **major** (minor while version < 1.0). | ||
| 341 | - 3. Version files are updated, `CHANGELOG.md` is generated, and a tagged release commit (`chore(release): vX.Y.Z`) is pushed. | ||
| 342 | - 4. A GitHub Release is created with auto-generated release notes and the built sdist/wheel attached. | ||
| 343 | - 5. When drafts are disabled, the package is also published to PyPI via Trusted Publishing. | ||
| 344 | - - **Draft / published toggle**: the `DRAFT_RELEASE` variable at the top of `.github/workflows/release.yml` controls release mode. Set to `"true"` (the default) for draft GitHub Releases with PyPI publishing skipped; flip to `"false"` to publish releases and upload to PyPI immediately. | ||
| 345 | - - Commit types that trigger a release: `feat` (minor), `fix` and `perf` (patch), `BREAKING CHANGE` (major). All other types (`build`, `chore`, `ci`, `docs`, `refactor`, `revert`, `style`, `test`) are recorded in the changelog but do **not** trigger a release on their own. | ||
| 346 | - - Tag format: `v`-prefixed (e.g., `v0.4.0`). | ||
| 347 | - - Manual version bumps are no longer needed: just merge PRs with valid Conventional Commit titles. For ad-hoc runs, use the workflow's **Run workflow** button (`workflow_dispatch`). | ||
| 339 | + 1. `python-semantic-release` scans Conventional Commits, updates the version files and `uv.lock`, generates `CHANGELOG.md`, and pushes the release commit and tag. | ||
| 340 | + 2. A published GitHub release is created with generated release notes. | ||
| 341 | + 3. The shared `Distributions` workflow builds a source archive from that exact commit, then uses [cibuildwheel](https://cibuildwheel.pypa.io/) to build all wheels from the archive. | ||
| 342 | + 4. Each wheel is repaired where needed, installed in an isolated environment, and tested with the Yoga layout suite and CLI. The complete artifact set must also pass platform, version, resource, and metadata checks. | ||
| 343 | + 5. Validated distributions are attached to the GitHub release before PyPI uploads begin. PyPI uses Trusted Publishing; no API token is needed. | ||
| 344 | + - The same distribution builds and checks run on PRs. The wheel matrix covers CPython 3.13 and 3.14 on Linux x86-64 and ARM64 (glibc 2.28 or newer), macOS Intel and Apple Silicon (macOS 11 or newer), and Windows x64. These are development-host wheels; mobile apps compile the bundled Yoga source in their native builds. | ||
| 345 | + - Build policy lives in `scripts/cibuildwheel.toml`, separately from the tagged package source, so fixed tooling can rebuild an older release without changing its code or version. Windows compiler/linker flags also support the original `v0.40.0` source; newer source distributions include the export settings in `setup.py`. | ||
| 346 | + - Commit types that trigger a release: `feat` (minor), `fix` and `perf` (patch), and `BREAKING CHANGE` (major, or minor before 1.0). Other types, including `build` and `ci`, don't trigger a release on their own. Use a `build` or `ci` title for a publishing-only repair that should recover the existing version. | ||
| 347 | + - Tag format: `v`-prefixed (for example, `v0.40.0`). Manual version bumps aren't needed. | ||
| 348 | + | ||
| 349 | + ### Recovering a failed publication | ||
| 350 | + | ||
| 351 | + Version creation and package publication are separate jobs. A GitHub release can | ||
| 352 | + exist even when its PyPI upload failed. Rerunning version creation won't create | ||
| 353 | + another release for the same commits. | ||
| 354 | + | ||
| 355 | + Once the workflow repair is merged, select **Actions → Release → Run workflow**, | ||
| 356 | + choose `main`, and enter the existing tag in the recovery field. With the GitHub | ||
| 357 | + CLI, the equivalent is: | ||
| 358 | + | ||
| 359 | + ```bash | ||
| 360 | + gh workflow run release.yml --ref main -f tag=v0.40.0 | ||
| 361 | + ``` | ||
| 362 | + | ||
| 363 | + Recovery verifies that the tag belongs to `main` and matches the package version, | ||
| 364 | + then runs the same build and validation jobs. It doesn't bump the version, | ||
| 365 | + rewrite the tag, or change the tagged source. Existing distribution assets are | ||
| 366 | + reused byte for byte; missing assets are uploaded before publishing to PyPI. | ||
| 367 | + Files already uploaded to PyPI are skipped, so a partial upload can resume. | ||
| 368 | + Release runs are serialized to prevent competing uploads. | ||
| 369 | + | ||
| 370 | + For a build-only rehearsal, run **Distributions** with a `source-ref` of the tag | ||
| 371 | + or commit to check. This runs the full matrix without publishing anything: | ||
| 372 | + | ||
| 373 | + ```bash | ||
| 374 | + gh workflow run wheels.yml --ref main -f source-ref=v0.40.0 | ||
| 375 | + ``` | ||
| 376 | + | ||
| 377 | + A source-only install of `v0.40.0` on Windows still needs the export flags from | ||
| 378 | + `scripts/cibuildwheel.toml`; its tagged `setup.py` predates that fix. The recovered | ||
| 379 | + Windows wheels include those exports and install without a compiler. | ||
| 348 | 380 | ||
| 349 | 381 | ### Branch naming (suggested) | |
| 350 | 382 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,5 @@ | |||
| 1 | 1 | global-exclude *.pyc *.pyo .DS_Store | |
| 2 | + include scripts/cibuildwheel.toml scripts/check-distributions.py scripts/release-assets.py | ||
| 2 | 3 | recursive-exclude src/pythonnative/native .build/* .gradle/* .cxx/* build/* .swiftpm/* .kotlin/* | |
| 3 | 4 | prune src/pythonnative/native/android/build | |
| 4 | 5 | prune src/pythonnative/native/android/.gradle | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -153,11 +153,11 @@ target-version = ['py313'] | |||
| 153 | 153 | # PSR stamps the new version into pyproject.toml before running this, so | |
| 154 | 154 | # `--upgrade-package` re-syncs only this project's own version in the lock | |
| 155 | 155 | # (never dependency upgrades), and the staged uv.lock lands in the release | |
| 156 | - # commit. This also replaces the workflow's separate `python -m build`. | ||
| 156 | + # commit. The release workflow builds distributions from that tagged commit | ||
| 157 | + # using the same cibuildwheel matrix as PR CI. | ||
| 157 | 158 | build_command = """ | |
| 158 | 159 | uv lock --upgrade-package pythonnative | |
| 159 | 160 | git add uv.lock | |
| 160 | - uv build | ||
| 161 | 161 | """ | |
| 162 | 162 | version_toml = ["pyproject.toml:project.version"] | |
| 163 | 163 | version_variables = ["src/pythonnative/__init__.py:__version__"] | |
| Back | FazBrowse Home | New Git URL |
0 commit comments