Use Homebrew Bundle to manage software installation on macOS 
Tags: macos, homebrew, bundle, packages
Published: 2020-11-30 15:37:55

---
title: Use Homebrew Bundle to manage software installation on macOS 
published: true
description: From manual brew install scripts to a Brewfile definition to let brew bundle manage the package installation on macOS
tags: macos, homebrew, bundle, packages
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/7aq91ii88yu2gosbo7yl.png
---

Automation is iteration. First you'll find a way to make it work, and share your learnings - feedback and best practices help make it even better. When I first published my [dotfiles - Document and automate your Macbook setup](https://about.gitlab.com/blog/2020/04/17/dotfiles-document-and-automate-your-macbook-setup/) blog post, Nate [suggested to use](https://gitlab.com/dnsmichi/dotfiles/-/issues/2) Homebrew bundle for a more clean management approach. 

[Homebrew bundle](https://github.com/Homebrew/homebrew-bundle) follows the same idea as known from Ruby bundle, has a `Brewfile` and specific keywords for packages to install.

## First Steps with Homebrew Bundle

* tap: Add more brew third-party repos 
* cask: Install application casks (Gimp, etc.)
* brew: Install packages
* mas: Install App store items (requires `brew install mas`)

You can also override the default cask installation path:

```
$ vim Brewfile

cask_args appdir: "/Applications"

tap "homebrew/cask"

cask "gimp"

brew "git"

mas "Slack", id: 803453959
```

Run it to see how it works. 

```
$ brew bundle
Using homebrew/cask
Using gimp
Using git
Using Slack
Homebrew Bundle complete! 4 Brewfile dependencies now installed.
```

`git` from Homebrew is newer than the macOS Git binary. 

```
$ git --version
git version 2.29.2

$ /usr/bin/git --version
git version 2.24.3 (Apple Git-128)
```

Homebrew's Git is installed into `/usr/local/bin` which has a priority in the `PATH` environment variable.

```
$ which git
/usr/local/bin/git

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```

## Existing Homebrew Setup? No Problem

`bundle` provides the `dump` sub command which writes a new `Brewfile` with your current setup. 

```
brew bundle dump
```

Homebrew automatically installs the `bundle` tap the first time the command is invoked, no extra preparations needed. 

You can either use this file to continue working, or start fresh in your own `Brewfile`. You can also manually install software first and then analyse how a dump looks like. 


## Caveats with GNU tools (sed, tar, ...)

You can install GNU tools to avoid different parameters and behaviour with the native UNIX representation of sed, tar, etc. Homebrew provides binaries prefixed with the `g` character, so even an updated PATH variable won't help, requiring you to call `gsed` instead of `sed`. To keep compatibility with existing Linux scripts, I used symlinks created inside a [brew setup script](https://gitlab.com/dnsmichi/dotfiles/-/blob/v1.0.0/brew.sh):

```
BREW_PREFIX=$(brew --prefix)
ln -s "${BREW_PREFIX}/bin/gsed" "${BREW_PREFIX}/bin/sed"
```

This isn't possible inside a Brewfile. Luckily this can be automated in your PATH, as the Homebrew formulas provide these symlink overrides on their own:

```
$ brew info gnu-sed

GNU "sed" has been installed as "gsed".
If you need to use it as "sed", you can add a "gnubin" directory
to your PATH from your bashrc like:

    PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"

$ ls -la /usr/local/opt/gnu-sed/libexec/gnubin
total 0
drwxr-xr-x  3 mfriedrich  staff   96 Jan 15  2020 .
drwxr-xr-x  5 mfriedrich  staff  160 Jan 15  2020 ..
lrwxr-xr-x  1 mfriedrich  staff   14 Jan 15  2020 sed -> ../../bin/gsed
```

Collect the inventory from all GNU tools and render an update for your environment file. You can automate this task with [this inventory snippet](https://gist.github.com/skyzyx/3438280b18e4f7c490db8a2a2ca0b9da#gistcomment-3049694) added to [.oh-my-zsh/custom/path.zsh](https://gitlab.com/dnsmichi/dotfiles/-/blob/v2.0.0/.oh-my-zsh/custom/path.zsh) or `.zshrc`:

```
# GNU utils path overrides as default CLI tools
if type brew &>/dev/null; then
  HOMEBREW_PREFIX=$(brew --prefix)
  for d in ${HOMEBREW_PREFIX}/opt/*/libexec/gnubin; do export PATH=$d:$PATH; done
fi
```

If you prefer to use symlinks, you can do so too instead of the export. Keep in mind that the PATH environment is generated on each new terminal, while symlinks can run stale. 

## A complete example

Follow the iteration in [this merge request](https://gitlab.com/dnsmichi/dotfiles/-/merge_requests/2/diffs) and open the [dotfiles repository](https://gitlab.com/dnsmichi/dotfiles) to learn more.

My Brewfile focusses on my work as [Developer Evangelist at GitLab](https://about.gitlab.com/handbook/marketing/community-relations/developer-evangelism/) with a local Linux-ified CLI environment and Docker. Heavier workloads are shifted into cloud environments. Image manipulation tools help with automating tasks for blog posts and workshops. 

I'm also not pinning specific versions for packages, this is not supported by Homebrew bundle either.

```
cask_args appdir: "/Applications"

# Tap Homebrew
tap "homebrew/bundle"
tap "homebrew/cask"
tap "homebrew/cask-fonts"
tap "homebrew/cask-versions"
tap "homebrew/core"
tap "homebrew/services"

cask "java"
cask "visual-studio-code"
cask "firefox"
cask "vlc"
cask "wireshark"
cask "gimp"
cask "inkscape"
cask "jitsi-meet"
cask "handbrake"
cask "vagrant"
cask "spotify"

# System
brew "mas"
brew "curl"
brew "wget"
brew "git"
brew "vim"
brew "openssl"
brew "coreutils"
brew "moreutils"
brew "findutils"
brew "binutils"
brew "rename"
brew "gnu-sed"
brew "gnu-tar"
brew "gawk"
brew "gnutls"
brew "gnu-indent"
brew "gnu-getopt"
brew "tree"
brew "htop"
brew "pidof"
brew "pstree"
brew "grep"
brew "openssh"
brew "rsync"
brew "ssh-copy-id"
brew "screen"
brew "gmp"
brew "nmap"
brew "socat"
brew "rlwrap"
brew "dnstracer"

# Images, Audio, Video
brew "imagemagick"
brew "gifsicle"
brew "gifify"
brew "ffmpeg"

# Archive & Git
brew "xz"
brew "p7zip"
brew "git"
brew "git-lfs"
brew "tig"
brew "hub"

# Extract rpm file content with rpm2cpio *.rpm | cpio -ivd
brew "rpm2cpio"

# JSON
brew "jq"
brew "jo"

# Dev
brew "ruby"
brew "yarn"
brew "rbenv"
brew "python"
brew "go"
brew "cmake"
brew "openjdk"
brew "kind"

# GitLab Pages
brew "hugo"

# App Store
mas "1Password 7", id: 1333542190
mas "Slack", id: 803453959
mas "Telegram", id: 747648890
mas "uBlock", id: 1385985095
```