How to create brew formulas for installation on macOS
Overview
This tutorial provides a deep dive of Homebrew, a package manager for macOS that’s like other package mangers for Linux:
Distribution | Package Manager | Format | GUI tools |
---|---|---|---|
Darwin (macOS) | Homebrew | - | brew |
Debian, Ubuntu | dpkg | .deb | APT (Advanced Packaging Tool) |
RedHat, Fedora, openSUSE | RPM | .rpm | Yum, apt4rpm, up2date, urpmi, ZYpp, poldek |
Slackware | tgz | - | - |
Arch Linux, Frugalware, DeLi Linux | Pacman | - | - |
Puppy Linux | PETget | - | - |
Windows | Chocolatey | - | choco |
DEFINITION: A formula provides instructions on how to install CLI packages and their dependencies, such as where tar.gzip and *.zip files.
A cask provides instructions on how to install GUI apps from .dmg files.
Step-by-step instructions are provided here to install Homebrew itself and then install Homebrew packages based on the name of formulae specified for installation in a command such as:
brew install wget
Brew installs packages in its own Cellar directory (folder) and adds symlinks to the /usr/local folder.
Homebrew is the newest and most popular package utility on macOS.
Homebrew’s web page is at http://brew.sh
Alternatives to Homebrew
sudo port install tree
Creating Homebrew Formula
After you have installed Homebrew for the brew command (see below):
Discussion forum about Writing Formulae/Casks
export HOMEBREW_NO_INSTALL_FROM_API=1 before installing.
-
For example:
brew install prometheus
https://prometheus.io/download/#prometheus
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/p/prometheus.rb
-
See the (very long) list of all Homebrew formulas from:
Each formula is a Ruby-language file stored for brew install by users at:
https://github.com/Homebrew/homebrew-core/tree/master/Formula
- Click on a program (such as htop, jq, tree, wget, etc.).
-
Click on the Formula code, such as wget.rb on GitHub:
https://github.com/Homebrew/homebrew-core/blob/1cde8401d8be5f28d74a402afe67fd52ac0575ed/Formula/j/jq.rb
Notice that the blob is referenced in GitHub by its SHA.
mirror
In case the url goes down:
mirror "https://ftpmirror.gnu.org/a2ps/a2ps-4.15.5.tar.gz"
livecheck do
# There can be a notable gap between when a version is tagged and a # corresponding release is created, so we check the "latest" release instead # of the Git tags. livecheck do url :stable strategy :github_latest end
Code here verifies the version within github.com:
url :stable regex(/^v?(\d+(?:\.\d+)+)$/i)
url "https://beyondgrep.com/install/" regex(/href=.*?ack[._-]v?(\d+(?:\.\d+)+)["' >]/i)
url "https://github.com/smmr-software/mabel.git", tag: "v0.1.7", revision: "1e74a44f69ce86a0ada6d162c0dabcf2ad3c5077"
head
This line defines the git branch “main” for the repo:
head "https://github.com/htop-dev/htop.git", branch: "main"
The variables are based on this document.
SHA
The SHA value is the hash code generated based on the contents.
Hashing is done before and after a file is saved and transmitted. A hash is created after transmission to verify if every bit is the same.
version "2.5.1" url "https://github.com/gatewayd-io/gatewayd/releases/download/v#{version}/gatewayd-darwin-amd64-v#{version}.tar.gz"
</pre>
### bottle do
If there are different installers depending on each operating system, each is listed within bottle do
sha256 cellar: :any, arm64_sonoma: "a07989af65c77dbfb28b07b8faec12d3760831c360e0caa6a32a58eff0e8fd65" sha256 cellar: :any, arm64_ventura: "66603fe2d93294af948155b0392e6631faec086b0bcc68537d931861e9b1de39" sha256 cellar: :any, arm64_monterey: "f8c4b4433a3fda0ee127ba558b4f7a53dff1e92ff6fb6cef3c8fbf376f1512c8" sha256 cellar: :any, sonoma: "5cd79199db8d7394d331dbb362dd101d12519325f78dde1af4e7c67fb9f4e5da" sha256 cellar: :any, ventura: "d47397e29f584bedd7d1f453af5ff42f10c3607a823fa72314b6d4f1c44cd176" sha256 cellar: :any, monterey: "665c48cbe7434b5850d66512008e143193cd22b69ae54788314955415b6c546d" sha256 cellar: :any_skip_relocation, x86_64_linux: "e6734208d3ea8db55123b1d1d9ac4f427c5e7ba89472193afe51543a2bb1a9a1"
PROTIP: This needs to be updated with every new OS version added.
Otherwise:
sha256 cellar: :any_skip_relocation, all: a54aa4f028ef042948961ef62524557dd8afd2c05eb658bd5f6d1ec04dddc22f”
### depends on
Especially if you’re installing a package dependent on Python, specify the specific version :
depends_on "python-hatch-vcs" => :build depends_on "python-hatchling" => :build depends_on "python-setuptools" => :build depends_on "python-setuptools-scm" => :build depends_on "python-requests" depends_on "python@3.12" def python3 "python3.12" end
### caveats
def caveats <<~EOS When run from `brew services`, `prometheus` is run from `prometheus_brew_services` and uses the flags in: #{etc}/prometheus.args EOS end
### def install
def install defines commands to install:
QUESTION: What is prefix?
system "make", "install", "PREFIX=#{prefix}"
For htop:
system "./autogen.sh" args = ["--prefix=#{prefix}"] args << "--enable-sensors" if OS.linux? system "./configure", *args system "make", "install"
For AivenClient:
assert_match "aiven-client", shell_output("#{bin}/avn --version") assert_match "UserError: not authenticated", pipe_output("AIVEN_CONFIG_DIR=/tmp #{bin}/avn user info 2>&1")
system "make", "install"
### caveats
If your package requires sudo to run, please remind users with a statement such as this from htop:
def caveats <<~EOS This requires root privileges to correctly display all running processes, so you will need to run `sudo htop`. You should be certain that you trust any software you grant root privileges. EOS end
### test do
test do</strong> defines the CLI command(s) to verify proper installation:
assert_predicate bin/"Zzz", :exist?
pipe_output("#{bin}/htop", "q", 0)
system "#{bin}/tree", prefix
assert_equal "2\n", pipe_output("#{bin}/jq .bar", '{"foo":1, "bar":2}')
system bin/"wget", "-O", "/dev/null", "https://google.com"
### zap to Trash
If the install generates folders and files not needed to run, (especially cask apps), add:
zap trash: [ "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.tinynudge.pomello.*", "~/Library/Application Support/Pomello", "~/Library/Caches/com.tinynudge.pomello", "~/Library/Caches/com.tinynudge.pomello.ShipIt", "~/Library/HTTPStorages/com.tinynudge.pomello", "~/Library/Preferences/com.tinynudge.pomello.plist", "~/Library/Saved Application State/com.tinynudge.pomello.savedState", ]
Preparations: XCode CLI
-
Make a full backup of your system right before following the instructions below.
-
Open the App Store to install XCode, Apple’s IDE for developing Swift and Objective-C to run on iPhones and iPads.
PROTIP: Apple’s App Store only installs .app files. So programs invoked from the command line Terminal (such as gcc) need to be installed a different way.
-
See this XCode tutorial of mine about ensuring that XCode CLI commands were installed.
-
WARNING: Since the El Capitan version of macOS, file permissions in /usr/local have changed, causing error messages such as:
The linking step did not complete successfully The formula built, but is not symlinked into /usr/local
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
So in a Terminal shell window at any folder:
sudo chown -R :staff /usr/local
Install Homebrew
Homebrew makes use of Ruby, which comes with macOS.
-
Install Homebrew if you haven’t already.
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
CAUTION: Don’t press Enter on the Terminal until the Download Software dialog reaches 100%.
-
Press the Enter key to the message:
Press RETURN to continue or any other key to abort. then -
To proceed, enter the root password, or type Ctrl+C to abort.
NOTE: The download is from
https://github.com/Homebrew/homebrew/HISTORICAL NOTE: Previously, the Homebrew installer was at
https://raw.github.com/Homebrew/homebrew/go/install/ -
Identify where the Homebrew program itself is located:
which brew
The response is the brew executable program at:
/usr/local/bin/brew
The “brew” above is a shell script file.
PROTIP: The “/usr/local” is the default specified by the $HOMEBREW_PREFIX environment variable.
-
Identify where the Homebrew program stores packages:
brew --repository
The response:
/usr/local/Homebrew
Update Homebrew itself
-
Get Homebrew version:
brew -v
The response (at time of writing):
Homebrew 2.1.4 Homebrew/homebrew-core (git revision 07aa4; last commit 2019-06-04) Homebrew/homebrew-cask (git revision 1a93c; last commit 2019-06-04)
NOTE: Homebrew is open-sourced at
https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Common-Issues.md -
To update Homebrew itself and its formulas:
brew update
brew updateYes, run it twice to make sure all dependencies took.
Each run can take several minutes.
Formulas
Popular formulas
wget
iterm2
htop
geoip
nmap
Search for a formula to install
-
Use an internet browser (such as Google Chrome) to view formula defined in
-
http://braumeister.org provides recent activity.
Install formula
-
-
Install the wget command-line utility by formula name (for example, wget):
brew install wget
This installs to folder /usr/local/bin/wget.
See Tips & Tricks on how to use proxy, remove the beer mug emoji, highlighting within editors, etc.
How many?
-
Get a count of kegs, how many files, and the disk space they take:
brew info --all
A sample response:
15 kegs, 18,528 files, 540.6M
Where did it go?
-
List where .tar.gz “bottle” files are downloaded into from the internet:
DEFINITION: A “Bottle” is a pre-built binary Keg used for installation instead of building from source. It can be unpacked.
brew --cache
The response includes your user name, which enables Homebrew to work without using sudo (elevation to root).
/Users/mac/Library/Caches/Homebrew
The equivalent of the above is:
~/Library/Caches/Homebrew
-
List bottles downloaded:
ls ~/Library/Caches/Homebrew
Examples of responses:
autoconf-2.69.el_capitan.bottle.4.tar.gz maven-3.3.9.tar.gz awscli-1.10.44.el_capitan.bottle.tar.gz node-6.2.2.tar.xz docker-1.11.2.el_capitan.bottle.tar.gz openssl-1.0.2h_1.el_capitan.bottle.tar.gz gimp-2.8.16-x86_64.dmg pkg-config-0.29.1.el_capitan.bottle.tar.gz libgpg-error-1.23.el_capitan.bottle.tar.gz readline-6.3.8.el_capitan.bottle.tar.gz libksba-1.3.4.el_capitan.bottle.tar.gz
-
List brew formulas installed:
ls /usr/local/Cellar
brew list
brew lsThe alternative commands above all do the same thing of the same folder, for example:
autoconf awscli libgpg-error libtool maven node pkg-config xz automake docker libksba libyaml mysql openssl readline
There is no response if no brew package has been installed.
-
See one level below one of the above folders for a specific formula, such as openssl:
ls /usr/local/Cellar/openssl/
It is usually a version number, such as:
1.0.2h_1
DEFINITION: A “Keg” is the installation prefix of a formula, such as:
openssl-1.0.2h_1.el_capitan.bottle.tar.gz
Packages
-
List brew package .rb (Ruby language) files installed:
ls /usr/local/Library/Taps/homebrew/homebrew-core/Formula
The response is a long list.
-
List brew package folders:
brew search
The response is a long list.
Troubleshoot Homebrew
-
Different ways to install weget.
The above is one of several ways to install the wget command-line utility.
One way is to install Apple’s Xcode.
brew install --build-from-source wget
Test wget operating:
cd ~/Downloads
wget http://ftp.gnu.org/gnu/wget/wget-1.15.tar.gz -
Verify brew installation:
brew doctor
If you see this message at the top of messages returned:
Warning: Unbrewed header files were found in /usr/local/include. If you didn't put them there on purpose they could cause problems when building Homebrew formulae, and may need to be deleted.
The above may occur if curl and nodejs were installed without using homebrew.
Remove them before installing node and curl using Homebrew:
rm -rf /usr/local/include/node/
-
Create symlinks to installations performed manually in Cellar. This allows you to have the flexibility to install things on your own but still have those participate as dependencies in homebrew formulas.
First, see what exactly will be overwritten, without actually doing it:
brew link --overwrite --dry-run openssl
The response is:
Warning: openssl is keg-only and must be linked with --force Note that doing so can interfere with building software.
“Keg-only” refers to a formula installed only into the Cellar and not linked into /usr/local, which means most tools will not find it. This is to avoid conflicting with the system version of the same package.
Alternately, if aswcli is specified for dry-run, the response is, on an Intel x86 mac:
Warning: Already linked: /usr/local/Cellar/awscli/1.10.44 To relink: brew unlink awscli && brew link awscli
brew link
NOTE: Homebrew installs to the Cellar it then symlinks some of the installation into /usr/local so that other programs can see what’s going on.
A symlink to the active version of a Keg is called an “opt prefix”.
-
List where a link goes:
ls -l $(which wget)
/usr/local/bin/wget -> /usr/local/Cellar/wget/1.18/bin/wget
Prune symlinks
If you see this message:
Warning: Broken symlinks were found. Remove them with `brew prune`:
brew prune
A sample response:
Pruned 1598 symbolic links and 185 directories from /usr/local
-
List formula (package definitions):
brew edit $FORMULA
The above command brings you to your default text editor (vim or whatever is specified in the $EDITOR variable).
Type :q to quit out.
Upgrade brew formulas
-
List brew packages that are obsolete:
brew outdated
To stop a specific package from being updated/upgraded, pin it:
brew pin $FORMULA
$FORMULA is ???
To allow that formulae to update again, unpin it.
-
Download and update ALL software packages installed:
brew upgrade
-
To see which files would be removed as no longer needed:
brew cleanup -n
No response if there is nothing to clean. Otherwise, example:
Warning: Skipping awscli: most recent version 1.16.170 not installed
-
To really remove all files no longer needed:
brew cleanup
A sample response:
Removing: /Users/mac/Library/Caches/Homebrew/mariadb-10.1.14.el_capitan.bottle.tar.gz... (36.6M) ==> This operation has freed approximately 36.6M of disk space.
Remove/Uninstalll
PROTIP: Before deleting, identify its dependencies. For example:
brew deps python3</strong> would yield:gdbm openssl readline sqlite xzTwo delete commands does the same:brew uninstall packagebrew remove packageAdditional flags: `–force` or `-f` forcibly removes all versions of that package. `–ignore-dependencies` ignore dependencies for the formula when uninstalling the designated package, which may cause other brews to no longer work correctly. ## Tap # Brew tap adds repos not in the Homebrew master repo from inside a larger package. https://github.com/Homebrew/brew/blob/master/docs/brew-tap.md says tap adds to the list of formulae that brew tracks, updates, and installs from. 1. List brew tap packages already installed:brew tap1. Install the ip tool included with iproute2 on Linux:brew tap brona/iproute2mac brew install iproute2macThe command specififies the account and repo in GitHub, as in
https://github.com/brona/iproute2mac or https://superuser.com/questions/687310/ip-command-in-mac-os-x-terminalifconfig en0 | grep inet | grep -v inet6 | cut -d ' ' -f21. Try it (instead of ifconfig):ip ip addr show en01. Remove a tap:brew untap brona/iproute2mac## brew install --cask # Homebrew cask extends homebrew and brings its elegance, simplicity, and speed to MacOS (OS X) GUI applications and large binaries. https://caskroom.github.io With Cask, you can skip the long URLs, the "To install, drag this icon…", and manually deleting installer files. 1. Temporarily set the permissions on /usr/local:sudo chown $USER /usr/local1. Install brew cask:brew tap caskroom/cask brew install brew-caskApplications are kept in their Caskroom under /opt and symblinked to $HOME/Applications from https://github.com/caskroom/homebrew-cask 1. https://caskroom.github.io, the home page, said there are 3,197 casks as of June 5, 2016. QUESTION: Is there a graph of growth in cask counts over time? 1. Install the cask extension to Homebrew:brew tap caskroom/caskAlternately:ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null ; brew install caskroom/cask/brew-cask 2> /dev/null1. Search for a cask by name, in website is where casks are obtained: https://github.com/caskroom/homebrew-cask/search?utf8=✓ Alternately, run a search command. This example searches for "yo":brew cask search yoPROTIP: One should see the cask definition before using it. I would be suspicious of casks with sparse information. The safe way to get the homepage URL of the programmer is from here (don't Google it and end up at a rogue site). 1. Look at some cask definitions: https://github.com/caskroom/homebrew-cask/blob/master/Casks/google-chrome.rb is a sample cask definition: 1. Install the cask: brew install --cask google-chrome Cask downloads then moves the app to the ~/Applications folder, so it can be opened this way: 1. Open the installed cask from Terminal:open /Applications/"Google Chrome.app"1. Installing with cask enables you to cleanup:brew cask cleanup### Error prevention If you get an error about "permissions denied": 1. Create a Caskroom foldercd ~ mkdir Caskroom1. Edit the .bash_profilevim ~/.bash_profile1. Add this line:export HOMEBREW_CASK_OPTS="--appdir=~/Applications --caskroom=~/Caskroom"QUESTION: The use of --caskroom is deprecated? 1. Save the file. 1. Restart the terminal.source ~/.bash_profile## GUI for Homebrew packages https://www.cakebrew.com/ is a GUI to help manage Homebrew packages:brew install --cask cakebrew## Analytics off Homebrew now defaults to retrieving behavioral analytics tracking. Although anonymized, you may not want to participate in that. To disable the extra network traffic:brew analytics off## Debian apt-get Download Fink commander Fink Installer.pkg from
http://finkcommander.sourceforge.net/help/install.php This explains: Fink stores data in the directory “/sw” by default. This goes against the Filesystem Hierarchy Standard’s recommendation to use “/usr/local”. Within Fink’s directory, a FHS-like layout (/sw/bin, /sw/include, /sw/lib, etc.) is used. ## Documentation # 1. For more documentation on brew, look here and: man brew ## Social media # Social media from brew's readme: * @MacHomebrew on Twitter * IRC freenode.net#machomebrew * Email homebrew-discuss@googlegroups.com * Read archive of emails at https://groups.google.com/forum/#!forum/homebrew-discuss ## Resources * https://docs.brew.sh/Formula-Cookbook * https://rubydoc.brew.sh/Formula ## More on macOS This is one of a series on macOS: * [MacOS Setup step-by-step, with automation](/mac-setup/) * [MacOS Hardware and accessories](/apple-macbook-hardware/) * [MacOS dotfiles for System Preferences setup automation](/dotfiles/) * [MacOS Homebrew installers](/macos-homebrew/) * [MacOS Boot-up](/macos-bootup/) * [MacOS Versions](/apple-mac-osx-versions/) * [MacOS Keyboard tricks](/apple-mac-osx-keyboard/) * [MacOS Terminal Tips and Tricks](/mac-osx-terminal/) * [MacOS Find (files and text in files)](/find/) * [Text editors and IDEs on MacOS](/text-editors/) * [MacOS Xcode.app and CommandTools (gcc)](/xcode/) * [MacOS Command-line utilities](/mac-utilities/) * [Task Runners Grunt and Gulp](/task-runners/) * [Applications on MacOS](/macos-apps/) * [1password on MacOS](/1password/) * [Data Backups on MacOS](/apple-mac-osx-backup/) * [Manage Disk Space on MacOS](/mac-diskspace/) * [Screen capture on MacOS](/screen-capture-apple-mac-osx/) * [Printing from macOS or Linux](/printing/) * [Ports open](/ports-open/) * [MacOS iPhone integration](/mac-iphone/) * [MacOS within AWS](/macos-aws/) * [Linux and Windows on Apple MacOS](/windows-on-mac/) * [Packer create Vagrant Windows image](/packer/) * [Remote into Windows](/rdp/) * [Python on MacOS](/python-install/) * [Maven on MacOS](/maven-on-macos/) * [Ruby on MacOS](/ruby-on-apple-mac-osx/) * [Node on MacOS installation](/node-osx-install/) * [PHP on MacOS](/php-on-apple-mac-osx/) * [Java on MacOS](/java-on-apple-mac-osx/) * [Scala ecosystem](/scala-ecosystem/)