OSX Homebrew addons module not as reliable as claimed

In the documentation:

The Homebrew addon correctly handles up-to-date, outdated, and missing packages. Manual Homebrew dependency scripts are error-prone, and we recommend against using them.

However, I have found there are at least three cases where the homebrew addon fails to properly handle things

  1. When an update is needed because homebrew is out of date, the addon fails to do so automatically. This means I am forced to always specify update: yes in my configurations
  2. The homebrew addon does not handle when homebrew has a very large install that doesn’t print things for a while, and will kill the build
  3. The homebrew addon does not retry steps such as timed-out downloads, which are a very common issue with Travis’ unreliable network connections.

In addition we’re 5 years on and the homebrew addon still doesn’t do caching: OSX brew install timeout · Issue #1961 · travis-ci/travis-ci · GitHub

We also see issues like missing features like: Support forced link in homebrew addon

2 Likes

I haven’t hit that yet, but now I’m wondering if it’s going to be a problem that the addon runs its own brew update command with the output redirected to /dev/null.

It takes forever to run, recently — 316.22s, in the job I just ran — should we be worried that it’s going to start causing timeouts?

1 Like

@gdevenyi, to my knowledge, Homebrew doesn’t provide a supported way to do any of that. You can check out https://stackoverflow.com/questions/39930171/cache-brew-builds-with-travis-ci to get an idea of what kind of hoops one has to jump through to get it working. So you need to work with the upstream project in that direction. Only when there’s upstream support can Travis consider supporting any of this officially.

If you have 10+ minute package installs, you are likely using a old MacOS version that Homebrew no longer provides bottles for (and they only do that when Apple drops support for it, too) – and as such, you are on your own in this case, through no fault of Travis. You can use the library linked to on the above link to build bottles from source and cache them but you’ll probably need one or more cache stages to build everything, and Homebrew doesn’t guarantee that more modern formulae won’t break for those old OS versions (but it does accept pull requests to fix such breakages).

2 Likes

And I’m sympathetic to the fact that the tool isn’t exactly well-suited to their needs. Homebrew does seem frustratingly slanted towards interactive use. Just the fact that it considers “this package you asked to install is already installed” to be a warning condition, instead of silently ignoring it as “request already met”, is a bit strange for a package manager. AFAICT there doesn’t even seem to be a commandline argument to brew install that would optionally tell it to ignore already-installed packages. There’s a lot of room for improvement, I agree.

While I agree that the Homebrew developers should be involved in any efforts to improve this situation, and I would hope that they’d be amenable to discussions along those lines, it seems to me that those discussions would be infinitely more productive if the Travis developers who’re supporting the addon were involved in them, or even initiated them.

If the Travis team goes to the Brew team and says, “Hey, we want to use your tool to support our user base, but here are some things about it that don’t meet our needs, and these other feature ideas we’ve had would really make it a much better fit for our process.” then it’s a discussion between a group of tool developers and the downstream consumers who are implementing solutions based on that tool.

If Travis users go to Homebrew, then you’ve got a bunch of someone else’s endusers asking for things which may or may not be any use to the Travis team, and may be delivered in a way that doesn’t meet their needs. I know I’d certainly rather be part of that discussion, rather than just waiting to see what comes out of it and hoping it produces what I need.

The addon is opensource, and Homebrew is actually maintained by unpaid volunteers. So you are in a prime position to gauge the tooling’s needs and suggest stuff, especially if you are using the CI for free. Nothing prevents you from CCing staff members in case they have any comments.

Related: Run stock installation logic after before_install (or other chunk of user code)
This would allow to fix up things on the fly before any addon logic runs.

1 Like

I would like to add an example illustrating what @gdevenyi was saying in his first bullet point about homebrew not being up to date.
Currently I am not able to use the addons: brew in my .travis.yml because the Installing Homebrew Packages step in the build consistently fails with the following error:

Error: Your Homebrew is outdated. Please run `brew update`.
Error: Kernel.exit

