chore: bump version to 2.5.24 (#1448) #196
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 | |
| # An incomplete release must be retried when its release machinery is | |
| # repaired without forcing another public version bump. | |
| - .github/workflows/release-auto.yml | |
| - .github/workflows/template_native_build.yml | |
| workflow_dispatch: | |
| inputs: | |
| publish: | |
| description: "Publish GitHub Release and PyPI after building" | |
| required: false | |
| type: boolean | |
| default: false | |
| 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 | |
| timeout-minutes: 10 | |
| 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 | |
| env: | |
| PUBLISH_REQUESTED: ${{ inputs.publish }} | |
| 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 --connect-timeout 10 --max-time 30 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)" | |
| # One wheel per PLATFORMS entry in ci/publish.py — derive the | |
| # expected count from the SOT instead of hardcoding it, so | |
| # matrix/lane changes can't silently break this gate | |
| # (a stale hardcoded 4 bit the 2.3.22-2.3.24 window). | |
| expected_wheels="$(python -c 'import ci.publish; print(len(ci.publish.PLATFORMS))')" | |
| if [ "${GITHUB_EVENT_NAME}" != "workflow_dispatch" ] \ | |
| && [ "$tag_exists" != "true" ] \ | |
| && [ "$newest" = "$cargo_version" ] \ | |
| && [ "$pypi_file_count" -lt "$expected_wheels" ]; then | |
| should_build="true" | |
| should_publish_github="true" | |
| should_publish_pypi="true" | |
| fi | |
| # Manual dispatch is always at least a build. Publishing is an | |
| # explicit opt-in checkbox so release dry-runs are the default. | |
| if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then | |
| should_build="true" | |
| release_ref="${commit_sha}" | |
| should_publish_github="false" | |
| should_publish_pypi="false" | |
| if [ "${PUBLISH_REQUESTED:-false}" = "true" ]; then | |
| should_publish_github="true" | |
| should_publish_pypi="true" | |
| if [ "$tag_exists" = "true" ]; then | |
| release_ref="${version}" | |
| fi | |
| fi | |
| 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 "Publish requested: ${PUBLISH_REQUESTED:-false}" | |
| 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 | |
| # macOS lanes were restored once setup-soldr could install the | |
| # published soldr v0.8.8 release. They were dropped when every usable soldr | |
| # version still pointed the Apple SDK fetch at the retired | |
| # manifest branch (HTTP 404); the "setup-soldr version | |
| # --json incompatibility" that blocked bumping soldr was a | |
| # misdiagnosis — soldr v0.7.85/v0.7.87 shipped silent | |
| # binaries. v0.8.0 is the known-good floor for the Apple | |
| # SDK URL fix. The action reference may follow v0 updates, while | |
| # every fbuild workflow pins the binary version explicitly. | |
| # | |
| # aarch64-apple-darwin cross-compiles from the Linux | |
| # runner via soldr's blessed cross path + its managed | |
| # Apple SDK — no mac-host dependency in the matrix. | |
| - target: aarch64-apple-darwin | |
| runner: ubuntu-latest | |
| binary_ext: "" | |
| linux_cross: false | |
| macos_cross: false | |
| mac_cross_linux: true | |
| # x86_64-apple-darwin shares the mac_cross_linux template | |
| # branch — soldr's apple_sdk fetcher auto-picks | |
| # `darwin-x86_64` (thin-x86_64) when the target triple is | |
| # `x86_64-apple-darwin`, and `soldr build --target | |
| # x86_64-apple-darwin` resolves the SDKROOT + frameworks | |
| # identically to the arm64 lane. Closes Lane 3 of soldr#1006. | |
| - target: x86_64-apple-darwin | |
| runner: ubuntu-latest | |
| binary_ext: "" | |
| linux_cross: false | |
| macos_cross: false | |
| mac_cross_linux: true | |
| # Windows x86_64 now uses the same Linux -> MSVC soldr build path | |
| # as aarch64-pc-windows-msvc. The native windows-latest release | |
| # lane repeatedly exceeded the 45 minute release-binary step while | |
| # compiling fbuild-cli, blocking the v2.4.0 publish. soldr v0.8.0's | |
| # clang-cl shim fixes the earlier cargo-xwin/ring header routing | |
| # issue that forced native Windows on the 2.3.12 cut. | |
| - target: x86_64-pc-windows-msvc | |
| runner: ubuntu-latest | |
| binary_ext: ".exe" | |
| linux_cross: false | |
| macos_cross: false | |
| # ARM64 uses the same Linux -> MSVC soldr build path as x86_64. | |
| # Keep this release lane: soldr's clang shim routes ring + cc-rs | |
| # through clang-cl for the MSVC target while its internal xwin path | |
| # supplies the Windows SDK. | |
| - target: aarch64-pc-windows-msvc | |
| runner: ubuntu-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 }} | |
| mac_cross_linux: ${{ matrix.mac_cross_linux == true }} | |
| 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 | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release_ref }} | |
| - name: Download native artifacts | |
| uses: actions/download-artifact@v8 | |
| 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@v4 | |
| 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 | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release_ref }} | |
| - name: Download native artifacts | |
| uses: actions/download-artifact@v8 | |
| 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 | |
| timeout-minutes: 5 | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python -m venv .venv | |
| . .venv/bin/activate | |
| python -m pip install --upgrade --timeout 60 pip | |
| python -m pip install --timeout 60 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 | |
| timeout-minutes: 20 | |
| environment: pypi | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Download PyPI distributions | |
| uses: actions/download-artifact@v8 | |
| 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 | |
| - name: Verify all wheels visible on PyPI | |
| shell: bash | |
| env: | |
| PYPI_VERSION: ${{ needs.prepare.outputs.project_version }} | |
| run: | | |
| set -euo pipefail | |
| # Derive the expected count from the wheels this run actually | |
| # built (downloaded into dist/ above) instead of hardcoding | |
| # it — a stale hardcoded 4 broke this gate when the release | |
| # matrix changed (2.3.22-2.3.24 window). | |
| expected="$(find dist -name '*.whl' | wc -l)" | |
| if [ "$expected" -lt 1 ]; then | |
| echo "No wheels found in dist/ — nothing to verify" >&2 | |
| exit 1 | |
| fi | |
| deadline=$(( $(date +%s) + 300 )) | |
| while :; do | |
| count="$(curl -fsSL --connect-timeout 5 --max-time 10 "https://pypi.org/pypi/fbuild/${PYPI_VERSION}/json" \ | |
| | python -c 'import json,sys; print(len(json.load(sys.stdin).get("urls",[])))' 2>/dev/null \ | |
| || echo 0)" | |
| echo "PyPI reports ${count}/${expected} files for fbuild ${PYPI_VERSION}" | |
| if [ "$count" -ge "$expected" ]; then | |
| echo "All wheels visible on PyPI" | |
| exit 0 | |
| fi | |
| if [ "$(date +%s)" -ge "$deadline" ]; then | |
| echo "Timed out waiting for ${expected} wheels on PyPI (saw ${count})" >&2 | |
| exit 1 | |
| fi | |
| sleep 15 | |
| done |