Packaging a Python CLI Tool for Homebrew
To package a Python CLI tool for Homebrew, create a GitHub tap, generate a formula using homebrew-pypi-poet, add tests, and optionally submit to Homebrew core for pre-compiled bottle distributions.
Quick Glance
- Tap: GitHub repository with
homebrew-prefix that gets tapped when users runbrew tap - Formula: Ruby
.rbfile in theFormula/folder defining how to install the package - homebrew-pypi-poet: Tool to automatically generate formulas from Python packages
- Test block: Required to verify basic functionality without user input
- Bottle: Pre-compiled distributions for faster Homebrew installs
Key Concepts
| Concept | Definition | Context/Example |
|---|---|---|
| Tap | GitHub repository with homebrew- prefix that users tap with brew tap |
https://github.com/simonw/homebrew-datasette for brew tap simonw/datasette |
| Formula | Ruby .rb file in the Formula/ folder defining package installation |
datasette.rb with dependencies, resources, and test block |
| Bottle | Pre-compiled .tar.gz bundles for each macOS version for faster installs |
Homebrew core provides bottles for all core packages |
Creating a Tap
- Create a GitHub repository with
homebrew-prefix - Repository URL becomes the tap:
https://github.com/simonw/homebrew-datasette - Users tap it with
brew tap simonw/datasette - Repository needs a
Formula/folder containing.rbformula files
Creating the Formula
- Every dependency must be listed as a resource
- All dependencies must have
sdistpackages on PyPI - Use
homebrew-pypi-poetto generate the formula - Must run in a fresh virtual environment to avoid including unrelated packages
Workflow:
- Create fresh virtual environment:
python -m venv venvand activate - Install tool and poet:
pip install strip-tags homebrew-pypi-poet - Generate formula:
poet -f strip-tags > strip-tags.rb - Test locally:
HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source --verbose --debug strip-tags.rb - Verify installation:
which strip-tags - Add to GitHub repo and test from tap:
brew uninstall strip-tags && brew install yourname/yourtap/strip-tags
Implementing the Test Block
- Tests should not require user input and verify basic functionality
- Avoid
--versionand--helptests (widely used but not ideal) - Example for Datasette:
test do
assert_match "15", shell_output("#{bin}/datasette --get '/:memory:.csv?sql=select+3*5'")
assert_match "<title>Datasette:", shell_output("#{bin}/datasette --get '/'")
end
Iterating During Development
- Run
brew install package, test, thenbrew uninstall package - Modify
.rbfile on GitHub - Run
brew install yourname/yourtap/packageagain - Use
brew install package --debugfor detailed error information and interactive debugging
Submitting to Homebrew-core
- If accepted, users can install with
brew install packagename - Homebrew provides bottles (pre-compiled) for all core packages
- For Python packages, remember to:
- Add a
licensefield (e.g.,license "Apache 2.0") - Run
brew audit --new-formula packageand fix warnings - Submit PR with title format:
package X.Y.Z (new formula)
- Add a
Key Takeaways
- Formula Generation: Use
homebrew-pypi-poetin a fresh virtual environment to automatically generate Homebrew formulas from Python packages. - Testing Requirements: Always include a test block that verifies basic functionality without user input.
- Development Workflow: Use
brew install --debugfor detailed error information and interactive debugging during development. - Core Submission: For Python packages, add a license field, run
brew audit, and submit a PR with the correct title format to get into Homebrew core. - Bottles Matter: Homebrew core provides pre-compiled bottles for faster installs across macOS versions.