Skip to content

Instantly share code, notes, and snippets.

@maelvls
Last active February 23, 2025 20:02
Show Gist options
  • Select an option

  • Save maelvls/068af21911c7debc4655cdaa41bbf092 to your computer and use it in GitHub Desktop.

Select an option

Save maelvls/068af21911c7debc4655cdaa41bbf092 to your computer and use it in GitHub Desktop.
Automate build workflow for Homebrew tap bottles (Linux and macOS)

How to automate the build of bottles on your tap

This tutorial is a follow-up to the discussion we had on davidchall/homebrew-hep#114. It relies on the test-bot provided by davidchall; you can get it with brew tap davidchall/test-bot.

Use Travis CI to test and bottle your formulae

The idea is to use staged build in Travis CI, where the two stages will be

  1. the testing stage where the bottles are built and tested for the different architectures with

    brew test-bot
    

    At the end of this stage, each job must upload its bottle.tar.gz and bottle.json to somewhere (e.g., free AWS S3 bucket).

  2. the deploy stage (only on master) where we fetch the bottle.json and bottle.tar.gz, upload the bottle.tar.gz to Bintray and merge the multiple bottle.json into a commit that contains the bottle DSL with the command

    brew test-bot --ci-upload
    

Screenshot of the staged build status on Travis CI

