Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Formula-Cookbook.md 43.60 KiB

Formula Cookbook

A formula is a package definition written in Ruby. It can be created with brew create $URL where $URL is a zip or tarball, installed with brew install $FORMULA, and debugged with brew install --debug --verbose $FORMULA. Formulae use the Formula API which provides various Homebrew-specific helpers.

Homebrew Terminology

Term Description Example
Formula The package definition /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/foo.rb
Keg The installation prefix of a Formula /usr/local/Cellar/foo/0.1
opt prefix A symlink to the active version of a Keg /usr/local/opt/foo
Cellar All Kegs are installed here /usr/local/Cellar
Tap A Git repository of Formulae and/or commands /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core
Bottle Pre-built Keg used instead of building from source qt-4.8.4.mavericks.bottle.tar.gz
Cask An extension of homebrew to install macOS native apps /Applications/MacDown.app/Contents/SharedSupport/bin/macdown
Brew Bundle An extension of homebrew to describe dependencies brew 'myservice', restart_service: true

An Introduction

Homebrew uses Git for downloading updates and contributing to the project.

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. We suggest you brew ls a few of the kegs in your Cellar to see how it is all arranged.

Packages are installed according to their formulae, which live in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula. Check one out a simple one e.g. brew edit etl (or etl) or a more advanced one e.g. brew edit git or Git.

Basic Instructions

Make sure you run brew update before you start. This turns your Homebrew installation into a Git repository.

