Back to Brew

Language-Specific Formulae

docs/Language-Specific-Formulae.md

6.0.129.8 KB
Original Source

Language-Specific Formulae

This guide covers the language-specific parts of formula authoring. The Formula Cookbook, Acceptable Formulae and shared package acceptance policy still apply.

Shared requirements

  • Declare every language runtime needed at build or runtime rather than relying on the contributor's environment.
  • Use immutable, checksummed sources and a reproducible dependency set.
  • Install application dependencies inside the formula prefix so they do not modify a user's global language environment.
  • Do not let the installed application download required code automatically at runtime.
  • Add a functional test that exercises installed behaviour rather than only checking that an executable exists.

Ordinary libraries already handled well by a language package manager are generally not useful as standalone formulae. Command-line applications, substantial native libraries and bindings needed by other formulae can be appropriate when they meet the acceptance policy.

Python

Applications, libraries and bindings

Python applications provide user-facing behaviour and are good formula candidates even when they are published on PyPI. Users should not need to know that an application is implemented in Python or manually add its modules to sys.path.

Ordinary importable libraries should normally be installed in a project environment with pip rather than packaged as formulae. A library may be appropriate when it has a substantial native build, is required by other formulae or needs Homebrew-specific integration. Bindings supplied by a non-Python project may be installed with that project when they are useful and maintainable.

Python dependency and resources

Python applications in homebrew/core use its current versioned Python formula. Update the dependency when homebrew/core moves to a new supported Python minor version.

All Python module dependencies and their recursive dependencies that are not provided by another formula must be declared as resources. This keeps every source version and SHA-256 checksum in the formula, and Homebrew's pip helper installs with dependency resolution disabled.

Use brew update-python-resources <formula> to generate or update the resource blocks. Use --print-only to inspect the result without changing the formula. For third-party taps, --ignore-errors records every discovered resource and leaves a RESOURCE-ERROR comment for each one that cannot be resolved. This option is disabled for all official Homebrew taps, which require complete resolution. Verify the generated URLs and checksums.

The pypi_packages stanza records resolver configuration that should remain with the formula. Use package_name when the formula name or URL does not identify the correct PyPI package, extra_packages for additional dependency roots, exclude_packages for packages provided by another formula and dependencies for formulae that must be installed while resources are resolved:

ruby
pypi_packages package_name:     "upstream-name",
              extra_packages:   "extra-package",
              exclude_packages: "package-from-homebrew",
              dependencies:     "resolver-dependency"

Installing a Python application

Include Language::Python::Virtualenv and use virtualenv_install_with_resources for the standard application layout. The examples use 3.y as a placeholder for the current Python minor version used by homebrew/core:

ruby
class Foo < Formula
  include Language::Python::Virtualenv

  desc "Example Python command-line application"
  homepage "https://example.com/foo"
  url "https://files.pythonhosted.org/packages/.../foo-1.0.tar.gz"
  sha256 "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"

  depends_on "[email protected]"

  resource "dependency" do
    url "https://files.pythonhosted.org/packages/.../dependency-1.2.3.tar.gz"
    sha256 "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"
  end

  def install
    virtualenv_install_with_resources
  end

  test do
    assert_match version.to_s, shell_output("#{bin}/foo --version")
  end
end

The helper creates a virtual environment under libexec, installs every resource and links newly installed commands into bin. Use its start_with:, end_with: and without: keywords when only the resource order or selection differs. Use virtualenv_create directly when additional installation steps require explicit calls to pip_install or pip_install_and_link.

Installing Python bindings

Declare the same versioned Python dependency used by other current homebrew/core formulae. For a standard pyproject.toml or setup.py package, install through the declared interpreter and Homebrew's pip arguments:

ruby
system "python3.y", "-m", "pip", "install", *std_pip_args(build_isolation: true), "./source/python"

std_pip_args delays packages published within Homebrew's release-cooldown period, disables automatic dependency resolution and binary wheels and installs only under the requested prefix. Match the executable to the declared dependency whenever the Python minor version changes. Use upstream build-system options to direct bindings into the formula prefix rather than patching global Python paths.