You can see a detailed example of such a configuration in the [.travis.yml][#file-travis-yml] of this gist, where bottles are built for linux, sierra and el_capitan. You can also see an example of tap, touist/homebrew-touist, that uses it and its .travis.yml.

Drawbacks w.r.t. bottles when maintaining a tap relying on core formulae

On the homebrew-core repo, whenever a formula on which some other formulae are relying on is updated, we also must rebuild the bottles of these dependent formulae (this is why we have the revision number _1 after the actual version number). This seems to be automatically taken care of on the homebrew-core tap.

On your own tap, this revision bump whenever a dependency bottle gets rebuilt can lead to broken bottles. This problem has not been solved yet, but:

  1. may be we could rely on a cron job that tests the bottles every week to check if the bottle still works;
  2. or we could subscribe to the changes on the formulae we depend on and run a script on a Travis CI build

Both solutions require a lot a thoughts, that is why I did not do anything about it. I only rely on gmp at runtime so maybe it is fine... Note that this problem has been also descussed here and here.

What you need

  1. the name of the Github repo for your tap must be <user>/homebrew-<tap>
  2. you need a Github OAuth2 token that you can get in your Github settings Β  (secret variable GITHUB_TOKEN in .travis.yml);
  3. the name of the Bintray repo must be <bintray-user>/bottles-<tap> Β  (secret variable HOMEBREW_BINTRAY_KEY in .travis.yml);
  4. you also need an AWS S3 bucket, it is free and takes 1 minute to create; then you need to create a token+password and set them as secret variables in Β  the travis-ci settings (secret variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .travis.yml).

Tricks I want to remember when I was setting up Travis CI + test-bot

  1. when using brew test-bot, never use --ci-pr, --ci-master, --ci-testing on your mac as it is going to remove everything installed. You can still use brew test-bot and brew test-bot --ci-upload; what I would do is to make sure everything runs correctly locally (except for brew doctor which generally always fails on my mac) using:

    brew test-bot Formula/touist.rb
    
  2. On Travis CI, mac builds are costly (in time). For trying to make the Β  .travis.yml work, I really recommend to use os: linux instead of a mac image.

  3. Also, when you find yourself stuck when trying to make the CI work, I recommend to re-run your job in debug mode in order to see what is going wrong. Again, as mac images are slow, I recommend to use the linux alternative.

  4. if you see that brew test-bot is not building/testing any bottle, check Β  that you have made the symlink (in travis-ci) at $(brew --repo touist/touist) Β  that should target $TRAVIS_BUILD_DIR. It is important because brew test-bot Β  is testing the tap inside $(brew --repo) but the actual repo cloned by travis is in $TRAVIS_BUILD_DIR.

  5. the --keep-old option when uploading seems to be a good idea (= keeps bottles you previously built but that you don't produce anymore) but it keeps failing on stupid errors all the time; I don't use it anymore.

  6. when you try to make a formula work (on master) and you want to rebuid the bottle of the formula, just edit somewhere in the formula and push. This is going to increment "rebuild 1" in the bottle DSL and reupload the bottles.

  7. same thing on a PR or a branch: to make the formula rebuild, the push has to only contain edits on a Formula/formula.rb file

  8. adding "revision 1" to a formula does not seem to be appropriate for forcing a bottle to be rebuil

  9. the tests/building of the bottle only happens when you push a series of commit on ONE single formula; if you include some edits to .travis.yml for example, the push won't trigger build of the bottle. This caused me a lot of time lost on try and errors.

  10. when creating a new formula, you may get these kind of errors during the build (on PR, branch and master):

Β  - New formulae should not require patches to build. Patches should be submitted and accepted upstream first. - GitHub fork (not canonical repository) - GitHub repository too new (<30 days old)

Β  First, make sure the bottles build without errors and that the only step failing is brew audit --online. Then, accept the PR and make a small change to the formula to force the rebuild of the bottle. The errors will disappear (as they are only there when the formula is new).

# Inspired from the .travis.yml in
# https://github.com/davidchall/homebrew-hep/
language: ruby
if: tag IS blank
env:
global:
# Beyond these global variables, you also need to set some secret variables
# in your travis-ci settings:
# - AWS_ACCESS_KEY_ID
# - AWS_SECRET_ACCESS_KEY (for the AWS S3 bucket)
# - GITHUB_TOKEN (for pushing bottle DSL commits)
# - HOMEBREW_BINTRAY_KEY (for the Bintray repo)
- HOMEBREW_BINTRAY_USER=maelvalais
- TAP_BOTTLE_DOMAIN=https://dl.bintray.com/touist
# Note: I removed $HOME/Library/Caches/Homebrew from the caches
# because it was enormous (~2GB for the 4 jobs). This is because
# this folder is already present on osx images and it is big.
# The linuxbrew one isn't as big because it is created from scratch.
cache:
directories:
- "$HOME/.cache/Homebrew" # cache of Linuxbrew
- "$HOME/.cache/pip"
- "$HOME/.gem/ruby"
install:
# the official test-bot won't let you run inside TravisCI, so we use
# davidchall's one. David's test-bot cannot push the commit using
# Oauth github + https (only ssh) so I use my own.
- brew tap davidchall/test-bot
# IMPORTANT STEP: link the tap inside brew to our current travis-cloned tap
# Step: 1) create the intermediate folders <user>/<repo> so that
# we can 2) remove <repo> and 3) replace it with a sym link
# that will point to the travis build folder.
# If we don't do that, the tap be cloned using the default master
# branch, and thus we cannot test our tap at the current pushed commit.
# We also need to unshallow in 4) because sometimes travis does not
# clone thouroughly but we need a deep clone.
- mkdir -p $(brew --repo $TRAVIS_REPO_SLUG) # 1)
- rm -rf $(brew --repo $TRAVIS_REPO_SLUG) # 2)
- ln -s $PWD $(brew --repo $TRAVIS_REPO_SLUG) # 3)
- git fetch --unshallow || true # 4)
- brew install awscli
- aws s3 sync s3://homebrew-touist-travis/${TRAVIS_BUILD_NUMBER} ~/shared || true
script:
# Note on HOMEBREW_DEVELOPER: I don't want to put in env.global because
# it should be '1' only during test-bot. If it is '1' during
# brew cask uninstall... it will fail on the deprecation notice.
# Unless I am in 'brew test-bot', I don't want to fail on warnings.
# Also, 'brew doctor' must be run under HOMEBREW_DEVELOPER=1. Otherwise,
# it returns 1 with the message "this osx version is outdated" on old osx ver.
- export HOMEBREW_DEVELOPER=1
- brew doctor # needs HOMEBREW_DEVELOPER because of outdated osx returning 1
- brew test-bot
- cp *.bottle*.* ~/shared 2>/dev/null || echo "==> No bottle created here"
# https://docs.travis-ci.com/user/reference/osx/#OS-X-Version
jobs:
include:
- os: osx
osx_image: xcode8.3
env: OS=sierra-10.12
rvm: system
- &run-on-osx-old-xcode # only for xcode6.4 and xcode7.3
os: osx
osx_image: xcode7.3
env: OS=el_capitan-10.11
rvm: system
before_install: # IMPORTANT: HOMEBREW_DEVELOPER must not be set here.
# Sometimes, brew update fails with the error 'Homebrew must be run
# under Ruby 2.3! You're running 2.0.0 (althouth ruby-portable is there).
# Workaround: double brew update: https://github.com/Homebrew/brew/issues/3299
- brew update | grep "==>" || brew update
# (brew doctor) fix the many missing deps on gnupg and others
- brew missing | cut -d':' -f1 | xargs brew uninstall
# (brew doctor) fix broken symlinks (oclint and cloog)
- brew prune
# (brew doctor) fix "your XQuartz (2.7.9) is outdated" if it is installed
- brew cask uninstall xquartz || true
# (brew test-bot on travis) fix libyaml 0.1.6_1 already installed
- brew uninstall libyaml || true; brew uninstall md5deep || true
# - <<: *run-on-osx-old-xcode
# os: osx
# osx_image: xcode6.4
# env: OS=yosemite-10.10
# rvm: system
- &run-on-linux
os: linux
env: OS=x86_64_linux
rvm: 2.3
before_install:
# Fix the permission problem on linux (664 instead of 644) during
# git clone (the one done by travis-ci). Homebrew needs formulas to be
# 'chmod 644'. This is because git does not conserve permissions and
# travis-ci seems to have by default a umask too permissive.
# Because we cannot do 'umask 002' just before travis clones the repo,
# I set umask afterwards (1) and I change the permission of
# already cloned files from 664 to 644 (2).
- umask 022 # (1)
- chmod 0644 Formula/*.rb # (2)
# Instal linuxbrew
- export PATH="/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:$PATH";
- yes '' | sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)";
# Fix a `brew doctor` error on "config" scripts for some reason
- sudo rm -f /home/travis/.phpenv/shims/php-config
/opt/pyenv/shims/*-config /usr/local/clang-3.9.0/bin/llvm-config
before_cache:
- brew cleanup
- <<: *run-on-linux
stage: deploy
if: branch = master AND type != pull_request
os: linux
rvm: 2.3
env: OS=any_linux
script:
# This hook will add [ci skip] the the commit messages for bottle DSL
- echo 'echo "\n\n[ci skip]" >> "$1"' > .git/hooks/commit-msg; chmod +x .git/hooks/commit-msg
- if cp -v ~/shared/* . ; then
brew test-bot --ci-upload --git-name=maelvalais [email protected] --bintray-org=touist --verbose;
else
echo "==> No bottle downloaded, cannot run --ci-upload";
fi
after_script:
# Clean the bucket
#- aws s3 rm s3://homebrew-touist-travis/${TRAVIS_BUILD_NUMBER} --recursive
after_script:
# In case the AWS_SECRET_ACCESS_KEY and AWS_SECRET_ACCESS_KEY are not
# available, we don't want the build to fail.
- aws s3 sync ~/shared s3://homebrew-touist-travis/${TRAVIS_BUILD_NUMBER} || true
@davidchall
Copy link

AWS S3 buckets are only free for the first 12 months. Are there any truly free options to store the bottles before deployment?

@maelvls
Copy link
Author

maelvls commented Mar 12, 2018

Awww... I did not know about that. :( I cannot think of anything else yet, I'll have to find another way!

@maelvls
Copy link
Author

maelvls commented Mar 13, 2018

Update: up to now I have paid 0.05€/month so I guess I'll just continue that way! :)
Here is the details:

screen shot 2018-03-13 at 12 03 09

Note: I run Travis CI daily (cron job) on both my taps so it increases a bit the PUT counter

@MikeMcQuaid
Copy link

Note on Oct 4, 2018: due to a change in Homebrew's brew test-bot behaviour, the user must set HOMEBREW_TRAVIS_CI and HOMEBREW_TRAVIS_SUDO appropriately (it was previously using Travis-CI-provided TRAVIS and TRAVIS_SUDO).

This is not the case. These are set by brew test-bot: https://github.com/Homebrew/homebrew-test-bot/blob/650adb8e413c3b902e87baa125c49f2e16760d5a/cmd/brew-test-bot.rb#L1558-L1565

run a cron job that tests the bottles every night to check if the bottle still works, using brew test *.rb and brew linkage --test *.rb;
or we could subscribe to the changes on the formulae we depend on and run a script on a Travis CI build (I could not find a way to do that yet).

Good idea. I'd recommend a Travis CI daily cron job for this.

you also need an AWS S3 bucket, it is free and takes 1 minute to create; then you need to create a token+password and set them as secret variables in the travis-ci settings (secret variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .travis.yml).

Is it possible to upload only to Bintray?


As a general comment: nice work! It would be great to get this included in some form in Homebrew's official documentation and submit some of these issues and workarounds as issues or fixes in brew test-bot and other commands.

@bblacey
Copy link

bblacey commented Dec 17, 2018

Nice work. I am prototyping a bintray-only variant of this for FreeCAD but brew test-bot is failing with Error: undefined local variable or method verbose. Thoughts?

Have you considered adding GitHub issues to your https://github.com/maelvalais/homebrew-test-bot fork so there is a way to file and resolve issues?

@izaid
Copy link

izaid commented Dec 18, 2018

Same problem here, @bblacey.

@ladislas
Copy link

ladislas commented Jun 5, 2019

@bblacey @izaid - I've put up a working example using Azure Pipelines, it might be helpful for you :)

https://github.com/ladislas/homebrew-greetings

@maelvls
Copy link
Author

maelvls commented Jun 5, 2019

Wow, excellent work, very well documented! πŸŽ‰ πŸŽ‰ πŸŽ‰
I'll move everything to Azure Pipelines asap.
Thank you @ladislas πŸ‘

@bblacey
Copy link

bblacey commented Jun 5, 2019

@ladislas, @maelvalais, I managed to get things working reasonably well using Bintary as a free intermediary but your Azure pipeline approach offers cleaner/separation and abstraction... Since my last stab at this, FreeCAD has started moving to Conda as a cross-platform build system because many of the APIs and extensions are python (think of FreeCAD the application as being a core CAD engine written in C++ with exposed Python Bindings so the rest of the app, plugins, extensions, etc are written in Python where Conda makes a lot of sense. Great work and thanks for letting me know. I am sure this will benefit the broader HomeBrew community because personal taps can be a huge time sink without auto-management.

@ladislas
Copy link

ladislas commented Jun 5, 2019

@maelvalais my pleasure :)
by all means, if you find any issues or think that the documentation could be improved, please open an issue/pr!
your work here has been very helpful for me and helped me dig deep into brew test-bot. I could not use travis as my compilation time for avr-gcc was too long, but then I discovered Azure Pipelines and voila! :)

@maelvls
Copy link
Author

maelvls commented Feb 26, 2020

I really really have to move to Azure pipelines and away from Travis πŸ™‚ I'm worried that sooner or later, the upstream brew test-bot will drop support for Travis, so I need to get moving

@ladislas
Copy link

I think they already did... and Azure as well. Github Actions is the future. Haven't moved my workflows yet but I'm planning on it. I'll let you know how it goes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment