A GitHub Action that synchronizes a Homebrew formula with a GitHub release.
🚧 This is still in development, and not ready for general use.
Add this action to a workflow to update a corresponding Homebrew formula in your tap repository whenever you create a new GitHub release. Your Homebrew formula will be updated with the tag and revision of the release. Any assets associated with the release whose name matches the expected pattern will be added as pre-built binary artifacts, or bottles.
To better understand what this action does and why it's useful, consider the following scenario:
Mona has a project mona/hello
with source code
for building an executable named hello
.
To make it easier to install,
Mona provides the following Homebrew formula
in a tap
that she hosts in a repository named mona/homebrew-formulae
:
class Hello < Formula
desc "👋"
homepage "https://github.com/mona/hello"
url "https://github.com/mona/hello.git", tag: "1.0.0", revision: "d95b2990f6186523cda25cea4f9d45bc1fde069f"
depends_on xcode: ["12.0", :build]
def install
system "make", "install", "prefix=#{prefix}"
end
test do
system bin/"hello"
end
end
This allows anyone with Homebrew installed
to install the hello
command with a single command:
$ brew install mona/formulae/hello
However, this convenience for the user comes at a cost to Mona
(beyond the fixed cost of creating a formula in the first place).
Whenever Mona wants to release a new version of hello
,
she must do the following:
- Create and push a new tag
- Create a new release on GitHub
- Build and upload a pre-built binary for the release
- Calculate the SHA256 checksum for the binary
- Update the Homebrew formula with the new version, tag, revision, and asset checksums
If she forgets to do all of these steps
(or makes a mistake),
her users won't get the latest version when they install hello
.
This action automates the manual, error-prone process described above, streamlining the release of any tool you distribute via your own Homebrew tap.
Let's say Mona tags a new 1.0.1
version
after setting up a workflow like the one described below.
When update-homebrew-formula-action
runs,
it updates the formula with a new tag and revision:
class Hello < Formula
desc "👋"
homepage "https://github.com/mona/hello"
- version "1.0.0"
+ version "1.0.1"
- url "https://github.com/mona/hello.git", tag: "1.0.0", revision: "d95b2990f6186523cda25cea4f9d45bc1fde069f"
+ url "https://github.com/mona/hello.git", tag: "1.0.1", revision: "5aa05bf843ef74f6c3e5ed6d504d6f305e0945d1"
This action requires the GH_PERSONAL_ACCESS_TOKEN
environment variable
to be set with a Github personal access token
that has "repo" authorization for the repository containing
the formula you want to update.
Important: GitHub automatically creates a
GITHUB_TOKEN
secret to use in your workflow, but it lacks the permissions necessary to run this action successfully. For more information, see "Authentication in a workflow".
To generate one, navigate to the Personal access tokens page in your GitHub account settings (Settings > Developer settings > Personal access tokens) and click the "Generate a new token" button.
In the "New personal access token" form, provide a descriptive comment in the "Note" field, like "Wiki Management". Under "Select scopes", enable all of the entries under "repo" perms.
Next, click the "Generate token" button at the bottom of the form.
Finally,
copy your generated personal access token to your clipboard
and navigate to your settings page for your repository or organization.
Navigate to the "Secrets" page,
click "Add a new secret",
and fill in the form by
entering GH_PERSONAL_ACCESS_TOKEN
into the "Name" field and
pasting your token into the "Value" field.
Note: You can use any name for this secret, so long as it's passed to this action as
GH_PERSONAL_ACCESS_TOKEN
.
repository
: Required. The project repository (e.g. mona/hello).tap
: Required. The Homebrew tap repository (e.g. mona/homebrew-formulae).formula
: Required. The path to the formula in the tap repository (e.g. Formula/hello.rb).message
: Optional. The message of the commit updating the formula. (e.g. "Update hello to 1.0.1")
We recommend running this action as part of a workflow that triggers on
release events
with the created
activity type.
This way, any release that's created —
whether manually or programmatically
(such as with actions/create-release) —
will benefit from the same automation.
# .github/workflows/release.yml
name: Release
on:
release:
types:
- created
jobs:
formula:
name: Update Homebrew formula
runs-on: ubuntu-latest
steps:
- name: Update the Homebrew formula with latest release
uses: NSHipster/update-homebrew-formula-action@main
with:
repository: mona/hello
tap: mona/homebrew-formulae
formula: Formula/hello.rb
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
For extra credit, you can extend your workflow to create a release for the new tag and build bottles once the formula is updated. It's a bit involved, but your users will appreciate your going the extra mile.
Here's the order of operations:
- Update the formula for the latest release, like before
- Once that's finished, use Homebrew to build a bottle using the updated formula.
- Upload the bottle to the release on GitHub
- Update the formula again, this time to add the bottle
# .github/workflows/release.yml
name: Release
on:
release:
types:
- created
jobs:
update_formula_version:
name: Update the Homebrew formula with latest release
runs-on: ubuntu-latest
steps:
- uses: NSHipster/update-homebrew-formula-action@main
with:
repository: mona/hello
tap: mona/homebrew-formulae
formula: Formula/hello.rb
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
upload_bottle:
name: Build and distribute Homebrew bottle for macOS Catalina
runs-on: macos-10.15
needs: [update_formula_version]
steps:
- name: Build a bottle using Homebrew
run: |
brew tap mona/formulae
brew install --build-bottle --verbose hello
brew bottle hello
- name: Upload the bottle to the GitHub release
uses: actions/[email protected]
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./hello--${{ github.event.release.tag_name }}.catalina.bottle.tar.gz
asset_name: hello-${{ github.event.release.tag_name }}.catalina.bottle.tar.gz
asset_content_type: application/gzip
update_formula_bottle:
name: Update the Homebrew formula again with bottle
runs-on: ubuntu-latest
needs: [upload_bottle]
steps:
- uses: NSHipster/update-homebrew-formula-action@main
with:
repository: mona/hello
tap: mona/homebrew-formulae
formula: Formula/hello.rb
message: |
Add bottle for hello ${{ github.event.release.tag_name }}
on macOS Catalina
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
Note: Homebrew generates bottles with a double dash (
--
) in the resulting filename, but expects a single dash (-
) when downloading the bottle.
After running this workflow,
your formula will have a bottle
declaration like the following:
+ bottle do
+ root_url "https://github.com/mona/hello/releases/download/1.0.1"
+ cellar :any
+ sha256 "d7493440a64c3a11fac793fb0f28a21e6974e1f430fe246d603496b61a565ae9" => :catalina
+ end
For a real-world example of this action in use, check out the release infrastructure for swift-doc.
MIT