Build-system integration for Python bindings

When more than one Python is available in the dependency graph, pass the declared interpreter to the build system explicitly. For CMake, use the variable recognised by upstream's discovery module, commonly Python3_EXECUTABLE, Python_EXECUTABLE or the older PYTHON_EXECUTABLE. For Meson, check how upstream calls find_installation() and use its supported options to select the interpreter. If Meson cannot infer Homebrew's installation directories, set python.purelibdir or python.platlibdir to a path inside the formula prefix. For Autotools projects, use an upstream --with-python option when available or disable the build-system installation and install the bindings with the declared interpreter and std_pip_args.

Node.js

Source and dependency

Prefer the release tarball published to the npm registry when it contains the complete distributable application. Registry tarballs normally omit development-only files and include upstream's published build output. Use the exact tarball URL and SHA-256 checksum for the packaged version. The usual registry URL has the form https://registry.npmjs.org/<name>/-/<name>-<version>.tgz.

Applications compatible with the current Node.js release should declare:

ruby
depends_on "node"

Use a versioned Node.js formula only when upstream documents that requirement and that formula remains supported.

Standard npm installation

Install a normal npm application into libexec and link its executables:

ruby
class Foo < Formula
  def install
    system "npm", "install", *std_npm_args
    bin.install_symlink libexec.glob("bin/*")
  end
end

std_npm_args uses Homebrew's npm cache, applies the package release cooldown, builds native dependencies from source and installs in npm's global layout under libexec. It ignores lifecycle scripts by default to reduce the amount of package code executed during installation.

If the package requires an install-phase lifecycle script such as postinstall, review every enabled script and dependency before using:

ruby
system "npm", "install", *std_npm_args(ignore_scripts: false)

Explain why those scripts are necessary in the pull request.

Use a local npm layout when npm is only one stage of a larger build:

ruby
system "npm", "install", *std_npm_args(prefix: false)

Continue the upstream build, then install the resulting files into the formula prefix explicitly.

Native addons

A dependency tree containing native addons also needs the tools required by node-gyp. Declare Python as a build dependency when the build invokes it:

ruby
depends_on "python" => :build

Native addons are tied to a Node.js ABI. Add a functional test that exposes an incompatible Node.js major version so maintainers know when the formula needs a revision bump.

Java

Declare the JDK used to build or run the software. Use openjdk for software that supports the current JDK, or a supported versioned formula when upstream requires a specific release:

ruby
depends_on "openjdk@21"

Use a build-only dependency only when the installed software does not need Java at runtime.

Wrap installed commands with Language::Java.java_home_env when they must always use the declared JDK:

ruby
bin.env_script_all_files libexec/"bin", Language::Java.java_home_env("21")

Use Language::Java.overridable_java_home_env when the declared JDK should be the default but upstream supports a user-selected JAVA_HOME:

ruby
(bin/"foo").write_env_script libexec/"bin/foo", Language::Java.overridable_java_home_env("21")

The helper's version must match the formula dependency. Do not embed a Cellar path or a macOS-only JDK location in an installed script.

Java bytecode published by upstream may be installed directly when it meets the formula acceptance policy. When building with Maven, Gradle or another tool, ensure its dependency inputs are versioned and reproducible.

Ruby

Use Bundler with an upstream Gemfile.lock when it records the complete dependency set. Install the bundle under libexec rather than into the user's gem environment:

ruby
ENV["GEM_HOME"] = libexec
ENV["BUNDLE_WITHOUT"] = "development"
system "bundle", "install"

Install commands from that environment and preserve GEM_HOME in their wrappers:

ruby
bin.install libexec/"bin/foo"
bin.env_script_all_files libexec/"bin", GEM_HOME: ENV.fetch("GEM_HOME")

If upstream does not provide a usable lock file, declare immutable, checksummed resources or use another reproducible installation method accepted for that ecosystem.