fix: honor configured framework runtime settings #425
Workflow file for this run
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: Package | |
| on: | |
| pull_request: | |
| paths: | |
| - "lib/python/base_cli/**" | |
| - "pyproject.toml" | |
| - "VERSION" | |
| - "README.md" | |
| - "LICENSE" | |
| - "MANIFEST.in" | |
| - "scripts/validate_package_artifact.py" | |
| - "scripts/validate_installed_package.py" | |
| - "scripts/validate_examples.py" | |
| - "scripts/generate_release_metadata.py" | |
| - "scripts/validate_release_metadata.py" | |
| - "scripts/verify_release_assets.py" | |
| - "scripts/validate_changelog.py" | |
| - "scripts/validate_release_ref.py" | |
| - "CHANGELOG.md" | |
| - "examples/**" | |
| - "compatibility/**" | |
| - "docs/releasing.md" | |
| - "tests/test_package_workflow.py" | |
| - "tests/test_verify_release_assets.py" | |
| - "requirements/release.in" | |
| - "requirements/release.txt" | |
| - ".github/workflows/package.yml" | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| publish_target: | |
| description: "Protected publication target" | |
| required: true | |
| type: choice | |
| options: | |
| - testpypi | |
| - pypi | |
| default: testpypi | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| PACKAGE_NAME: base-cli | |
| jobs: | |
| build: | |
| name: Build and validate distributions | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| outputs: | |
| version: ${{ steps.metadata.outputs.version }} | |
| steps: | |
| - name: Check out source | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.13" | |
| - name: Read package version | |
| id: metadata | |
| run: echo "version=$(tr -d '\\r\\n' < VERSION)" >> "$GITHUB_OUTPUT" | |
| - name: Validate release ref | |
| env: | |
| PUBLISH_TARGET: ${{ inputs.publish_target || '' }} | |
| run: | | |
| if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" != "v${{ steps.metadata.outputs.version }}" ]]; then | |
| echo "Release tag must be v${{ steps.metadata.outputs.version }}; got $GITHUB_REF_NAME" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$PUBLISH_TARGET" == "pypi" && "$GITHUB_REF_TYPE" != "tag" ]]; then | |
| echo "PyPI publication requires dispatching this workflow from the matching version tag." >&2 | |
| exit 1 | |
| fi | |
| - name: Validate changelog | |
| run: python scripts/validate_changelog.py | |
| - name: Validate tagged release notes | |
| if: ${{ github.ref_type == 'tag' }} | |
| env: | |
| RELEASE_TAG: ${{ github.ref_name }} | |
| run: python scripts/validate_release_ref.py | |
| - name: Prepare clean artifact destination | |
| run: | | |
| git clean -ffdx | |
| mkdir -p dist | |
| - name: Install build and validation tools | |
| run: python -m pip install --require-hashes --no-deps -r requirements/release.txt | |
| - name: Build sdist and wheel | |
| env: | |
| # Keep the reproducibility input numeric; push-event timestamps are | |
| # ISO-8601 strings and are not accepted by wheel/build backends. | |
| SOURCE_DATE_EPOCH: '0' | |
| run: | | |
| rm -rf dist-a dist-b "$RUNNER_TEMP/base-cli-build-a" "$RUNNER_TEMP/base-cli-build-b" | |
| mkdir -p dist-a dist-b | |
| mkdir -p "$RUNNER_TEMP/base-cli-build-a" "$RUNNER_TEMP/base-cli-build-b" | |
| git archive "$GITHUB_SHA" | tar -x -C "$RUNNER_TEMP/base-cli-build-a" | |
| git archive "$GITHUB_SHA" | tar -x -C "$RUNNER_TEMP/base-cli-build-b" | |
| (cd "$RUNNER_TEMP/base-cli-build-a" && python -m build --sdist --wheel --outdir "$GITHUB_WORKSPACE/dist-a") | |
| (cd "$RUNNER_TEMP/base-cli-build-b" && python -m build --sdist --wheel --outdir "$GITHUB_WORKSPACE/dist-b") | |
| python - <<'PY' | |
| import gzip | |
| import os | |
| import tarfile | |
| from pathlib import Path | |
| epoch = int(os.environ["SOURCE_DATE_EPOCH"]) | |
| for directory in (Path("dist-a"), Path("dist-b")): | |
| for path in directory.glob("*.tar.gz"): | |
| with tarfile.open(path, "r:gz") as source: | |
| members = [(member, source.extractfile(member).read() if member.isfile() else None) for member in source.getmembers()] | |
| temporary = path.with_suffix(path.suffix + ".tmp") | |
| with temporary.open("wb") as raw: | |
| with gzip.GzipFile(filename=path.name, mode="wb", fileobj=raw, mtime=epoch) as compressed: | |
| with tarfile.open(fileobj=compressed, mode="w|") as target: | |
| for member, payload in sorted(members, key=lambda item: item[0].name): | |
| member.mtime = epoch | |
| member.uid = member.gid = 0 | |
| member.uname = member.gname = "" | |
| member.pax_headers = {} | |
| target.addfile(member, None if payload is None else __import__("io").BytesIO(payload)) | |
| temporary.replace(path) | |
| PY | |
| (cd dist-a && sha256sum * | sort) > dist-a.SHA256SUMS | |
| (cd dist-b && sha256sum * | sort) > dist-b.SHA256SUMS | |
| diff -u dist-a.SHA256SUMS dist-b.SHA256SUMS | |
| cp dist-a/* dist/ | |
| - name: Validate artifact contents and metadata | |
| run: python scripts/validate_package_artifact.py dist | |
| - name: Validate package indexes | |
| run: python -m twine check dist/* | |
| - name: Generate release checksums and SPDX SBOM | |
| env: | |
| SOURCE_REVISION: ${{ github.sha }} | |
| SOURCE_DATE_EPOCH: '0' | |
| run: python scripts/generate_release_metadata.py dist | |
| - name: Validate release checksums and SPDX SBOM | |
| env: | |
| SOURCE_REVISION: ${{ github.sha }} | |
| run: python scripts/validate_release_metadata.py dist | |
| - name: Upload reviewed distributions | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: base-cli-dist-${{ github.run_id }} | |
| path: | | |
| dist/*.whl | |
| dist/*.tar.gz | |
| if-no-files-found: error | |
| retention-days: 14 | |
| - name: Upload release metadata | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: base-cli-release-metadata-${{ github.run_id }} | |
| path: | | |
| dist/SBOM.spdx.json | |
| dist/SHA256SUMS | |
| dist/RELEASE-BOM-ROW.json | |
| if-no-files-found: error | |
| retention-days: 90 | |
| smoke: | |
| name: Install smoke test (Python ${{ matrix.python-version }}) | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - name: Check out source | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Download reviewed distributions | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: base-cli-dist-${{ github.run_id }} | |
| path: dist | |
| - name: Install wheel and runtime dependencies | |
| run: python -m pip install dist/base_cli-*.whl | |
| - name: Verify installed package | |
| env: | |
| EXPECTED_VERSION: ${{ needs.build.outputs.version }} | |
| run: | | |
| python - <<'PY' | |
| import base_cli | |
| import importlib.metadata | |
| expected = __import__("os").environ["EXPECTED_VERSION"] | |
| assert base_cli.__version__ == expected, (base_cli.__version__, expected) | |
| assert importlib.metadata.version("base-cli") == expected | |
| assert hasattr(base_cli, "App") | |
| print(f"base-cli {base_cli.__version__} installed successfully") | |
| PY | |
| - name: Exercise installed wheel API and lifecycle | |
| env: | |
| EXPECTED_VERSION: ${{ needs.build.outputs.version }} | |
| run: python -I scripts/validate_installed_package.py | |
| - name: Check installed dependency consistency | |
| run: python -m pip check | |
| publish: | |
| name: Publish reviewed distribution | |
| needs: [build, smoke] | |
| if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| environment: | |
| name: ${{ github.event_name == 'push' && 'pypi' || inputs.publish_target }} | |
| url: ${{ github.event_name == 'push' && 'https://pypi.org/p/base-cli' || 'https://test.pypi.org/p/base-cli' }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Download reviewed distributions | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: base-cli-dist-${{ github.run_id }} | |
| path: dist | |
| - name: Publish to TestPyPI | |
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi' }} | |
| uses: pypa/gh-action-pypi-publish@4bb033805d9e19112d8c697528791ff53f6c2f74 | |
| with: | |
| packages-dir: dist | |
| repository-url: https://test.pypi.org/legacy/ | |
| - name: Publish to PyPI | |
| if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi') }} | |
| uses: pypa/gh-action-pypi-publish@4bb033805d9e19112d8c697528791ff53f6c2f74 | |
| with: | |
| packages-dir: dist | |
| attest: | |
| name: Attest reviewed release | |
| needs: [build, smoke] | |
| if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - name: Download reviewed distributions | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: base-cli-dist-${{ github.run_id }} | |
| path: dist | |
| - name: Download release metadata | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: base-cli-release-metadata-${{ github.run_id }} | |
| path: dist | |
| - name: Attest artifact provenance | |
| uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 | |
| with: | |
| subject-checksums: dist/SHA256SUMS | |
| - name: Attest SPDX SBOM | |
| uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 | |
| with: | |
| subject-checksums: dist/SHA256SUMS | |
| sbom-path: dist/SBOM.spdx.json | |
| release: | |
| name: Create GitHub Release | |
| needs: [build, smoke, publish, attest] | |
| if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| attestations: read | |
| steps: | |
| - name: Check out the tag source | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.13" | |
| - name: Download reviewed distributions | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: base-cli-dist-${{ github.run_id }} | |
| path: dist | |
| - name: Download release metadata | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: base-cli-release-metadata-${{ github.run_id }} | |
| path: dist | |
| - name: Verify and create immutable GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| tag="$GITHUB_REF_NAME" | |
| assets=(dist/*.whl dist/*.tar.gz dist/SHA256SUMS dist/SBOM.spdx.json dist/RELEASE-BOM-ROW.json) | |
| tag_commit="$(gh api "repos/$GITHUB_REPOSITORY/commits/$tag" --jq .sha)" | |
| if [[ "$tag_commit" != "$GITHUB_SHA" ]]; then | |
| echo "Release tag $tag resolves to $tag_commit, not reviewed commit $GITHUB_SHA." >&2 | |
| exit 1 | |
| fi | |
| python scripts/validate_release_metadata.py dist | |
| for asset in dist/*.whl dist/*.tar.gz; do | |
| gh attestation verify "$asset" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --signer-workflow "github.com/$GITHUB_REPOSITORY/.github/workflows/package.yml" \ | |
| --source-digest "$GITHUB_SHA" \ | |
| --source-ref "$GITHUB_REF" | |
| gh attestation verify "$asset" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --signer-workflow "github.com/$GITHUB_REPOSITORY/.github/workflows/package.yml" \ | |
| --source-digest "$GITHUB_SHA" \ | |
| --source-ref "$GITHUB_REF" \ | |
| --predicate-type "https://spdx.dev/Document" | |
| done | |
| release_tmp="$(mktemp -d "$RUNNER_TEMP/base-cli-release.XXXXXX")" | |
| trap 'rm -rf "$release_tmp"' EXIT | |
| release_json="$release_tmp/release.json" | |
| release_error="$release_tmp/release-error.txt" | |
| release_response="$release_tmp/release-response.txt" | |
| read_release_metadata() { | |
| local retry_404="${1:-false}" | |
| local attempt=1 | |
| local status_line | |
| while :; do | |
| if gh api --include "repos/$GITHUB_REPOSITORY/releases/tags/$tag" \ | |
| >"$release_response" 2>"$release_error"; then | |
| awk 'BEGIN { body = 0 } { sub(/\r$/, ""); if (body) print; else if ($0 == "") body = 1 }' \ | |
| "$release_response" >"$release_json" | |
| return 0 | |
| fi | |
| status_line="$(sed -n '1s/\r$//p' "$release_response")" | |
| if [[ "$status_line" =~ ^HTTP/[0-9.]+[[:space:]]404[[:space:]] ]]; then | |
| if [[ "$retry_404" != true || "$attempt" -ge 5 ]]; then | |
| return 1 | |
| fi | |
| sleep $((attempt * 2)) | |
| attempt=$((attempt + 1)) | |
| continue | |
| fi | |
| cat "$release_error" >&2 | |
| return 2 | |
| done | |
| } | |
| verify_existing_release() { | |
| local existing_assets="$release_tmp/existing-assets" | |
| mkdir -p "$existing_assets" | |
| gh release download "$tag" --repo "$GITHUB_REPOSITORY" --dir "$existing_assets" | |
| python scripts/verify_release_assets.py \ | |
| --expected-dir dist \ | |
| --existing-dir "$existing_assets" \ | |
| --release-json "$release_json" \ | |
| --version-file VERSION \ | |
| --tag "$tag" \ | |
| --source-commit "$GITHUB_SHA" \ | |
| --resolved-tag-commit "$tag_commit" | |
| } | |
| if read_release_metadata; then | |
| verify_existing_release | |
| echo "Existing release $tag is byte-for-byte identical; leaving the immutable release unchanged." | |
| exit 0 | |
| else | |
| release_status=$? | |
| if [[ "$release_status" -ne 1 ]]; then | |
| exit "$release_status" | |
| fi | |
| fi | |
| create_status=0 | |
| gh release create "$tag" "${assets[@]}" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --title "$tag" \ | |
| --generate-notes \ | |
| --notes "Published distributions and release metadata for $tag. See CHANGELOG.md for the reviewed release notes." \ | |
| || create_status=$? | |
| if read_release_metadata true; then | |
| verify_existing_release | |
| if [[ "$create_status" -ne 0 ]]; then | |
| echo "gh release create failed with status $create_status; refusing to hide the publication error." >&2 | |
| exit "$create_status" | |
| fi | |
| echo "Verified immutable release $tag after publication." | |
| else | |
| release_status=$? | |
| if [[ "$create_status" -ne 0 ]]; then | |
| exit "$create_status" | |
| fi | |
| echo "Release $tag was created but could not be read back (HTTP 404)." >&2 | |
| exit "$release_status" | |
| fi |