-
Notifications
You must be signed in to change notification settings - Fork 70
Automate versioned Homebrew formula publishing, fix patch releases #864
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
Conversation
4f14561 to
c858825
Compare
| tags: | ||
| tags: | ||
| - '*' | ||
| - '!latest' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use this tag anymore, so I deleted it so as not to be confusing.
| if [[ $(semver compare ${{ env.latest_tag }} ${{ env.most_recent_tag }}) == 0 ]] | ||
| then | ||
| echo "is_latest_version=1" >> $GITHUB_ENV | ||
| else | ||
| echo "is_latest_version=0" >> $GITHUB_ENV | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't figure out a way to write this as a one-liner but if someone who's better at bash-fu wants to give it a go, be my guest. 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer this over a one-liner. Easier to read.
| - name: Log variables | ||
| run: | | ||
| echo "Most recent (current) tag: ${{ env.most_recent_tag }}" | ||
| echo "Latest version tag: ${{ env.latest_tag }}" | ||
| echo "Building current version as new latest?: ${{ env.is_latest_version }}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could probably get rid of all this logging but it doesn't hurt to keep it, at least until we actually test an actual release. 🙂
| For example, suppose we have the the recommended versions. | ||
|
|
||
| | Sourcegraph version | Recommended src-cli version | | ||
| | ------------------- | --------------------------- | | ||
| | `3.100` | `3.90.5` | | ||
| | `3.99` | `3.85.7` | | ||
|
|
||
| If a new feature is added to a new `3.91.6` release of src-cli and this change requires only features available in Sourcegraph `3.99`, then this feature should also be present in a new `3.85.8` release of src-cli. Because a Sourcegraph instance will automatically select the highest patch version, all non-breaking changes should increment only the patch version. | ||
|
|
||
| Note that if instead the recommended src-cli version for Sourcegraph `3.99` was `3.90.4` in the example above, there is no additional step required, and the new patch version of src-cli will be available to both Sourcegraph versions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example just confused me, since 99% of the time the recommended src-cli version matches the Sourcegraph version, so I decided to just remove it. Perhaps this was more common historically.
| # For latest and second latest tags, we can't use git tag --sort=version:refname | ||
| # because git doesn't have a concept of pre-release versions and thus mis-sorts | ||
| # versions like 4.0.0-rc.0 *after* 4.0.0. | ||
| run: | | ||
| echo "most_recent_tag=$(git tag --sort=taggerdate | tail -1)" >> $GITHUB_ENV | ||
| echo "latest_tag=$(git tag | tr - \~ | sort --version-sort | tr \~ - | tail -1)" >> $GITHUB_ENV | ||
| echo "second_latest_tag=$(git tag | tr - \~ | sort --version-sort | tr \~ - | tail -2 | sed -n 1p)" >> $GITHUB_ENV |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the comment above explains, git tag --sort=version:refname sorts by semantic version number but doesn't understand pre-release versions, so it sorts versions like 4.0.0-rc.0 after 4.0.0 when they should come before. I shamelessly stole this from a comment on this gist, but it worked reliably from my testing. To break it down, since I hate obtuse bash commands:
git taglists all tags in src-cli.tr - \~replaces hyphens with tildes, so4.0.0-rc.0becomes4.0.0~rc.0(this is apparently what's typically understood as pre-releases by certain Linux distros)sort --version-sortdoes natural sort by version number of the tagstr \~ -replaces the tildes with hyphens againtail -1takes the last item, which based on the sorting should be the latest version tag
The command for the second-latest tag is the same, except it takes the second-to-last item instead.
| if [[ $(semver compare ${{ env.latest_tag }} ${{ env.most_recent_tag }}) == 0 ]] | ||
| then | ||
| echo "is_latest_version=1" >> $GITHUB_ENV | ||
| else | ||
| echo "is_latest_version=0" >> $GITHUB_ENV | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer this over a one-liner. Easier to read.
Closes #862.
Context
These GH action workflow changes enable us to automatically version Homebrew formulas for future src-cli releases while also preserving the "main" (unversioned) formula.
Before this, any new release would just overwrite the main formula. This means there was no way to get an older version of src-cli with
brewunless someone manually published a versioned formula. But what's worse is this also means that if I wanted to cut a patch release for 3.43.3 with a backwards-compatible bug fix that was introduced after 4.0, and I used our standard release process, the next person who tried to install src-cli withbrew install sourcegraph/src-cli/src-cliwouldn't get 4.0.2, they'd get 3.43.3. Not ideal.Implementation overview
This is accomplished with 3 additional jobs to our GH action:
The first step,
release_typedetermines what type of release we're doing: patch vs. update latest. This is because we need to make changes to the Homebrew tap repo ourselves if we're releasing a new latest version, and we need to use a different goreleaser config for either case due to Homebrew's naming convention expectations.The second step,
goreleaser_preonly runs if we're releasing a new latest version. It copies the current "main" formula in our Homebrew tap for the previous latest release to a versioned formula, so that it is preserved when goreleaser runs and overwrites the main formula for the latest build. It is not necessary to do this step if the latest version did not change.Then the normal
goreleaserstep runs. If we're not releasing a new latest version, we use a different config which just publishes a versioned formula, ensuring we don't overwrite the main one. It also skips publishing the Docker image for src-cli:latest. If we are releasing the latest version, we just use the same config as before.The third additional step,
goreleaser_postalso only runs if we're releasing a new latest version. It simply updates the name of the symlink to the main formula in the Homebrew tap repo. This enables someone to install the latest version either with or without the version. For example right now for 4.0.2:# These will do the same thing brew install sourcegraph/src-cli/src-cli brew install sourcegraph/src-cli/src-cli@4.0.2It is not necessary to update the symlink if the latest version did not change.
I added lots of comment inline to give clarity to what these steps are and why they are necessary. Ideally this would be something that goreleaser could handle for us automatically, but at the moment it's One Name Pattern to Rule Them All, and we'd rather not forego versions for a main formula, or vice versa.
I also updated our docs, as I realized we didn't even mention in the README that you can install src-cli with npm/npx.
Test plan
Obviously I haven't tested the full end-to-end workflow with a full release. It's possible something could still break.
--dry-run, but y'know)--dry-runagain)