fix(build): strip semver pre-release suffix for cmake project() #7
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: homebrew-release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Tag to (re)build (e.g. v0.1.0)" | |
| required: true | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: homebrew-release-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| prep: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.v.outputs.version }} | |
| tag: ${{ steps.v.outputs.tag }} | |
| stable: ${{ steps.v.outputs.stable }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.tag || github.ref }} | |
| - id: v | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| tag="${{ inputs.tag }}" | |
| else | |
| tag="${GITHUB_REF_NAME}" | |
| fi | |
| [[ "$tag" =~ ^v ]] || { echo "tag must start with 'v', got: $tag" >&2; exit 1; } | |
| version="${tag#v}" | |
| file_v="$(tr -d '[:space:]' < VERSION)" | |
| if [[ "$version" != "$file_v" ]]; then | |
| echo "tag ${tag} does not match VERSION file (${file_v})" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| stable="true" | |
| else | |
| stable="false" | |
| fi | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| echo "stable=$stable" >> "$GITHUB_OUTPUT" | |
| echo "::notice title=Release plan::tag=$tag version=$version stable=$stable" | |
| bottle: | |
| needs: prep | |
| name: bottle ${{ matrix.platform }} | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-latest | |
| platform: x86_64_linux | |
| primary: true | |
| - runner: macos-13 | |
| platform: ventura | |
| primary: false | |
| - runner: macos-14 | |
| platform: arm64_sonoma | |
| primary: false | |
| env: | |
| VERSION: ${{ needs.prep.outputs.version }} | |
| TAG: ${{ needs.prep.outputs.tag }} | |
| ROOT_URL: https://github.com/${{ github.repository }}/releases/download/${{ needs.prep.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.prep.outputs.tag }} | |
| submodules: recursive | |
| fetch-depth: 0 | |
| - name: Set up Homebrew (Linux) | |
| if: runner.os == 'Linux' | |
| uses: Homebrew/actions/setup-homebrew@master | |
| - name: Stage source tarball | |
| id: src | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| name="graft-${VERSION}" | |
| mkdir -p "${RUNNER_TEMP}/src" | |
| out="${RUNNER_TEMP}/src/${name}.tar.gz" | |
| git archive --format=tar --prefix="${name}/" HEAD | gzip > "$out" | |
| sha=$(shasum -a 256 "$out" | awk '{print $1}') | |
| echo "tarball=${out}" >> "$GITHUB_OUTPUT" | |
| echo "sha256=${sha}" >> "$GITHUB_OUTPUT" | |
| echo "asset=${name}.tar.gz" >> "$GITHUB_OUTPUT" | |
| - name: Inject stable section into formula | |
| env: | |
| SRC_TARBALL: ${{ steps.src.outputs.tarball }} | |
| SRC_SHA: ${{ steps.src.outputs.sha256 }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python3 - <<'PY' | |
| import os, re, pathlib | |
| path = pathlib.Path("Formula/graft.rb") | |
| t = path.read_text() | |
| version = os.environ["VERSION"] | |
| src_tarball = os.environ["SRC_TARBALL"] | |
| src_sha = os.environ["SRC_SHA"] | |
| # 1) Drop any existing `livecheck do skip ... end` block. | |
| t = re.sub(r"\n livecheck do\n.*? end\n", "\n", t, count=1, flags=re.S) | |
| # 2) Extract and remove the `head ...` line (if present) so we can reinsert it. | |
| head_match = re.search(r"^ head .*$", t, flags=re.M) | |
| head_line = head_match.group(0) if head_match else None | |
| if head_line: | |
| t = re.sub(r"^ head .*\n", "", t, count=1, flags=re.M) | |
| # 3) Insert stable url + sha256 + version after the `homepage` line. | |
| stable = ( | |
| f' url "file://{src_tarball}"\n' | |
| f' sha256 "{src_sha}"\n' | |
| f' version "{version}"\n' | |
| ) | |
| t = re.sub( | |
| r'(^ homepage\s+"[^"]+"\n)', | |
| rf"\g<1>{stable}", | |
| t, count=1, flags=re.M, | |
| ) | |
| # 4) Reinsert `head` after `license`, preserving canonical order. | |
| if head_line: | |
| t = re.sub( | |
| r'(^ license\s+"[^"]+"\n)', | |
| rf"\g<1>{head_line}\n", | |
| t, count=1, flags=re.M, | |
| ) | |
| path.write_text(t) | |
| print(t) | |
| PY | |
| - name: Build and bottle | |
| shell: bash | |
| run: | | |
| set -euxo pipefail | |
| brew tap-new local/staging --no-git | |
| tap_dir="$(brew --repository local/staging)" | |
| mkdir -p "${tap_dir}/Formula" | |
| cp Formula/graft.rb "${tap_dir}/Formula/graft.rb" | |
| brew install --build-bottle --verbose local/staging/graft | |
| brew bottle --no-rebuild --json --root-url="${ROOT_URL}" local/staging/graft | |
| mkdir -p out | |
| mv ./*.bottle.* out/ | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: bottle-${{ matrix.platform }} | |
| path: out/* | |
| if-no-files-found: error | |
| - uses: actions/upload-artifact@v4 | |
| if: matrix.primary | |
| with: | |
| name: source-tarball | |
| path: ${{ steps.src.outputs.tarball }} | |
| if-no-files-found: error | |
| publish: | |
| needs: [prep, bottle] | |
| if: needs.prep.outputs.stable == 'true' | |
| runs-on: ubuntu-latest | |
| env: | |
| VERSION: ${{ needs.prep.outputs.version }} | |
| TAG: ${{ needs.prep.outputs.tag }} | |
| REPO: ${{ github.repository }} | |
| ROOT_URL: https://github.com/${{ github.repository }}/releases/download/${{ needs.prep.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: master | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| merge-multiple: true | |
| - name: Source asset metadata | |
| id: src | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| src=$(ls dist/graft-*.tar.gz | head -1) | |
| sha=$(shasum -a 256 "$src" | awk '{print $1}') | |
| echo "sha=$sha" >> "$GITHUB_OUTPUT" | |
| echo "name=$(basename "$src")" >> "$GITHUB_OUTPUT" | |
| - name: Create or update GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if ! gh release view "${TAG}" >/dev/null 2>&1; then | |
| gh release create "${TAG}" --title "${TAG}" --generate-notes --verify-tag | |
| fi | |
| gh release upload "${TAG}" dist/*.bottle.* dist/graft-*.tar.gz --clobber | |
| - name: Patch Formula/graft.rb on master | |
| env: | |
| SRC_NAME: ${{ steps.src.outputs.name }} | |
| SRC_SHA: ${{ steps.src.outputs.sha }} | |
| shell: bash | |
| run: | | |
| set -euxo pipefail | |
| python3 - <<'PY' | |
| import os, re, json, glob, pathlib | |
| version = os.environ["VERSION"] | |
| root_url = os.environ["ROOT_URL"] | |
| src_name = os.environ["SRC_NAME"] | |
| src_sha = os.environ["SRC_SHA"] | |
| # Collect bottle SHAs and cellar info from JSON sidecars. | |
| bottles = [] | |
| for jp in sorted(glob.glob("dist/*.bottle.json")): | |
| data = json.loads(pathlib.Path(jp).read_text()) | |
| ((_, payload),) = data.items() | |
| for os_tag, info in payload["bottle"]["tags"].items(): | |
| bottles.append((os_tag, info.get("cellar", ":any"), info["sha256"])) | |
| if not bottles: | |
| raise SystemExit("no bottle JSON files in dist/") | |
| lines = [ | |
| " bottle do", | |
| f' root_url "{root_url}"', | |
| " rebuild 0", | |
| ] | |
| for os_tag, cellar, sha in bottles: | |
| cellar_lit = cellar if cellar.startswith(":") else f'"{cellar}"' | |
| lines.append(f' sha256 cellar: {cellar_lit}, {os_tag}: "{sha}"') | |
| lines.append(" end") | |
| new_bottle = "\n".join(lines) | |
| path = pathlib.Path("Formula/graft.rb") | |
| t = path.read_text() | |
| # Strip any previous livecheck skip, bottle block, or stable url/sha/version. | |
| t = re.sub(r"\n livecheck do\n.*? end\n", "\n", t, count=1, flags=re.S) | |
| t = re.sub(r"\n bottle do\n.*? end\n", "\n", t, count=1, flags=re.S) | |
| t = re.sub(r'^ url\s+"[^"]+"\n', "", t, count=1, flags=re.M) | |
| t = re.sub(r'^ sha256\s+"[0-9a-f]+"\n', "", t, count=1, flags=re.M) | |
| t = re.sub(r'^ version\s+"[^"]+"\n', "", t, count=1, flags=re.M) | |
| head_match = re.search(r"^ head .*$", t, flags=re.M) | |
| head_line = head_match.group(0) if head_match else None | |
| if head_line: | |
| t = re.sub(r"^ head .*\n", "", t, count=1, flags=re.M) | |
| stable_block = ( | |
| f' url "{root_url}/{src_name}"\n' | |
| f' sha256 "{src_sha}"\n' | |
| f' version "{version}"\n' | |
| ) | |
| t = re.sub(r'(^ homepage\s+"[^"]+"\n)', rf"\g<1>{stable_block}", t, count=1, flags=re.M) | |
| if head_line: | |
| t = re.sub(r'(^ license\s+"[^"]+"\n)', rf"\g<1>{head_line}\n", t, count=1, flags=re.M) | |
| # Insert bottle block right before the first `depends_on` line. | |
| t = re.sub( | |
| r"(^ depends_on )", | |
| new_bottle + "\n\n\\1", | |
| t, count=1, flags=re.M, | |
| ) | |
| path.write_text(t) | |
| print(t) | |
| PY | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Formula/graft.rb | |
| if git diff --cached --quiet; then | |
| echo "no formula changes to commit" | |
| else | |
| git commit -m "chore(release): publish ${TAG} bottles and stable url [skip ci]" | |
| git push origin master | |
| fi | |
| - name: Summary | |
| shell: bash | |
| run: | | |
| { | |
| echo "## Released ${TAG}" | |
| echo | |
| echo "- Release: https://github.com/${REPO}/releases/tag/${TAG}" | |
| echo "- Bottles attached for arm64_sonoma, ventura, x86_64_linux" | |
| echo "- Source tarball attached: \`${{ steps.src.outputs.name }}\`" | |
| echo "- Formula on master updated with stable url, sha256, and bottle block" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| summarize-prerelease: | |
| needs: [prep, bottle] | |
| if: needs.prep.outputs.stable != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| merge-multiple: true | |
| - shell: bash | |
| run: | | |
| set -euo pipefail | |
| ls -la dist/ | |
| { | |
| echo "## Pre-release validated: ${{ needs.prep.outputs.tag }}" | |
| echo | |
| echo "Bottle build succeeded on all platforms. Artifacts are attached to this workflow run for inspection." | |
| echo | |
| echo "Pre-release versions (matching \`x.y.z-suffix\`) do **not** publish a GitHub Release and do **not** modify \`Formula/graft.rb\` on master." | |
| echo | |
| echo "To cut a stable release, tag a version of the form \`vX.Y.Z\` (no suffix). The workflow will then create the GitHub Release and patch the formula." | |
| } >> "$GITHUB_STEP_SUMMARY" |