(but interestingly, this does not fail the build, it goes on to execute the job, which is a bug I think).

Here is a little sandbox build I have made to illustrate what’s happening (https://travis-ci.com/glostis/travis-ci-sandbox/builds/121097946):

os: osx
language: generic

jobs:
  include:
    - stage: test
      name: "Manual brew install"
      install: HOMEBREW_NO_AUTO_UPDATE=1 brew install jq
      script: echo {} | jq .
    - stage: test
      name: "Addon brew install"
      addons:
        homebrew:
          packages: jq
      script: echo {} | jq .
    - stage: test
      name: "Addon brew update + install"
      addons:
        homebrew:
          packages: jq
          update: true
      script: echo {} | jq .

As you can see, the best solution is the “manual” one, but it’s a shame because it doesn’t use the addons feature which is really neat. The addons with update: true option works, but adds a 10 minutes overhead to any build…

My question is: is this issue known to the Travis team working on the addons: homebrew feature? If so, what is the recommended solution?

1 Like

Wow, that’s a really interesting bug. I wonder why the manual install works when the addon doesn’t.

If you look at the log of the build with addon, you see these lines under the section “brew”:

# Installing Homebrew Packages
$ rvm $brew_ruby do brew bundle --verbose --global
/usr/local/bin/brew tap homebrew/bundle
==> Tapping homebrew/bundle
Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-bundle'...
remote: Enumerating objects: 88, done.
remote: Counting objects: 100% (88/88), done.
remote: Compressing objects: 100% (80/80), done.
remote: Total 88 (delta 7), reused 29 (delta 4), pack-reused 0
Unpacking objects: 100% (88/88), done.
Tapped 0 formulae (183 files, 249.5KB)
Error: Your Homebrew is outdated. Please run `brew update`.
Error: Kernel.exit

So the difference between the “manual” install and the “addon” one comes from the fact that addon builds a Brewfile and executes it with brew bundle, whereas the manual install simply runs straight brew install.

The bundle command is not part of the core brew, it lives in its separate repo (see this issue which explains that it was outsourced). If you dig in their code, you find these lines:

if !defined?(HOMEBREW_VERSION) || !HOMEBREW_VERSION ||
   Version.new(HOMEBREW_VERSION) < Version.new("2.1.0")
  odie "Your Homebrew is outdated. Please run `brew update`."
end

I’m not fluent in Ruby, but it seems like they make their command error out if the Homebrew version is not high enough.

I propose to open an issue to them asking if it would be possible to warn instead of erroring out, because in our case we clearly have no control over what Homebrew version is included in the Travis macos images.

1 Like

I have posted the issue here, for reference: https://github.com/Homebrew/homebrew-bundle/issues/540

I’ve hit a limitation where the addon always installs all the formulae before all the casks. Here I’m trying to install the bazel formula (from the bazelbuild/tap tap) but it needs the adoptopenjdk8 cask (from the homebrew/cask-versions tap) to be installed first. There seems to be no way to represent this declaratively in .travis.yml without writing my own Brewfile, which I don’t want to do. :confused:

I’ve even tried having a separate addons.homebrew section beforehand with just the cask, but it seems only the final addons.homebrew section is respected and any preceding ones are ignored.

(Happy to raise my case as a separate question if that is more appropriate.)

EDIT: I managed to work around by installing bazelbuild/tap/bazel instead of just bazel. Apparently this method doesn’t have the JDK8 dependency, so I managed to skip installing the cask. However, I’m leaving this post here because the formulae-before-casks limitation theoretically still exists.

Looking at the code you’ve referred to and https://github.com/bazelbuild/homebrew-tap/blob/master/Formula/bazel.rb, this look like a problem with the brew bundle command – it doesn’t resolve all dependencies first to determine the install order – and/or the formula – it doesn’t declare the cask as a dependency (apparently, it is possible to depend on a cask).

Since this formula is essentially broken, it’s not illogical that it cannot be installed with standard means and you need to install it manually to workaround its problem.