Before submitting a new formula make sure your package:

  • meets all our Acceptable Formulae requirements
  • isn't already in Homebrew (check brew search $FORMULA)
  • isn't in another official Homebrew tap
  • isn't already waiting to be merged (check the issue tracker)
  • is still supported by upstream (i.e. doesn't require extensive patching)
  • has a stable, tagged version (i.e. not just a GitHub repository with no versions).
  • passes all brew audit --new-formula $FORMULA tests.

Before submitting a new formula make sure you read over our contribution guidelines.

Grab the URL

Run brew create with a URL to the source tarball:

brew create https://example.com/foo-0.1.tar.gz

This creates /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/foo.rb and opens it in your $EDITOR. It'll look something like:

class Foo < Formula
  desc ""
  homepage ""
  url "https://example.com/foo-0.1.tar.gz"
  sha256 "85cc828a96735bdafcf29eb6291ca91bac846579bcef7308536e0c875d6c81d7"

  # depends_on "cmake" => :build

  def install
    system "./configure", "--prefix=#{prefix}", "--disable-debug", "--disable-dependency-tracking"
    # system "cmake", ".", *std_cmake_args
    system "make", "install"
  end
end

If brew said Warning: Version cannot be determined from URL when doing the create step, you’ll need to explicitly add the correct version to the formula and then save the formula.

Homebrew will try to guess the formula’s name from its URL. If it fails to do so you can override this with brew create <url> --set-name <name>.

Fill in the homepage

We don’t accept formulae without a homepage!

A SSL/TLS (https) homepage is preferred, if one is available.

Try to summarize from the homepage what the formula does in the description. Note that the description is automatically prepended with the formula name.

Check the build system

brew install -i foo

You’re now at new prompt with the tarball extracted to a temporary sandbox.

Check the package’s README. Does the package install with ./configure, cmake, or something else? Delete the commented out cmake lines if the package uses ./configure.

Check for dependencies

The README probably tells you about dependencies and Homebrew or macOS probably already has them. You can check for Homebrew dependencies with brew search. Some common dependencies that macOS comes with:

  • libexpat
  • libGL
  • libiconv
  • libpcap
  • libxml2
  • Python
  • Ruby

There are plenty of others; check /usr/lib for them.

We generally try to not duplicate system libraries and complicated tools in core Homebrew but we do duplicate some commonly used tools.

Special exceptions are OpenSSL and LibreSSL. Things that use either should be built using Homebrew’s shipped equivalent and our test bot's post-install audit will warn if it detects you haven't done this.

Homebrew’s OpenSSL is keg_only to avoid conflicting with the system so sometimes formulae need to have environment variables set or special configuration flags passed to locate our OpenSSL. You can see this mechanism in the clamav formula. Usually this is unnecessary because Homebrew sets up our build environment to favour finding keg_only formulae first.

Homebrew maintains a special tap that provides other useful system duplicates.

Important: $(brew --prefix)/bin is NOT on the $PATH during formula installation. If you have dependencies at build time, you must specify them and brew will add them to the $PATH or create a Requirement.

Specifying other formulae as dependencies

class Foo < Formula
  depends_on "pkg-config" => :run
  depends_on "jpeg"
  depends_on "boost" => "with-icu"
  depends_on "readline" => :recommended
  depends_on "gtk+" => :optional
  depends_on :x11 => :optional
end

A String (e.g. "jpeg") specifies a formula dependency.

A Symbol (e.g. :x11) specifies a Requirement which can be fulfilled by one or more formulae, casks or other system-wide installed software (e.g. X11).

A Hash (e.g. =>) specifies a formula dependency with some additional information. Given a single string key, the value can take several forms:

  • a Symbol (currently one of :build, :optional, :run or :recommended).

    • :build means that dependency is a build-time only dependency so it can be skipped when installing from a bottle or when listing missing dependencies using brew missing.

    • :optional generates an implicit with-foo option for the formula. This means that, given depends_on "foo" => :optional, the user must pass --with-foo in order to use the dependency.

    • :run can mean the dependency is only required at run, or it can be used to declare build dependencies such as pkg-config are needed at runtime as well, which will silence the audit warning. :run dependencies are currently available at build-time.

    • :recommended generates an implicit without-foo option, meaning that the dependency is enabled by default and the user must pass --without-foo to disable this dependency. The default description can be overridden using the normal option syntax (in this case, the option declaration must precede the dependency):

      option "with-foo", "Compile with foo bindings" # This overrides the generated description if you want to
      depends_on "foo" => :optional # Generated description is "Build with foo support"
  • a String or an Array String values are interpreted as options to be passed to the dependency. You can also pass an array of strings, or an array of symbols and strings, in which case the symbols are interpreted as described above, and the strings are passed to the dependency as options.

    depends_on "foo" => "with-bar"
    depends_on "foo" => %w{with-bar with-baz}
    depends_on "foo" => [:optional, "with-bar"]

Specifying conflicts with other formulae

Sometimes there’s hard conflict between formulae, and it can’t be avoided or circumvented with keg_only.

mbedtls is a good example formula for minor conflict.

mbedtls ships and compiles a "Hello World" executable. This is obviously non-essential to mbedtls’s functionality, and conflict with the popular GNU hello formula would be overkill, so we just remove it during the installation process.

pdftohtml provides an example of a serious conflict, where both formula ship an identically-named binary that is essential to functionality, so a conflicts_with is preferable.

As a general rule, conflicts_with should be a last-resort option. It’s a fairly blunt instrument.

The syntax for conflict that can’t be worked around is:

conflicts_with "blueduck", :because => "yellowduck also ships a duck binary"

Formulae Revisions

In Homebrew we sometimes accept formulae updates that don’t include a version bump. These include resource updates, new patches or fixing a security issue with a formula.

Occasionally, these updates require a forced-recompile of the formula itself or its dependents to either ensure formulae continue to function as expected or to close a security issue. This forced-recompile is known as a revision and inserted underneath the homepage/url/sha block.

Where a dependent of a formula fails against a new version of that dependency it must receive a revision. An example of such failure can be seen here and the fix here.

revisions are also used for formulae that move from the system OpenSSL to the Homebrew-shipped OpenSSL without any other changes to that formula. This ensures users aren’t left exposed to the potential security issues of the outdated OpenSSL. An example of this can be seen in this commit.

Version Scheme Changes

Sometimes formulae have version schemes that change such that a direct comparison between two versions no longer produces the correct result. For example, a project might be version 13 and then decide to become 1.0.0. As 13 is translated to 13.0.0 by our versioning system by default this requires intervention.

Where a version scheme of a formula fails to recognise a new version as newer it must receive a version_scheme. An example of this can be seen here.

Double-check for dependencies

When you already have a lot of formulae installed, it's easy to miss a common dependency. You can double-check which libraries a binary links to with the otool command (perhaps you need to use xcrun otool):

$ otool -L /usr/local/bin/ldapvi
/usr/local/bin/ldapvi:
/usr/local/opt/openssl/lib/libssl.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/local/lib/libglib-2.0.0.dylib (compatibility version 4201.0.0, current version 4201.0.0)
/usr/local/opt/gettext/lib/libintl.8.dylib (compatibility version 10.0.0, current version 10.2.0)
/usr/local/opt/readline/lib/libreadline.6.dylib (compatibility version 6.0.0, current version 6.3.0)
/usr/local/lib/libpopt.0.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/System/Library/Frameworks/LDAP.framework/Versions/A/LDAP (compatibility version 1.0.0, current version 2.4.0)
/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)

