test: cover mtime-stable watch fingerprints #5
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: Autonomous Release | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - Cargo.toml | |
| - pyproject.toml | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| attestations: write | |
| id-token: write | |
| concurrency: | |
| group: release-auto-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUSTFLAGS: "-D warnings" | |
| jobs: | |
| prepare: | |
| name: Prepare release | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| should_build: ${{ steps.prepare.outputs.should_build }} | |
| should_publish_github: ${{ steps.prepare.outputs.should_publish_github }} | |
| should_publish_pypi: ${{ steps.prepare.outputs.should_publish_pypi }} | |
| version: ${{ steps.prepare.outputs.version }} | |
| project_version: ${{ steps.prepare.outputs.project_version }} | |
| commit_sha: ${{ steps.prepare.outputs.commit_sha }} | |
| release_ref: ${{ steps.prepare.outputs.release_ref }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Check release eligibility | |
| id: prepare | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cargo_version="$( | |
| python - <<'PY' | |
| import tomllib | |
| with open("Cargo.toml", "rb") as f: | |
| print(tomllib.load(f)["workspace"]["package"]["version"]) | |
| PY | |
| )" | |
| pyproject_version="$( | |
| python - <<'PY' | |
| import tomllib | |
| with open("pyproject.toml", "rb") as f: | |
| print(tomllib.load(f)["project"]["version"]) | |
| PY | |
| )" | |
| if [ "$cargo_version" != "$pyproject_version" ]; then | |
| echo "Cargo.toml version ($cargo_version) does not match pyproject.toml version ($pyproject_version)" >&2 | |
| exit 1 | |
| fi | |
| version="v${cargo_version}" | |
| commit_sha="$(git rev-parse HEAD)" | |
| release_ref="${commit_sha}" | |
| tag_exists="false" | |
| tag_sha="" | |
| if tag_sha="$(git ls-remote --exit-code --tags origin "refs/tags/${version}" | awk '{print $1}' | tail -n1)"; then | |
| tag_exists="true" | |
| release_ref="${version}" | |
| fi | |
| pypi_json="$(mktemp)" | |
| if curl -fsSL https://pypi.org/pypi/fbuild/json -o "$pypi_json"; then | |
| latest="$(python -c 'import json,sys; print(json.load(open(sys.argv[1]))["info"]["version"])' "$pypi_json")" | |
| pypi_file_count="$(python -c 'import json,sys; data=json.load(open(sys.argv[1])); print(len(data.get("releases", {}).get(sys.argv[2], [])))' "$pypi_json" "$cargo_version")" | |
| else | |
| latest="0.0.0" | |
| pypi_file_count="0" | |
| fi | |
| should_build="false" | |
| should_publish_github="false" | |
| should_publish_pypi="false" | |
| newest="$(printf '%s\n%s\n' "$latest" "$cargo_version" | sort -V | tail -n1)" | |
| if [ "$tag_exists" != "true" ] && [ "$newest" = "$cargo_version" ] && [ "$pypi_file_count" -lt 4 ]; then | |
| should_build="true" | |
| should_publish_github="true" | |
| should_publish_pypi="true" | |
| elif [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "$tag_exists" = "true" ] && [ "$pypi_file_count" -lt 4 ]; then | |
| should_build="true" | |
| should_publish_pypi="true" | |
| fi | |
| { | |
| echo "version=${version}" | |
| echo "project_version=${cargo_version}" | |
| echo "commit_sha=${commit_sha}" | |
| echo "release_ref=${release_ref}" | |
| echo "should_build=${should_build}" | |
| echo "should_publish_github=${should_publish_github}" | |
| echo "should_publish_pypi=${should_publish_pypi}" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Candidate version: ${version}" | |
| echo "Latest PyPI version: ${latest}" | |
| echo "PyPI files for ${cargo_version}: ${pypi_file_count}" | |
| echo "Tag exists: ${tag_exists}" | |
| echo "Tag SHA: ${tag_sha:-<none>}" | |
| echo "Release ref: ${release_ref}" | |
| echo "Should build: ${should_build}" | |
| echo "Should publish GitHub release: ${should_publish_github}" | |
| echo "Should publish PyPI: ${should_publish_pypi}" | |
| build: | |
| name: Native build (${{ matrix.target }}) | |
| needs: prepare | |
| if: needs.prepare.outputs.should_build == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-musl | |
| runner: ubuntu-latest | |
| binary_ext: "" | |
| linux_cross: false | |
| macos_cross: false | |
| - target: aarch64-unknown-linux-musl | |
| runner: ubuntu-latest | |
| binary_ext: "" | |
| linux_cross: true | |
| macos_cross: false | |
| - target: aarch64-apple-darwin | |
| runner: macos-latest | |
| binary_ext: "" | |
| linux_cross: false | |
| macos_cross: false | |
| - target: x86_64-pc-windows-msvc | |
| runner: windows-latest | |
| binary_ext: ".exe" | |
| linux_cross: false | |
| macos_cross: false | |
| uses: ./.github/workflows/template_native_build.yml | |
| with: | |
| target: ${{ matrix.target }} | |
| runner: ${{ matrix.runner }} | |
| binary_ext: ${{ matrix.binary_ext }} | |
| linux_cross: ${{ matrix.linux_cross }} | |
| macos_cross: ${{ matrix.macos_cross }} | |
| ref: ${{ needs.prepare.outputs.release_ref }} | |
| publish: | |
| name: Publish GitHub release | |
| needs: [prepare, build] | |
| if: needs.prepare.outputs.should_publish_github == 'true' | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release_ref }} | |
| - name: Download native artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: binaries-* | |
| path: artifacts | |
| - name: Package release archives | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version="${{ needs.prepare.outputs.version }}" | |
| mkdir -p dist pkg | |
| for artifact_dir in artifacts/binaries-*; do | |
| [ -d "$artifact_dir" ] || continue | |
| target="${artifact_dir#artifacts/binaries-}" | |
| package_name="fbuild-${version}-${target}" | |
| package_dir="pkg/${package_name}" | |
| rm -rf "$package_dir" | |
| mkdir -p "$package_dir" | |
| cp -a "$artifact_dir"/. "$package_dir"/ | |
| if [[ "$target" == *windows* ]]; then | |
| (cd pkg && zip -r "../dist/${package_name}.zip" "$package_name") | |
| else | |
| tar -C pkg -czf "dist/${package_name}.tar.gz" "$package_name" | |
| fi | |
| done | |
| cd dist | |
| sha256sum fbuild-* > "fbuild-${version}-SHA256SUMS.txt" | |
| - name: Attest release artifacts | |
| uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3 | |
| with: | |
| subject-checksums: dist/fbuild-${{ needs.prepare.outputs.version }}-SHA256SUMS.txt | |
| - name: Create draft release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version="${{ needs.prepare.outputs.version }}" | |
| gh release create "$version" dist/* \ | |
| --draft \ | |
| --generate-notes \ | |
| --target "${{ needs.prepare.outputs.release_ref }}" \ | |
| --title "$version" | |
| - name: Publish release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: gh release edit "${{ needs.prepare.outputs.version }}" --draft=false | |
| build-pypi: | |
| name: Build PyPI wheels | |
| needs: [prepare, build] | |
| if: needs.prepare.outputs.should_publish_pypi == 'true' | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release_ref }} | |
| - name: Download native artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: binaries-* | |
| path: dist/_tmp | |
| - name: Build wheels | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python - <<'PY' | |
| import shutil | |
| import ci.publish as publish | |
| tmp = publish.DIST_DIR / "_tmp" | |
| missing = [] | |
| for artifact_name, subdir in publish.ARTIFACT_MAP.items(): | |
| src = tmp / artifact_name | |
| if not src.exists(): | |
| missing.append(artifact_name) | |
| continue | |
| dest = publish.DIST_DIR / subdir | |
| if dest.exists(): | |
| shutil.rmtree(dest) | |
| dest.mkdir(parents=True) | |
| for path in src.iterdir(): | |
| if path.is_file(): | |
| target = dest / path.name | |
| shutil.copy2(path, target) | |
| if not path.name.endswith(".exe"): | |
| target.chmod(0o755) | |
| if missing: | |
| raise SystemExit(f"Missing native artifacts: {', '.join(missing)}") | |
| meta = publish.read_project_meta() | |
| wheels = publish.build_all_wheels(*meta) | |
| print(f"Built {len(wheels)} wheel(s)") | |
| PY | |
| - name: Smoke test Linux x86_64 wheel | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python -m venv .venv | |
| . .venv/bin/activate | |
| python -m pip install --upgrade pip | |
| python -m pip install dist/wheels/*manylinux_2_17_x86_64*.whl | |
| fbuild --version | |
| python -c "import fbuild; print(fbuild.__version__)" | |
| - name: Upload PyPI distributions | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: pypi-fbuild | |
| path: dist/wheels/*.whl | |
| if-no-files-found: error | |
| publish-pypi: | |
| name: Publish PyPI | |
| needs: [prepare, build-pypi] | |
| if: needs.prepare.outputs.should_publish_pypi == 'true' | |
| runs-on: ubuntu-24.04 | |
| environment: pypi | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Download PyPI distributions | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: pypi-fbuild | |
| path: dist | |
| - name: List distributions | |
| shell: bash | |
| run: ls -lh dist | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 | |
| with: | |
| packages-dir: dist | |
| attestations: true | |
| skip-existing: true |