DEV Community

Billy Hadlow
Billy Hadlow

Posted on • Edited on

How to release to Homebrew with GoReleaser, GitHub Actions and Semantic Release

This article will guide you through the process of automating your Go project releases with GoReleaser and GitHub Actions. Tags will be generated by Semantic Release. All of this will be tied together so any merges to main will automatically deploy with an auto-generated changelog, and can then be installed via a Homebrew Tap.

1. Setup GitHub repos

The first thing you need is a second Github repository to store the Homebrew Formula. The name of this must start with homebrew- (this is explained more here). I'd recommend a format that is your current project name and just prepending homebrew- to it.

For example, I have github.com/hadlow/gopublish and github.com/hadlow/homebrew-gopublish.

2. Create access tokens

Next thing is to setup is your GitHub personal access token. This is used to allow Semantic Release and GoReleaser to create tags, push file changes etc.

To create a token, go to GitHub and click on your profile picture, in the top right. Then go to Settings and then Developer Settings on the left. Then click Personal access tokens and Fine-grained tokens, then Generate new token.

Personal access tokens sidebar screenshot

Name your token something descriptive. I've gone with GH_TOKEN_GO_PUBLISH. Now scroll down and give it access to both the main repository and the homebrew- repository.

For the permissions, I use the following:

  • Actions: Read and write
  • Commit statuses: Read and write
  • Contents: Read and write
  • Deployments: Read and write
  • Environments: Read-only
  • Secrets: Read-only
  • Variables: Read-only

You can now go ahead and create the token. Copy this token as we'll need it in the next step.

 3. Add access token to environment

Go to your repository settings and go to Environments. Create a new environment called 'Release' and add an environment secret. Call it GH_TOKEN and paste in your access token created in the previous step.

 4. Add Semantic Release

Semantic-release will automatically generate the Git version tags for our releases. If you use conventional commits, then tags will follow semantic versioning standards. Tags will be generated whenever we commit to our main branch.

Before adding Semantic release, we'll need a GitHub Actions file. Create a file named release.yml and put it in your main repository in the .github/workflows directory. Paste in the following:

name: release

on:
  push:
    branches:
      - main

jobs:
  main:
    runs-on: ubuntu-latest
    environment:
      name: Release
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: Semantic Release
        uses: cycjimmy/semantic-release-action@v4
        with:
          extra_plugins: |
            @semantic-release/git
            @semantic-release/exec
            @semantic-release/changelog
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
      - name: Run GoReleaser
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
          HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GH_TOKEN }}
        run: |
          curl -sL https://git.io/goreleaser | bash
Enter fullscreen mode Exit fullscreen mode

Next, in the root of your repo, create a file named .releaserc.json and paste in:

{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/github",
    [
      "@semantic-release/git",
      {
        "assets": ["CHANGELOG.md"],
        "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
      }
    ]
  ]
}
Enter fullscreen mode Exit fullscreen mode

 5. Configure GoReleaser

Next we want to add the GoReleaser configuration. Create a file in the root of your repo named .goreleaser.yaml. Paste in the following:

version: 2

project_name: <YOUR PROJECT NAME>

before:
  hooks:
    # You may remove this if you don't use go modules.
    - go mod tidy
    # you may remove this if you don't need go generate
    - go generate ./...

builds:
  - env:
      - CGO_ENABLED=0
    goos:
      - linux
      - windows
      - darwin

archives:
  - format: tar.gz
    name_template: >-
      {{ .ProjectName }}_
      {{- title .Os }}_
      {{- if eq .Arch "amd64" }}x86_64
      {{- else if eq .Arch "386" }}i386
      {{- else }}{{ .Arch }}{{ end }}
      {{- if .Arm }}v{{ .Arm }}{{ end }}
    # use zip for windows archives
    format_overrides:
      - goos: windows
        format: zip

changelog:
  sort: asc
  filters:
    exclude:
      - "^docs:"
      - "^test:"

brews:
  -
    name: <YOUR PROJECT NAME>
    commit_author:
      name: <YOUR GITHUB USERNAME>
      email: <YOUR GITHUB EMAIL>
    commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}"
    directory: Formula
    description: "<UPDATE DESCRIPTION>"
    license: "<UPDATE LICENSE>"
    install: |
      bin.install "<YOUR PROJECT NAME>"
    test: |
      system "#{bin}/<YOUR PROJECT NAME> --version"
    # Repository to push the generated files to.
    repository:
      owner: <YOUR GITHUB USERNAME>
      name: homebrew-<YOUR PROJECT NAME>
      branch: main
      token: "{{ .Env.GITHUB_TOKEN }}"
Enter fullscreen mode Exit fullscreen mode

Update all of the placeholders where necessary.

 6. Push changes

You can now commit and push these changes. You should see something running in your Actions tab. Take a look at these steps, they should be building and releasing your Go application.

GitHub Actions steps screenshot

Now take a look at your repo, it should have a tag created by semantic release.

GitHub tag screenshot

And then, in your homebrew- repo, you should see a Formula folder.

Formula in git repo screenshot

 7. Installing our application

To install, run the Homebrew tap command using the format brew tap <GITHUB USERNAME>/<GITHUB REPO>, for this example it would look something like:

brew tap hadlow/gopublish
Enter fullscreen mode Exit fullscreen mode

You should see an output that looks something like this:

==> Tapping hadlow/gopublish
Cloning into '/opt/homebrew/Library/Taps/hadlow/homebrew-gopublish'...
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 12 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (12/12), done.
Resolving deltas: 100% (2/2), done.
Tapped 2 formulae (14 files, 9.8KB).
Enter fullscreen mode Exit fullscreen mode

Then we can install with:

brew install gopublish
Enter fullscreen mode Exit fullscreen mode

Which will then give us:

==> Fetching hadlow/gopublish/gopublish
==> Downloading https://github.com/hadlow/gopublish/releases/download/v1.0.1/gopublish_Darwin_arm64.tar.gz
==> Downloading from https://objects.githubusercontent.com/github-production-release-asset-2e65be/92130980
################################################################################################### 100.0%
==> Installing gopublish from hadlow/gopublish
🍺  /opt/homebrew/Cellar/gopublish/1.0.1: 5 files, 1.5MB, built in 0 seconds
==> Running `brew cleanup gopublish`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Enter fullscreen mode Exit fullscreen mode

Then when I run gopublish, it works!

> gopublish
Hello, world
Enter fullscreen mode Exit fullscreen mode

 Wrapping up

And that's pretty much it. You can refine all of this further to suit your needs, but I wanted to keep it as simple as possible for the sake of making this guide easier to follow.

If you have any questions, I'd be glad to answer them in the comments!

Top comments (3)

Collapse
 
jozefcipa profile image
Jozef Cipa

Why did you decide to use semantic-release instead of goreleaser?

Collapse
 
hadlow profile image
Billy Hadlow • Edited

Semantic-release is mainly there to make automatic tagging for versions easier, then GoReleaser runs on the tag created by semantic-release
goreleaser.com/cookbooks/semantic-...

Collapse
 
jozefcipa profile image
Jozef Cipa

Ahh I see, completely missed that. Thank you