1

I need to install a newer version of a dependency via Brew than what is contained in the Travis CI image. Running brew update adds more than 100 seconds to every build job, so I'd like to find a way to cache the installation, or at least cache the Bottle and Formula.

It seems like there should be a simple recipe for caching the Bottles and Formulas from Brew, but so far I have not found that recipe.

To be specific, I'm using pyenv on TravisCI and for the build jobs that runs tests with Python 2.6 I need pyenv to be the latest (1.2.4), since get_pip fails on earlier versions due to recent changes on PyPI.

I added a conditional to my .travis.yml and it works fine:

if [[ "$pyver" == 2.6 ]]; then
  brew upgrade pyenv &>/dev/null
fi

However, the command takes a while to run. I've split out the upgrade command and timed it:

time brew update
time brew upgrade pyenv

The brew update takes more than 100 seconds, the brew upgrade takes 10 seconds.

That finding led to the idea to cache the Bottle and Formula and on every subsequent build run:

HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade pyenv

There's a question on how to cache Brew sources. That works fine.

cache:
  directories:
    - "$HOME/Library/Caches/Homebrew"

In $HOME/Library/Caches/Homebrew, I find the bottle cached:

pyenv-1.1.5.sierra.bottle.tar.gz
pyenv-1.2.4.sierra.bottle.tar.gz

However even with the bottle cached, running HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade pyenv doesn't succeed, presumably because the updated formula is not cached. The command yields:

Error: pyenv 1.1.5 already installed

I added caching of the formula:

cache:
  directories:
    - "$HOME/Library/Caches/Homebrew"
    - /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core

and implemented the following algorithm:

  1. Run HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade pyenv to upgrade pyenv using the latest cached formula/bottle.
  2. Test if pyenv >= 1.2.4 is installed. If the pyenv 1.2.4 formula and bottle were cached, it will be installed in step (1) without brew update having been run.
  3. If pyenv >= 1.2.4 was not cached, then run brew upgrade pyenv and save the cached bottle and formula.

and the following script:

function ver { printf "%03d%03d%03d" $(echo "$1" | tr '.' ' '); }
HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade pyenv &>/dev/null
# Update brew and upgrade pyenv if necessary
if [ $(ver $(pyenv --version | cut -d ' ' -f2)) -lt $(ver 1.2.4) ]; then
  brew upgrade pyenv &>/dev/null
fi

However, I question whether it's a good idea to cache /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core. The examples in the Travis CI documentation are always caching directories under $HOME. It seems like I might experience problems when Travis updates the image. Does Travis CI delete the branch caches after the image is updated? If not, it seems like I would need to do that manually.

Is there a better way? It seems like Travis CI should offer an easy way to cache Brew formula and bottles.

Should I not bother with Brew and just install pyenv from the source?

RjOllos
  • 2,900
  • 1
  • 19
  • 29

1 Answers1

1

I cached the brew formula under $HOME by copying the file on the first build:

cache:
  directories:
    - "$HOME/Library/Caches/Homebrew"

[...]

before_install:
  - |
    if [ "$TRAVIS_OS_NAME" = osx ]; then
      # Pyenv >= 1.2.4 is needed with Python 2.6
      if [[ "$pyver" == 2.6 ]]; then
        # Upgrade pyenv and save formula/bottle in cache.
        formula=$(brew --repository)/Library/Taps/homebrew/homebrew-core/Formula/pyenv.rb
        formula_cached=$(brew --cache)/pyenv.rb
        if [[ ! -f $formula_cached ]]; then
          brew upgrade pyenv &>/dev/null
          cp $formula $formula_cached
        else
          HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade $formula_cached
        fi
      fi
RjOllos
  • 2,900
  • 1
  • 19
  • 29
  • Wow, didn't know you can pass a formula `.rb` to `brew upgrade`. Is this documented? – ivan_pozdeev Nov 05 '18 at 07:00
  • 1
    I was also surprised to see that the feature. Not sure if I found that in the documentation, or just saw an example on StackOverflow or elsewhere. The [Tips N Tricks](https://docs.brew.sh/Tips-N'-Tricks) page shows a similar case of installing directly from a `rb` file in a pull request ("Installing directly from pull requests") – RjOllos Nov 05 '18 at 15:08
  • Note that this doesn't handle dependencies correctly. At the first run, you will have new versions of dependencies installed and cached. At the 2nd run, you will try to install new `pyenv` formula with old metadata -- so you will get old versions of dependencies (which might break things depending on how much apart they are) -- while new versions will still lie uselessly in the cache. If the new formula happens to have version-specific or recently-added dependencies, it will even fail to install if those versions are not in the old metadata. – ivan_pozdeev Nov 06 '18 at 01:14