Brew what? Link to heading
Being a MacOS user for the last 6 years, one of the things I install almost immediately is Homebrew. Homebrew is the de facto package manager for Mac and provides a nifty and intuitive way to install binaries through what they call formulae. Besides installing binaries Homebrew also supports installing Mac applications with the built-in cask functionality.
A collection of formulae is distributed trough a tap (hence the ๐บ gimmick). There is a tap containing the core formulae, one with drivers, one with fonts and even one with solely eid-software. More information on hosting your own tap can be found here.
Brew your own formula Link to heading
Although being a heavy user of the ecosystem it wasn’t until this week I actually had the need to create my own formula. Finding out about this amazing open source project awsweeper (a tool written in go to clean up resources in your AWS account) I felt it would be convenient to have it it installable as a Homebrew formula. Being flabbergasted by the ease of it I wanted to share the process of creating a formula and submitting a pull request to the Homebrew core tap.
Create a local version of the formula Link to heading
As with all great software Homebrew uses some sane default convenient to the majority of users. If the source code you want to wrap in a formula is hosted on github (or any other publicly accessible source) it is as simple as running brew create https://github.com/cloudetc/awsweeper/archive/v0.2.0.tar.gz
which point to a tarball containing the release you want to package. Releases of a github project can be found on the releases page of a project available at the /releases url of the project. In my case this is: https://github.com/cloudetc/awsweeper/releases.
This results in the creation a local version of the formula based on the homebrew template:
1# Documentation: https://docs.brew.sh/Formula-Cookbook
2# https://www.ruby"oc.info/github/Homebrew/brew/master/Formula
3# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
4class Awsweeper < Formula
5 desc "A tool to clean out your AWS account"
6 homepage ""
7 url "https://github.com/cloudetc/awsweeper/archive/v0.2.0.tar.gz"
8 sha256 "e867ecc3df01fb799fe900bc587676460ade1752f63c9176542bb66d27a1833a"
9 # depends_on "cmake" => :build
10
11 def install
12 # ENV.deparallelize # if your formula fails when building in parallel
13 # Remove unrecognized options if warned by configure
14 system "./configure", "--disable-debug",
15 "--disable-dependency-tracking",
16 "--disable-silent-rules",
17 "--prefix=#{prefix}"
18 # system "cmake", ".", *std_cmake_args
19 system "make", "install" # if this fails, try separate make/make install steps
20 end
21
22 test do
23 # `test do` will create, run in and delete a temporary directory.
24 #
25 # This test will fail and we won't accept that! For Homebrew/homebrew-core
26 # this will need to be a test that verifies the functionality of the
27 # software. Run the test with `brew test awsweeper`. Options passed
28 # to `brew install` such as `--HEAD` also need to be provided to `brew test`.
29 #
30 # The installed folder is not in the path, so use the entire path to any
31 # executables being tested: `system "#{bin}/program", "do", "something"`.
32 system "false"
33 end
34end
The creation of this newly created file will be /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/awsweeper.rb
. As you see it parses the name of the package from the tar file you provided when running brew create
.
Homebrew formulae are basically simple ruby classes which inherit from a base class Formula
.
Don’t panic, the DSL is pretty simple and you won’t need any prior Ruby knowledge to get going.
Dissection of a formula Link to heading
There are 4 important parts in a Homebrew formula:
1. The metadata: Link to heading
desc "A tool to clean out your AWS account"
homepage ""
url "https://github.com/cloudetc/awsweeper/archive/v0.2.0.tar.gz"
sha256 "e867ecc3df01fb799fe900bc587676460ade1752f63c9176542bb66d27a1833a"
desc
: a one-liner describing the package that will be installed by the formula. It will fetch the description from the related Github project by default. Be aware that these are not always compliant with the naming conventions of Homebrew itself. For example the desc above will be rejected because it starts with the article a. Also there is a maximum length of 80 characters for this field. The style guide is enforced by Rubocop a static code linting tool for Ruby. You can find the configuration files for all these rules here.homepage
: The URL of the location where people can find more about the project. By default it will use the Website it found on the Github page of the project.url
: URL of the source code tarball.sha256
: The SHA-256 encrypted hash of the source code tar.
In the case of my awsweeper formula this becomes:
desc "Clear out your AWS account with this convenient tool"
homepage "https://github.com/cloudetc"
url "https://github.com/cloudetc/awsweeper/archive/v0.2.0.tar.gz"
sha256 "e867ecc3df01fb799fe900bc587676460ade1752f63c9176542bb66d27a1833a
name
and version
are not present. This is the case when both those fields can be parsed from the tarball’s name.2. External dependencies: Link to heading
# depends_on "cmake" => :build
depends_on "pkg-config"
) or when using a symbol specifying a dependency which can be fulfilled by one or more formulae, casks or other system-wide installed software e.g. depends_on :xcode => "9.3"
.Given the fact we are trying to build a Go package we will need to define Go as a build dependency.
So our dependency config will become:
depends_on "go" => :build
3. The install block: Link to heading
def install
# ENV.deparallelize # if your formula fails when building in parallel
# Remove unrecognized options if warned by configure
system "./configure", "--disable-debug",
"--disable-dependency-tracking",
"--disable-silent-rules",
"--prefix=#{prefix}"
# system "cmake", ".", *std_cmake_args
system "make", "install" # if this fails, try separate make/make install steps
end
go build
. Notice we will have to set the environment variable GOPATH
to point to the location where you are building the binary for the package. GOPATH
is the workspace directory where go looks for dependencies. It will also contain your own go source code.This is how our install command ends up looking:
def install
ENV["GOPATH"] = buildpath
awsweeper_path = buildpath/"src/github.com/cloudetc/awsweeper"
awsweeper_path.install buildpath.children
cd awsweeper_path do
system "go", "build"
bin.install "awsweeper"
end
end
awsweeper_path
refers to the go compliant path we set up for the build to be able to proceed. awsweeper_path.install buildpath.children
copies all the files extracted from the tarball to the given awsweeper_path
. The cd awsweeper_path
changes the current path to the given directory. system "go", "build"
makes a system call running the default build commando for go binaries. bin.install
marks which file should be copied to the bin folder of our formula, in this case the name of the binary is awsweeper
. After installing this formula Homebrew will make sure the binary becomes available on our path4. The test block: Link to heading
test do
# `test do` will create, run in and delete a temporary directory.
#
# This test will fail and we won't accept that! For Homebrew/homebrew-core
# this will need to be a test that verifies the functionality of the
# software. Run the test with `brew test awsweeper`. Options passed
# to `brew install` such as `--HEAD` also need to be provided to `brew test`.
#
# The installed folder is not in the path, so use the entire path to any
# executables being tested: `system "#{bin}/program", "do", "something"`.
system "false"
end
awsweeper
this could look something like this:test do
assert_match "0.1.1", shell_output("#{bin}/awsweeper --version ")
end
What’s brewing next? Link to heading
So I hope this post helped in understanding the anatomy of Homebrew formulas and can motivate you in writing your own. In case you wonder how we can get this formula into homebrew-core
you can find the explanation here.
Oh, and if you want to you know how my awsweeper
pull request is doing, you can check the status of my pull request here.