NeuroAgent

Build Homebrew Bottle from Local Formula in GitHub Actions

Learn how to build Homebrew bottles from local formula files in GitHub Actions. Solve the 'requires formulae to be in a tap' error with proven solutions and complete workflow examples.

How to build a Homebrew bottle from a local formula file in GitHub Actions?

I want to use GitHub Actions to build a bottle for a Homebrew formula that lives in the same repository. My current approach is:

yaml
- name: build package
  run: |
    brew install --build-bottle ./Formula/mypackage.rb
    brew bottle mypackage

However, this fails with the error:

Error: Homebrew requires formulae to be in a tap, rejecting:
  ./Formula/mypackage.rb (/Users/runner/work/homebrew-test/homebrew-test/Formula/mypackage.rb)

If I use brew tap owner/mytap and brew install --build-bottle mypackage, it works but uses the main branch instead of the development branch where I’m making changes.

How can I force Homebrew to build the bottle from the local file mypackage.rb in GitHub Actions?

NeuroAgent

To build a Homebrew bottle from a local formula file in GitHub Actions, you need to create a proper tap structure or use Homebrew’s tap creation commands. Homebrew requires formulae to be within a tap context rather than raw file paths, which is why your current approach fails with the “requires formulae to be in a tap” error.

Contents

Understanding the Tap Requirement

Homebrew’s bottle system is designed around taps, which are essentially Git repositories containing formulae. This structure allows Homebrew to manage dependencies, versions, and binary packages more effectively. When you try to install from a local file path like ./Formula/mypackage.rb, Homebrew rejects it because:

  • Bottles need to be associated with a specific tap for proper versioning
  • The tap structure provides metadata about dependencies and compatibility
  • Bottles are stored in tap-specific directories with standardized naming conventions

As Homebrew discussions explain, “Make sure Formula/my_formular.rb lives in a Homebrew tap (e.g. one created with brew tap-new), and refer to it by name instead of using a relative path.”

Solution 1: Create a Local Tap Structure

The most robust solution is to create a proper tap structure within your repository. This approach mimics how official Homebrew taps are organized.

  1. Create the tap directory structure:

    your-repo/
    ├── .github/
    │   └── workflows/
    │       └── build.yml
    ├── Formula/
    │   └── mypackage.rb
    └── Homebrew/
        └── tap.rb
    
  2. Create a simple tap.rb file:

    ruby
    # Homebrew/tap.rb
    require "tap"
    
    class YourTap < Tap
      url "https://github.com/yourusername/your-repo"
      sha256 "your_checksum" # You can generate this after the first build
    end
    
  3. Update your GitHub Actions workflow:

    yaml
    - name: build package
      run: |
        brew tap-new yourusername/your-repo
        brew install --build-bottle --formula ./Formula/mypackage.rb
        brew bottle mypackage
    

This approach allows you to build from your local formula while maintaining proper tap context.

Solution 2: Use brew tap-new

You can create a temporary tap in the GitHub Actions environment using brew tap-new:

yaml
- name: build package
  run: |
    brew tap-new yourusername/your-repo
    brew install --build-bottle --formula ./Formula/mypackage.rb
    brew bottle mypackage

This command creates a tap that your local formula can be associated with. The formula will be referenced by its full tap name (yourusername/your-repo/mypackage) rather than the file path.

Solution 3: Use brew bottle with Formula Path

According to the Stack Overflow solution, you can use the --formula flag with the local path:

yaml
- name: build package
  run: |
    brew install --build-bottle --formula ./Formula/mypackage.rb
    brew bottle --json --write ./Formula/mypackage.rb

The --json --write options generate the bottle DSL that can be inserted into your formula file. This approach directly addresses your original attempt by adding the necessary flags.

Complete GitHub Actions Workflow

Here’s a comprehensive workflow that addresses all aspects of bottle building:

yaml
name: Build Homebrew Bottle

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [macos-latest, macos-13, macos-12]

    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Homebrew
      uses: Homebrew/actions/setup-homebrew@master
    
    - name: Build bottle
      run: |
        # Create a temporary tap
        brew tap-new yourusername/your-repo
        
        # Build from local formula
        brew install --build-bottle --formula ./Formula/mypackage.rb
        
        # Generate bottle
        brew bottle mypackage
        
        # Upload artifacts
        find . -name "*.bottle.tar.gz" -exec sh -c 'echo "::set-output name=artifact::$(basename "$0")"' {} \ |
        while read artifact; do
          echo "::upload-artifact::${artifact}"
        done
    
    - name: Generate bottle DSL
      run: |
        brew bottle --json --write ./Formula/mypackage.rb
        git add ./Formula/mypackage.rb
        git config --local user.email "action@github.com"
        git config --local user.name "GitHub Action"
        git commit -m "Update bottle DSL" || true
        git push

This workflow:

  • Supports multiple macOS versions
  • Creates a proper tap context
  • Builds bottles from your local formula
  • Uploads artifacts as GitHub releases
  • Updates the formula with bottle metadata

Troubleshooting Common Issues

Dependency Issues: If your formula has dependencies that aren’t available in the standard Homebrew repository, you may need to install them first:

yaml
- name: Install dependencies
  run: |
    brew install dependency1 dependency2

Cellar Issues: Bottles need to be built in the correct cellar location. If you’re using a custom Homebrew installation:

yaml
- name: Set up custom Homebrew
  run: |
    echo '/usr/local/opt/python/libexec/bin' >> $GITHUB_PATH

Bottle Naming: Bottles must follow the naming convention {formula}-{version}-{os}-{arch}.bottle.tar.gz. The brew bottle command handles this automatically.

Best Practices

  1. Use Multiple OS Versions: Build bottles for different macOS versions to ensure compatibility
  2. Test Before Building: Always test your formula with brew install --dry-run before building bottles
  3. Update Bottle DSL: After building, update your formula with the generated bottle DSL
  4. Use GitHub Releases: Upload bottles as GitHub releases for easy distribution
  5. Handle Dependencies: Ensure all dependencies are properly specified and available

According to Jonathan Chang’s guide, “When a library your formula depends_on has breaking changes (e.g., Boost), you’ll need to increment the revision number; if this isn’t already present in your formula, just add revision 1.”

Sources

  1. Build Homebrew bottle in GitHub Actions - Stack Overflow
  2. Homebrew tap with bottles uploaded to GitHub Releases — Homebrew
  3. Create bottle from local Formula · Homebrew · Discussion #4468
  4. Maintain your own Homebrew repository, with binary bottles · Jonathan Chang
  5. Automate build workflow for Homebrew tap bottles (Linux and macOS) · GitHub Gist
  6. Bottles.md - Homebrew Documentation

Conclusion

Building Homebrew bottles from local formulae in GitHub Actions requires understanding Homebrew’s tap architecture. The key solutions are:

  1. Create a proper tap structure - This is the most reliable approach that mirrors official Homebrew practices
  2. Use brew tap-new - Creates a temporary tap context for your local formula
  3. Use --formula flag - Allows building from local paths with proper flags
  4. Generate bottle DSL - Updates your formula with bottle metadata after building

By implementing one of these solutions, you can successfully build bottles from your local formula files while maintaining compatibility with Homebrew’s bottle system. The recommended approach is to create a proper tap structure as it provides the most robust and maintainable solution for continuous integration workflows.