-
Notifications
You must be signed in to change notification settings - Fork 0
fix: add homebrew #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+258
−0
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| # Copyright AI-Catalog Contributors (https://github.com/Agent-Card/ai-catalog-cli) | ||
| # Copyright AGNTCY Contributors (https://github.com/agntcy) | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| --- | ||
| name: homebrew-formula | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| tag: | ||
| description: Release tag to render into Formula/ai-catalog.rb | ||
| required: true | ||
| type: string | ||
| workflow_dispatch: | ||
| inputs: | ||
| tag: | ||
| description: Release tag to render into Formula/ai-catalog.rb | ||
| required: true | ||
| type: string | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ inputs.tag }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| refresh-formula: | ||
| name: Refresh Homebrew formula | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| ref: main | ||
|
|
||
| - name: Setup Python | ||
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - name: Render Homebrew formula | ||
| env: | ||
| TAG: ${{ inputs.tag }} | ||
| run: python3 scripts/render_homebrew_formula.py --tag "$TAG" | ||
|
|
||
| - name: Open formula refresh PR | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| TAG: ${{ inputs.tag }} | ||
| FORMULA: Formula/ai-catalog.rb | ||
| run: | | ||
| if [ -z "$(git status --porcelain -- "$FORMULA")" ]; then | ||
| echo "Formula already up to date for $TAG" | ||
| exit 0 | ||
| fi | ||
|
|
||
| branch="automation/homebrew-${TAG#v}" | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git checkout -B "$branch" | ||
| git add "$FORMULA" | ||
| git commit -s -m "release: update Homebrew formula for $TAG" | ||
| git push --force --set-upstream origin "$branch" | ||
|
|
||
| open_pr=$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$branch" \ | ||
| --base main --state open --json number --jq 'first(.[].number) // ""') | ||
| if [ -n "$open_pr" ]; then | ||
| echo "Updated existing PR #$open_pr" >> "$GITHUB_STEP_SUMMARY" | ||
| exit 0 | ||
| fi | ||
|
|
||
| if gh pr create --repo "$GITHUB_REPOSITORY" --base main --head "$branch" \ | ||
| --title "release: update Homebrew formula for $TAG" \ | ||
| --body "Repoints \`$FORMULA\` at the $TAG archives and their published SHA256 digests." | ||
| then | ||
| exit 0 | ||
| fi | ||
|
|
||
| # GITHUB_TOKEN cannot open pull requests unless the organization | ||
| # allows it, so leave the branch and a compare link behind instead. | ||
| { | ||
| echo "### Homebrew formula for $TAG" | ||
| echo | ||
| echo "Pushed \`$branch\`, but could not open the PR. Open it here:" | ||
| echo | ||
| echo "https://github.com/$GITHUB_REPOSITORY/compare/main...$branch?expand=1" | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| #!/usr/bin/env python3 | ||
| # Copyright AI-Catalog Contributors (https://github.com/Agent-Card/ai-catalog-cli) | ||
| # Copyright AGNTCY Contributors (https://github.com/agntcy) | ||
|
arpad-csepi marked this conversation as resolved.
|
||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import hashlib | ||
| import re | ||
| import textwrap | ||
| import tomllib | ||
| import urllib.request | ||
| from pathlib import Path | ||
|
|
||
| ROOT = Path(__file__).resolve().parent.parent | ||
| MANIFEST = ROOT / "Cargo.toml" | ||
| DEFAULT_OUTPUT = ROOT / "Formula" / "ai-catalog.rb" | ||
|
arpad-csepi marked this conversation as resolved.
|
||
|
|
||
| TAG_PATTERN = re.compile(r"^v(?P<version>[0-9A-Za-z.+-]+)$") | ||
| BINARY_NAME = "ai-catalog" | ||
| CLASS_NAME = "AiCatalog" | ||
| USER_AGENT = "ai-catalog-cli-release-automation" | ||
|
|
||
| # Homebrew on_<os>/on_<cpu> block to release archive name. The gnu Linux | ||
| # archives are used rather than musl: Homebrew targets glibc distributions. | ||
| ARCHIVES = { | ||
| "macos": {"arm": "darwin-arm64", "intel": "darwin-amd64"}, | ||
| "linux": {"arm": "linux-arm64-gnu", "intel": "linux-amd64-gnu"}, | ||
| } | ||
|
|
||
| HEADER = textwrap.dedent( | ||
| """\ | ||
| # Copyright AI-Catalog Contributors (https://github.com/Agent-Card/ai-catalog-cli) | ||
| # Copyright AGNTCY Contributors (https://github.com/agntcy) | ||
|
arpad-csepi marked this conversation as resolved.
|
||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """ | ||
| ) | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser( | ||
| description="Render the Homebrew formula for a released CLI tag." | ||
| ) | ||
| parser.add_argument("--tag", required=True, help="Release tag, e.g. v0.2.2") | ||
| parser.add_argument( | ||
| "--output", | ||
| type=Path, | ||
| default=DEFAULT_OUTPUT, | ||
| help=f"Formula output path (default: {DEFAULT_OUTPUT}).", | ||
| ) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def fetch(url: str) -> urllib.request.addinfourl: | ||
| request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) | ||
| return urllib.request.urlopen(request, timeout=60) | ||
|
|
||
|
|
||
| def hash_archive(url: str) -> str: | ||
| digest = hashlib.sha256() | ||
| with fetch(url) as response: | ||
| while chunk := response.read(1024 * 1024): | ||
| digest.update(chunk) | ||
| return digest.hexdigest() | ||
|
|
||
|
|
||
| def archive_sha256(base_url: str, archive: str) -> str: | ||
| """Prefer the digest the release published over re-hashing the archive.""" | ||
| try: | ||
| with fetch(f"{base_url}/{BINARY_NAME}-{archive}.sha256") as response: | ||
| fields = response.read().decode("utf-8").split() | ||
| if fields: | ||
| return fields[0] | ||
|
arpad-csepi marked this conversation as resolved.
|
||
| except OSError: | ||
| pass | ||
| return hash_archive(f"{base_url}/{BINARY_NAME}-{archive}.tar.gz") | ||
|
|
||
|
|
||
| def ruby_string(value: str) -> str: | ||
| return value.replace("\\", "\\\\").replace('"', '\\"') | ||
|
|
||
|
|
||
| def arch_blocks(base_url: str, targets: dict[str, str]) -> str: | ||
| blocks = [] | ||
| for cpu, archive in targets.items(): | ||
| url = f"{base_url}/{BINARY_NAME}-{archive}.tar.gz" | ||
|
arpad-csepi marked this conversation as resolved.
|
||
| blocks.append( | ||
| f" on_{cpu} do\n" | ||
| f' url "{ruby_string(url)}"\n' | ||
| f' sha256 "{archive_sha256(base_url, archive)}"\n' | ||
| f" end" | ||
| ) | ||
| return "\n\n".join(blocks) | ||
|
|
||
|
|
||
| def render_formula(tag: str) -> str: | ||
| match = TAG_PATTERN.match(tag) | ||
| if not match: | ||
| raise SystemExit(f"expected {TAG_PATTERN.pattern} tag, got: {tag}") | ||
| version = match.group("version") | ||
|
|
||
| with MANIFEST.open("rb") as handle: | ||
| package = tomllib.load(handle)["package"] | ||
|
|
||
| homepage = package["repository"].rstrip("/") | ||
| base_url = f"{homepage}/releases/download/{tag}" | ||
| git_url = homepage if homepage.endswith(".git") else f"{homepage}.git" | ||
|
|
||
| body = textwrap.dedent( | ||
| f"""\ | ||
| class {CLASS_NAME} < Formula | ||
| desc "{ruby_string(package["description"])}" | ||
| homepage "{ruby_string(homepage)}" | ||
| version "{version}" | ||
| license "{ruby_string(package["license"])}" | ||
| head "{ruby_string(git_url)}", branch: "main" | ||
|
|
||
| __MACOS__ | ||
|
|
||
| __LINUX__ | ||
|
|
||
| def install | ||
| bin.install "{BINARY_NAME}" | ||
| end | ||
|
|
||
| test do | ||
| assert_match "{BINARY_NAME}", shell_output("#{{bin}}/{BINARY_NAME} --help") | ||
| end | ||
| end | ||
| """ | ||
| ) | ||
|
|
||
| for placeholder, os_name in (("__MACOS__", "macos"), ("__LINUX__", "linux")): | ||
| blocks = arch_blocks(base_url, ARCHIVES[os_name]) | ||
| body = body.replace(placeholder, f" on_{os_name} do\n{blocks}\n end") | ||
|
|
||
| return f"{HEADER}{body}" | ||
|
|
||
|
|
||
| def main() -> int: | ||
| args = parse_args() | ||
| args.output.parent.mkdir(parents=True, exist_ok=True) | ||
| args.output.write_text(render_formula(args.tag), encoding="utf-8") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.