Specifying gems, Python modules, Go projects, etc. as dependencies

Homebrew doesn’t package already packaged language-specific libraries. These should be installed directly from gem/cpan/pip etc.

If you're installing an application then please use resources for all the language-specific dependencies:

class Foo < Formula
  resource "pycrypto" do
    url "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz"
    sha256 "f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c"
  end

  def install
    resource("pycrypto").stage { system "python", *Language::Python.setup_install_args(libexec/"vendor") }
  end
end

jrnl is an example of a formula that does this well. The end result means the user doesn't have use pip or Python and can just run jrnl.

homebrew-pypi-poet can help you generate resource stanzas for the dependencies of your Python application and gdm can help you generate go_resource stanzas for the dependencies of your go application.

Install the formula

brew install --verbose --debug foo

--debug will ask you to open an interactive shell if the build fails so you can try to figure out what went wrong.

Check the top of the e.g. ./configure output. Some configure scripts do not recognize e.g. --disable-debug. If you see a warning about it, remove the option from the formula.

Add a test to the formula

Please add a test do block to the formula. This will be run by brew test foo and the Brew Test Bot.

The test do block automatically creates and changes to a temporary directory which is deleted after run. You can access this Pathname with the testpath function. The environment variable HOME is set to testpath within the test do block.

We want tests that don't require any user input and test the basic functionality of the application. For example foo build-foo input.foo is a good test and (despite their widespread use) foo --version and foo --help are bad tests. However, a bad test is better than no test at all.

See cmake for an example of a formula with a good test. The formula writes a basic CMakeLists.txt file into the test directory then calls CMake to generate Makefiles. This test checks that CMake doesn't e.g. segfault during basic operation. Another good example is tinyxml2, which writes a small C++ source file into the test directory, compiles and links it against the tinyxml2 library and finally checks that the resulting program runs successfully.

Manuals

Homebrew expects to find manual pages in #{prefix}/share/man/..., and not in #{prefix}/man/....

Some software installs to man instead of share/man, so check the output and add a "--mandir=#{man}" to the ./configure line if needed.

A Quick Word on Naming

Name the formula like the project markets the product. So it’s pkg-config, not pkgconfig; sdl_mixer, not sdl-mixer or sdlmixer.

The only exception is stuff like “Apache Ant”. Apache sticks “Apache” in front of everything, but we use the formula name ant. We only include the prefix in cases like GNUplot (because it’s part of the name) and GNU Go (because everyone calls it “GNU go”—nobody just calls it “Go”). The word “Go” is too common and there are too many implementations of it.

If you’re not sure about the name check the homepage, and check the Wikipedia page and what Debian calls it.

Where Homebrew already has a formula called foo we typically do not accept requests to replace that formula with something else also named foo. This is to avoid both confusing and surprising users’ expectations.

When two formulae share an upstream name, e.g. AESCrypt and AESCrypt the newer formula must typically adapt the name to avoid conflict with the current formula.

If you’re still not sure, just commit. We’ll apply some arbitrary rule and make a decision :wink:.

When importing classes, Homebrew will require the formula and then create an instance of the class. It does this by assuming the formula name can be directly converted to the class name using a regexp. The rules are simple:

  • foo-bar.rb => FooBar
  • foobar.rb => Foobar

Thus, if you change the name of the class, you must also rename the file. Filenames should be all lowercase, and class names should be the strict CamelCase equivalent, e.g. formulae gnu-go and sdl_mixer become classes GnuGo and SdlMixer, even if part of their name is an acronym.

Add aliases by creating symlinks in an Aliases directory in the tap root.

Audit the formula

You can run brew audit --strict --online to test formulae for adherence to Homebrew house style. The audit command includes warnings for trailing whitespace, preferred URLs for certain source hosts, and a lot of other style issues. Fixing these warnings before committing will make the process a lot quicker for everyone.

New formulae being submitted to Homebrew should run brew audit --new-formula foo. This command is performed by the Brew Test Bot on new submissions as part of the automated build and test process, and highlights more potential issues than the standard audit.

Use brew info and check if the version guessed by Homebrew from the URL is correct. Add an explicit version if not.

Commit

Everything is built on Git, so contribution is easy:

brew update # required in more ways than you think (initializes the brew git repository if you don't already have it)
cd $(brew --repo homebrew/core)
# Create a new git branch for your formula so your pull request is easy to
# modify if any changes come up during review.
git checkout -b <some-descriptive-name>
git add Formula/foo.rb
git commit

