Fix windows builds #8444
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: 🚀 Build & release | |
| # Read https://github.com/actions/runner/issues/491 for insights on complex workflow execution logic. | |
| "on": | |
| workflow_call: | |
| secrets: | |
| PYPI_TOKEN: | |
| required: false | |
| WORKFLOW_UPDATE_GITHUB_PAT: | |
| required: false | |
| outputs: | |
| nuitka_matrix: | |
| description: Nuitka build matrix | |
| value: ${{ toJSON(fromJSON(jobs.metadata.outputs.metadata).nuitka_matrix) }} | |
| # Targets are chosen so that all commits get a chance to have their package tested. | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches-ignore: | |
| - prepare-release | |
| - renovate/** | |
| paths: | |
| - repomatic/** | |
| - tests/** | |
| - pyproject.toml | |
| - uv.lock | |
| - .github/workflows/release.yaml | |
| concurrency: | |
| # Release commits get a unique SHA-based group so they can never be cancelled. | |
| # See repomatic/github/actions.py for rationale. | |
| group: >- | |
| ${{ github.workflow }}-${{ | |
| github.event.pull_request.number | |
| || ( | |
| (startsWith(github.event.head_commit.message, '[changelog] Release') | |
| || startsWith(github.event.head_commit.message, '[changelog] Post-release')) | |
| && github.sha | |
| ) | |
| || github.ref | |
| }} | |
| cancel-in-progress: true | |
| jobs: | |
| detect-squash-merge: | |
| name: 🧯 No squash on release | |
| # Detect squash merges on release PRs and open an issue instead of releasing. | |
| # See RELEASE_COMMIT_PATTERN in repomatic/metadata.py for the detection mechanism. | |
| if: >- | |
| github.event_name == 'push' | |
| && startsWith(github.event.head_commit.message, 'Release `v') | |
| runs-on: ubuntu-slim | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| - name: Extract PR reference | |
| id: extract | |
| env: | |
| COMMIT_MSG: ${{ github.event.head_commit.message }} | |
| run: | | |
| pr_ref=$(echo "${COMMIT_MSG}" | grep -oE '#[0-9]+' | tail -1) | |
| echo "pr_ref=${pr_ref}" >> "$GITHUB_OUTPUT" | |
| - name: Generate issue body | |
| id: issue-metadata | |
| env: | |
| PR_REF: ${{ steps.extract.outputs.pr_ref }} | |
| run: > | |
| uvx --no-progress --from . repomatic pr-body | |
| --template detect-squash-merge | |
| --pr-ref "${PR_REF}" | |
| --output "$GITHUB_OUTPUT" | |
| - name: Open issue to notify maintainer | |
| id: issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| TITLE: ${{ steps.issue-metadata.outputs.title }} | |
| BODY: ${{ steps.issue-metadata.outputs.body }} | |
| run: | | |
| issue_url=$(gh issue create \ | |
| --title "${TITLE}" \ | |
| --assignee "${GITHUB_ACTOR}" \ | |
| --body "${BODY}") | |
| echo "issue_url=${issue_url}" >> "$GITHUB_OUTPUT" | |
| - name: Fail workflow | |
| env: | |
| ISSUE_URL: ${{ steps.issue.outputs.issue_url }} | |
| run: | | |
| echo "::error::Squash merge detected. Release was skipped. See ${ISSUE_URL}" | |
| exit 1 | |
| metadata: | |
| name: 🧬 Project metadata | |
| runs-on: ubuntu-slim | |
| outputs: | |
| metadata: ${{ steps.metadata.outputs.metadata }} | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| # Checkout pull request HEAD commit to ignore actions/checkout's merge commit. Fallback to push SHA. | |
| # See: https://github.com/actions/checkout/issues/504 | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| # We're going to browse all new commits. | |
| fetch-depth: 0 | |
| - name: List all branches | |
| run: git branch --all | |
| - name: List all commits | |
| run: git log --decorate=full --oneline | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| - name: Run repomatic metadata | |
| id: metadata | |
| run: > | |
| uvx --no-progress --from . repomatic metadata | |
| --format github-json --output "$GITHUB_OUTPUT" | |
| new_commits_matrix release_commits_matrix build_targets nuitka_matrix | |
| is_python_project package_name current_version released_version | |
| release_notes release_notes_with_admonition skip_binary_build | |
| build-package: | |
| name: 📦 Build package (${{ matrix.short_sha }}) | |
| needs: | |
| - metadata | |
| if: fromJSON(needs.metadata.outputs.metadata).is_python_project | |
| strategy: | |
| matrix: >- | |
| ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix | |
| || fromJSON(needs.metadata.outputs.metadata).new_commits_matrix }} | |
| runs-on: ubuntu-slim | |
| permissions: | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| ref: ${{ matrix.commit }} | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| - name: Build package | |
| run: uv --no-progress build | |
| - name: Generate build attestations | |
| uses: actions/attest-build-provenance@v3.2.0 | |
| with: | |
| subject-path: ./dist/* | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v6.0.0 | |
| with: | |
| name: ${{ github.event.repository.name }}-${{ matrix.short_sha }} | |
| path: ./dist/* | |
| compile-binaries: | |
| name: "${{ matrix.state == 'stable' && '✅' || '⁉️' }} ${{ matrix.os }}, ${{ matrix.short_sha }} build" | |
| needs: | |
| - metadata | |
| - create-release | |
| # Skip binary compilation for branches that don't affect code (e.g., update-mailmap, format-markdown). | |
| # Use always() because create-release is skipped on non-release pushes and PRs. | |
| if: >- | |
| always() | |
| && needs.metadata.result == 'success' | |
| && fromJSON(needs.metadata.outputs.metadata).nuitka_matrix | |
| && !(fromJSON(needs.metadata.outputs.metadata).skip_binary_build || false) | |
| strategy: | |
| matrix: ${{ fromJSON(needs.metadata.outputs.metadata).nuitka_matrix }} | |
| runs-on: ${{ matrix.os }} | |
| # We keep going when a job flagged as not stable fails. | |
| continue-on-error: ${{ matrix.state == 'unstable' }} | |
| permissions: | |
| id-token: write | |
| attestations: write | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| ref: ${{ matrix.commit }} | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| - name: Setup venv | |
| run: uv --no-progress venv --python 3.13 | |
| - name: Install Nuitka | |
| run: uv --no-progress pip install 'nuitka[onefile]==2.8.10' | |
| - name: Pre-bake version with commit hash | |
| if: contains(matrix.current_version, '.dev') | |
| env: | |
| SHORT_SHA: ${{ matrix.short_sha }} | |
| shell: bash | |
| run: > | |
| uvx --no-progress --from . repomatic | |
| prebake-version --hash "${SHORT_SHA}" | |
| - name: Nuitka + compilers versions | |
| run: uv --no-progress run --frozen -- nuitka --version | |
| - name: Build binary | |
| id: build-binary | |
| continue-on-error: true | |
| # Project-specific Nuitka flags come from [tool.repomatic] | |
| # nuitka-extra-args in pyproject.toml, passed via the build matrix. | |
| env: | |
| NUITKA_EXTRA_ARGS: ${{ matrix.nuitka_extra_args }} | |
| BIN_NAME: ${{ matrix.bin_name }} | |
| MODULE_PATH: ${{ matrix.module_path }} | |
| shell: bash | |
| run: | | |
| # shellcheck disable=SC2086 | |
| uv --no-progress run --frozen -- nuitka \ | |
| --onefile --assume-yes-for-downloads \ | |
| ${NUITKA_EXTRA_ARGS} \ | |
| --output-filename="${BIN_NAME}" "${MODULE_PATH}" | |
| - name: Upload Nuitka crash report | |
| uses: actions/upload-artifact@v6.0.0 | |
| with: | |
| name: nuitka-crash-report-${{ matrix.os }}-${{ matrix.short_sha }}.xml | |
| if-no-files-found: ignore | |
| path: nuitka-crash-report.xml | |
| - if: steps.build-binary.outcome == 'failure' | |
| run: | | |
| echo "Nuitka build failed, skipping the rest of the steps." | |
| exit 1 | |
| - name: Install exiftool - Linux | |
| if: runner.os == 'Linux' | |
| run: sudo apt --quiet --yes install exiftool | |
| - name: Install exiftool - macOS | |
| if: runner.os == 'macOS' | |
| run: brew install exiftool | |
| - name: Install exiftool - Windows | |
| if: runner.os == 'Windows' | |
| run: choco install exiftool --no-progress --yes --retry-count=3 | |
| - name: Verify binary architecture | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| BIN_NAME: ${{ matrix.bin_name }} | |
| shell: bash | |
| run: > | |
| uvx --no-progress --from . repomatic | |
| verify-binary --target "${TARGET}" --binary "${BIN_NAME}" | |
| - name: Upload binaries | |
| uses: actions/upload-artifact@v6.0.0 | |
| with: | |
| # Artifact name includes SHA for uniqueness across commits; the file | |
| # inside uses the clean release name (no SHA suffix). | |
| name: ${{ matrix.bin_name }}-${{ matrix.short_sha }} | |
| if-no-files-found: warn | |
| path: ${{ matrix.bin_name }} | |
| - name: Generate binary attestation | |
| if: >- | |
| github.ref == 'refs/heads/main' | |
| && fromJSON(needs.metadata.outputs.metadata).release_commits_matrix | |
| uses: actions/attest-build-provenance@v3.2.0 | |
| with: | |
| subject-path: ${{ matrix.bin_name }} | |
| - name: Upload binary to GitHub release | |
| if: >- | |
| github.ref == 'refs/heads/main' | |
| && fromJSON(needs.metadata.outputs.metadata).release_commits_matrix | |
| env: | |
| GH_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }} | |
| CURRENT_VERSION: ${{ matrix.current_version }} | |
| BIN_NAME: ${{ matrix.bin_name }} | |
| shell: bash | |
| run: > | |
| gh release upload "v${CURRENT_VERSION}" | |
| "${BIN_NAME}" | |
| --repo "${{ github.repository }}" | |
| test-binaries: | |
| name: "${{ matrix.state == 'stable' && '✅' || '⁉️' }} ${{ matrix.os }}, ${{ matrix.short_sha }} test" | |
| needs: | |
| - metadata | |
| - compile-binaries | |
| # Skip binary tests for branches that don't affect code (e.g., update-mailmap, format-markdown). | |
| if: > | |
| fromJSON(needs.metadata.outputs.metadata).nuitka_matrix | |
| && !(fromJSON(needs.metadata.outputs.metadata).skip_binary_build || false) | |
| strategy: | |
| matrix: ${{ fromJSON(needs.metadata.outputs.metadata).nuitka_matrix }} | |
| runs-on: ${{ matrix.os }} | |
| # We keep going when a job flagged as not stable fails. | |
| continue-on-error: ${{ matrix.state == 'unstable' }} | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| ref: ${{ matrix.commit }} | |
| - name: Download artifact | |
| uses: actions/download-artifact@v7.0.0 | |
| id: artifacts | |
| with: | |
| name: ${{ matrix.bin_name }}-${{ matrix.short_sha }} | |
| - name: Set binary permissions | |
| if: runner.os != 'Windows' | |
| env: | |
| DOWNLOAD_PATH: ${{ steps.artifacts.outputs.download-path }} | |
| BIN_NAME: ${{ matrix.bin_name }} | |
| run: chmod +x "${DOWNLOAD_PATH}/${BIN_NAME}" | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| - name: Run test plan for binary | |
| env: | |
| DOWNLOAD_PATH: ${{ steps.artifacts.outputs.download-path }} | |
| BIN_NAME: ${{ matrix.bin_name }} | |
| shell: bash | |
| run: > | |
| uvx --no-progress --from . repomatic test-plan | |
| --binary "${DOWNLOAD_PATH}/${BIN_NAME}" | |
| create-tag: | |
| name: 📌 Tag release (${{ matrix.short_sha }}) | |
| needs: | |
| - metadata | |
| # Only consider pushes to main branch as triggers for releases. | |
| if: github.ref == 'refs/heads/main' && fromJSON(needs.metadata.outputs.metadata).release_commits_matrix | |
| strategy: | |
| matrix: ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }} | |
| runs-on: ubuntu-slim | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| ref: ${{ matrix.commit }} | |
| # PAT required so tag pushes trigger downstream on.push.tags workflows. | |
| # See repomatic/git_ops.py module docstring. | |
| token: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }} | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| - name: Create and push tag | |
| # Idempotent: skips if tag already exists instead of failing. This allows re-running | |
| # workflows interrupted after tag creation. | |
| env: | |
| CURRENT_VERSION: ${{ matrix.current_version }} | |
| COMMIT: ${{ matrix.commit }} | |
| run: > | |
| uvx --no-progress --from . repomatic git-tag | |
| --tag "v${CURRENT_VERSION}" | |
| --commit "${COMMIT}" | |
| --skip-existing | |
| publish-pypi: | |
| name: 🐍 Publish to PyPI (${{ matrix.short_sha }}) | |
| needs: | |
| - metadata | |
| - build-package | |
| - create-tag | |
| if: fromJSON(needs.metadata.outputs.metadata).package_name | |
| strategy: | |
| matrix: ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }} | |
| runs-on: ubuntu-slim | |
| permissions: | |
| # Allow GitHub's OIDC provider to create a JSON Web Token: | |
| # https://github.blog/changelog/2023-06-15-github-actions-securing-openid-connect-oidc-token-permissions-in-reusable-workflows/ | |
| # https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v7.0.0 | |
| id: download | |
| with: | |
| name: ${{ github.event.repository.name }}-${{ matrix.short_sha }} | |
| - name: Generate attestations | |
| uses: actions/attest-build-provenance@v3.2.0 | |
| with: | |
| subject-path: "${{ steps.download.outputs.download-path }}/*" | |
| - name: Push to PyPI | |
| env: | |
| PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} | |
| DOWNLOAD_PATH: ${{ steps.download.outputs.download-path }} | |
| run: > | |
| uv --no-progress publish --token "${PYPI_TOKEN}" | |
| "${DOWNLOAD_PATH}/*" | |
| create-release: | |
| name: 🐙 Create GitHub release draft (${{ matrix.short_sha }}) | |
| needs: | |
| - metadata | |
| - build-package | |
| - create-tag | |
| - publish-pypi | |
| # Make sure this job always starts if create-tag ran and succeeded. | |
| if: always() && needs.create-tag.result == 'success' | |
| strategy: | |
| matrix: ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }} | |
| runs-on: ubuntu-slim | |
| permissions: | |
| # Allow GitHub's OIDC provider to create a JSON Web Token: | |
| # https://github.blog/changelog/2023-06-15-github-actions-securing-openid-connect-oidc-token-permissions-in-reusable-workflows/ | |
| # https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings | |
| id-token: write | |
| attestations: write | |
| # Allow project without WORKFLOW_UPDATE_GITHUB_PAT to create a GitHub release. | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| with: | |
| ref: ${{ matrix.commit }} | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| with: | |
| enable-cache: false | |
| - name: Download Python package | |
| # Do not fetch build artifacts if build-package was skipped (non-Python projects). | |
| if: needs.build-package.result != 'skipped' | |
| uses: actions/download-artifact@v7.0.0 | |
| id: artifacts | |
| with: | |
| path: release_artifact | |
| name: ${{ github.event.repository.name }}-${{ matrix.short_sha }} | |
| - name: Generate attestations | |
| # Do not try to attest artifacts if none have been downloaded. | |
| if: steps.artifacts.outputs.download-path | |
| uses: actions/attest-build-provenance@v3.2.0 | |
| with: | |
| subject-path: release_artifact/* | |
| - name: Delete dev pre-release | |
| # Clean up all rolling dev releases before creating the real release. | |
| # See repomatic/github/dev_release.py for rationale. | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }} | |
| run: uvx --no-progress --from . repomatic sync-dev-release --live --delete | |
| - name: Create GitHub release draft | |
| # Idempotent: skips if release already exists (e.g. workflow re-run). | |
| env: | |
| GH_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }} | |
| CURRENT_VERSION: ${{ matrix.current_version }} | |
| COMMIT: ${{ matrix.commit }} | |
| REPO: ${{ github.repository }} | |
| RELEASE_NOTES: >- | |
| ${{ needs.publish-pypi.result == 'success' | |
| && fromJSON(needs.metadata.outputs.metadata).release_notes_with_admonition | |
| || fromJSON(needs.metadata.outputs.metadata).release_notes }} | |
| run: | | |
| tag="v${CURRENT_VERSION}" | |
| if gh release view "${tag}" --repo "${REPO}" > /dev/null 2>&1; then | |
| echo "Release ${tag} already exists, skipping creation." | |
| else | |
| shopt -s nullglob | |
| files=(release_artifact/*) | |
| gh release create "${tag}" \ | |
| --draft \ | |
| --target "${COMMIT}" \ | |
| --title "${tag}" \ | |
| --repo "${REPO}" \ | |
| --notes-file - \ | |
| "${files[@]}" <<< "${RELEASE_NOTES}" | |
| fi | |
| publish-release: | |
| name: 🎉 Publish GitHub release (${{ matrix.short_sha }}) | |
| needs: | |
| - metadata | |
| - create-release | |
| - compile-binaries | |
| # Wait for all upstream jobs regardless of result, but only publish if the draft was created. | |
| if: >- | |
| always() | |
| && needs.create-release.result == 'success' | |
| strategy: | |
| matrix: ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }} | |
| runs-on: ubuntu-slim | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Publish release | |
| env: | |
| GH_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }} | |
| CURRENT_VERSION: ${{ matrix.current_version }} | |
| REPO: ${{ github.repository }} | |
| run: > | |
| gh release edit "v${CURRENT_VERSION}" | |
| --draft=false | |
| --repo "${REPO}" | |
| sync-dev-release: | |
| name: 🔄 Sync dev pre-release | |
| needs: | |
| - metadata | |
| - build-package | |
| - compile-binaries | |
| # Run on non-release pushes to main after builds complete. | |
| # Uses always() because compile-binaries depends on create-release (skipped for non-release pushes). | |
| if: >- | |
| always() | |
| && github.ref == 'refs/heads/main' | |
| && !fromJSON(needs.metadata.outputs.metadata).release_commits_matrix | |
| && fromJSON(needs.metadata.outputs.metadata).current_version | |
| runs-on: ubuntu-slim | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: astral-sh/setup-uv@v7.3.1 | |
| - name: Download all build artifacts | |
| uses: actions/download-artifact@v7.0.0 | |
| with: | |
| path: release_assets | |
| merge-multiple: true | |
| - name: Sync dev release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: > | |
| uvx --no-progress --from . repomatic sync-dev-release --live | |
| --upload-assets release_assets |