Jim Kubicek

Octo-Impressive

Creating a Homebrew Formula for a Python Project

I just finished writing a homebrew formula for a python script I wrote, I’m going to walk through how I did it. Hopefully this helps anyone else who is thinking about distributing their code.

Why did I use Homebrew and not Pip? Two reasons: first, my code requires some Java .jar files. The front end is python, but a big chuck of the code is Java, so I didn’t have any particular allegiance to Pip. Second, I think Homebrew is the most common package distribution method on OS X. Not everyone will have Pip installed, but anyone who can use my script, will have Homebrew installed (It is the easiest way to install Keybase, which I require).

As an example, I’m going to write a Formula to install my python script Switters.

First, you should push your package to Github. This will give you both a place to host your downloads and a homepage URL that you will need to add to your Formula. Homebrew requires that you version your project, so make sure your commit is tagged with a version.

You’ll need to find the download link for your repo. Checkout the Releases page on github for the download URL. It should look something like https://github.com/jkubicek/Switters/archive/0.1.2.tar.gz.

Now, run the command to create your formula.

1
brew create https://github.com/jkubicek/Switters/archive/0.1.1.tar.gz

This creates a formula at $HOMEBREW_REPOSITORY/Library/Formula/switters.rb and opens it in your text editor. If you get a warning, “Version cannot be determined from URL”, add the correct version to the formula with version '0.1.1'. You’ll also need to add your homepage. It’s fine to use the project’s Github page here.

Homebrew recomends that you explicitly add your python dependancies. For dependancies hosted on PyPI that means you’ll need to browse PyPI to find the download URL for your resource. Here’s an example resource entry for Tweetpony:

1
2
3
4
resource "tweetpony" do
  url "https://pypi.python.org/packages/source/T/TweetPony/tweetpony-1.5.0.tar.gz"
  sha1 "c6e217f2676e9980a5ad01d0da125f0e1914398a"
end

The URL is straight from the PyPI page. PyPI only gives you the MD5 hash, which is no longer recommended for Homebrew. To get the sha1 hash, use shasum.

1
2
curl -O https://pypi.python.org/packages/source/T/TweetPony/tweetpony-1.5.0.tar.gz
shasum tweetpony-1.5.0.tar.gz

Copy the resultant SHA into your forumla.

Now you’ll need to check the build system. Don’t bother with Homebrew’s interactive installation. It doesn’t run your install method, so it doesn’t let you debug the installation steps. Instead, run a regular install command with a few helpful flags. The results of the --verbose flag are obvious. The --debug flag will stop the installation on errors and give you an interactive prompt. From here you may print a stack trace or open up an IRB session. Very helpful.

1
brew install --verbose --debug switters

Take note of the SHA that Homebrew reports, you should add this to your Formula.

If everything goes well, the script should have run successfully and… nothing should have been installed. You’ve got to add the installation method.

First, let me explain the structure of my Switters project. I’ve got an executable at the top level of my project. There are two local python modules, switterslib and zxing and two Java .jars located in zxing_java. In order for everything to work, I’ve got to make sure my imports from PyPI are installed correctly and that the local resources are put in the correct spot. First, lets get our remote resources setup.

1
2
3
4
5
6
ENV.prepend_create_path "PYTHONPATH", libexec/"vendor/lib/python2.7/site-packages"
%w[tweetpony qrcode requests].each do |r|
  resource(r).stage do
  system "python", *Language::Python.setup_install_args(libexec/"vendor")
  end
end

What does this do? First, we append HOMEBREW_PREFIX+"Cellar"+name+version+"libexec/vendor/lib/python2.7/site-packages onto the PYTHONPATH environment variable. This ensures that Python will be aware of this directory and look here for the necessary modules. We’ll see how that works in a bit. Next, we iterate through our resources, create a stage environment, and call a special Homebrew python command that installs our dependancies into libexec/vendor. After installation, the actual location of these libraries will be libexec/vendor/lib/python2.7/site-packages, hence the environment variable we set as our first step.

1
2
3
4
ENV.prepend_create_path "PYTHONPATH", libexec
libexec.install Dir["switterslib"]
libexec.install Dir["zxing"]
libexec.install Dir["zxing_java"]

Here, we append the raw libexec path onto PYTHONPATH and copy the switterslib, zxing and zxing_java directories into it. This ensures that when our formula is installed, our python script can find these packages.

1
2
bin.install "switters"
bin.env_script_all_files(libexec/"bin", :PYTHONPATH => ENV["PYTHONPATH"])

Finally, we install our script into the bin directory and run the env_script_all_files command on everything in the bin directory. This creates a shell script that sets up our python environment (hence having to add specific directories into PYTHONPATH) and calls each script in the bin directory. This script is located in the HOMEBREW_PREFIX+"Cellar"+name+version+"/bin" dir. Everything in this directory is then symlinked into /usr/local/bin/ which is (hopefully) in your PATH and now you’ll be able to call the script from anywhere!

Comments