The established standard for Git commit messages is:

  • the first line is a commit summary of 50 characters or less
  • two (2) newlines, then
  • explain the commit thoroughly

At Homebrew, we like to put the name of the formula up front like so: foobar 7.3 (new formula). This may seem crazy short, but you’ll find that forcing yourself to summarise the commit encourages you to be atomic and concise. If you can’t summarise it in 50-80 characters, you’re probably trying to commit two commits as one. For a more thorough explanation, please read Tim Pope’s excellent blog post, A Note About Git Commit Messages.

The preferred commit message format for simple version updates is foobar 7.3 and for fixes is foobar: fix flibble matrix..

Ensure you reference any relevant GitHub issue e.g. Closes #12345 in the commit message. Homebrew’s history is the first thing future contributors will look to when trying to understand the current state of formulae they’re interested in.

Push

Now you just need to push your commit to GitHub.

If you haven’t forked Homebrew yet, go to the homebrew-core repository and hit the fork button.

If you have already forked Homebrew on GitHub, then you can manually push (just make sure you have been pulling from the Homebrew/homebrew-core master):

git push https://github.com/myname/homebrew-core/ <what-you-called-your-branch>

Now, please open a pull request for your changes.

  • One formula per commit; one commit per formula
  • Keep merge commits out of the pull request

Convenience Tools

Messaging

Three commands are provided for displaying informational messages to the user:

  • ohai for general info
  • opoo for warning messages
  • odie for error messages and immediately exiting

In particular, when a test needs to be performed before installation use odie to bail out gracefully. For example:

if build.with?("qt") && build.with("qt5")
  odie "Options --with-qt and --with-qt5 are mutually exclusive."
end
system "make", "install"

bin.install "foo"

You’ll see stuff like that in other formulae. This moves the file foo into the Formula’s bin directory (/usr/local/Cellar/pkg/0.1/bin) and makes it executable (chmod 0555 foo).

inreplace

A convenience function that can edit files in-place. For example:

inreplace "path", before, after

before and after can be strings or regular expressions. You should use the block form if you need to make multiple replacements in a file:

inreplace "path" do |s|
  s.gsub! /foo/, "bar"
  s.gsub! "123", "456"
end

Make sure you modify s! This block ignores the returned value.

inreplace should be used instead of patches when it is patching something that will never be accepted upstream e.g. make the software’s build system respect Homebrew’s installation hierarchy. If it's something that affects both Homebrew and MacPorts (i.e. macOS specific) it should be turned into an upstream submitted patch instead.

If you need modify variables in a Makefile, rather than using inreplace, pass them as arguments to make:

system "make", "target", "VAR2=value1", "VAR2=value2", "VAR3=values can have spaces"
args = %W[
  CC=#{ENV.cc}
  PREFIX=#{prefix}
]

system "make", *args

Note that values can contain unescaped spaces if you use the multiple-argument form of system.

Patches

While patches should generally be avoided, sometimes they are necessary.

When patching (i.e. fixing header file inclusion, fixing compiler warnings, etc.) the first thing to do is check whether or not the upstream project is aware of the issue. If not, file a bug report and/or submit your patch for inclusion. We may sometimes still accept your patch before it was submitted upstream but by getting the ball rolling on fixing the upstream issue you reduce the length of time we have to carry the patch around.

Always justify a patch with a code comment! Otherwise, nobody will know when it is safe to remove the patch, or safe to leave it in when updating the formula. The comment should include a link to the relevant upstream issue(s).

External patches can be declared using resource-style blocks:

patch do
  url "https://example.com/example_patch.diff"
  sha256 "85cc828a96735bdafcf29eb6291ca91bac846579bcef7308536e0c875d6c81d7"
end

A strip level of -p1 is assumed. It can be overridden using a symbol argument:

patch :p0 do
  url "https://example.com/example_patch.diff"
  sha256 "85cc828a96735bdafcf29eb6291ca91bac846579bcef7308536e0c875d6c81d7"
end

patches can be declared in stable, devel, and head blocks. NOTE: always use a block instead of a conditional, i.e. stable do ... end instead of if build.stable? then ... end.

stable do
  # some other things...

  patch do
    url "https://example.com/example_patch.diff"
    sha256 "85cc828a96735bdafcf29eb6291ca91bac846579bcef7308536e0c875d6c81d7"
  end
end

Embedded (END) patches can be declared like so: