Debugging Homebrew with Pry

Over the years I’ve written a few Homebrew formulas and sent the occasional Pull Request to update a formula or two. But I’ve never done any work within Homebrew. I’ve never needed to debug how Homebrew itself worked. Until now.

I assumed our typical Ruby debugging tools, like Pry and Pry-Byebug would work. Homebrew is just Ruby, after all. Which is true. But it’s also a bit special, and we can’t do the normal require "pry-byebug"; binding.pry tricks.

But we can, with a little poking around, still use those tools!

A Homebrew command

In this case, I was exploring a built-in command to see if I could extend it. The brew-postgres-upgrade-database command is a great little tool that ✨ automagically ✨ upgrades a PostgreSQL instance’s data between major versions.

$ brew postgresql-upgrade-database
==> Upgrading postgresql data from 11 to 12...
==> Moving postgresql data from /usr/local/var/postgres to /usr/local/var/postgres.old...
==> Creating database...
==> Migrating and upgrading data...
==> Upgraded postgresql data from 11 to 12!
==> Your postgresql 11 data remains at /usr/local/var/postgres.old

Except it only worked with the default PGDATA location. Which is sensible. But for… reasons, I had my PGDATA directory in a different location. I keep mine within my user directory at ~/.pg-data.

A first, failed attempt

The first step was figuring out where this command lived on my machine so I could hack at it, as a proof-of-concept. I needed it to allow an optional data directory.

$ brew --prefix
/usr/local

Knowing Homebrew was installed in /usr/local I went there and poked around until I found what I was looking for in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/cmd/brew-postgresql-upgrade-database.rb. Phew, that’s a mouthful!

From there I opened the code.

$ vim /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/cmd/brew-postgresql-upgrade-database.rb

Right above the existing code (snipped below for brevity) I tried the normal trick.

 #:  * `postgresql-upgrade-database`:
 #:    Upgrades the database for the `postgresql` formula.
+ 
+require "pry-byebug"; binding.pry
 
 name = "postgresql"
 pg = Formula[name]

Then I tried to run the command, hoping to pop a Pry session.

$ brew postgresql-upgrade-database
Error: cannot load such file -- pry-byebug
/usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/cmd/brew-postgresql-upgrade-database.rb:6:in `<top (required)>'
/usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/usr/local/Homebrew/Library/Homebrew/utils.rb:82:in `require?'
/usr/local/Homebrew/Library/Homebrew/brew.rb:108:in `<main>'

😔 Well, crap.

A second, successful attempt!

On a lark, I searched the Homebrew code base for references to Pry, hoping for an Issue or someone else talking about this. And it turns out, Homebrew has some Pry support built it.

 #:  * `postgresql-upgrade-database`:
 #:    Upgrades the database for the `postgresql` formula.
+ 
+ Homebrew.install_gem_setup_path! "pry"
+ Homebrew.install_gem_setup_path! "pry-byebug", executable: "pry"
+ require "pry-byebug"; binding.pry
 
 name = "postgresql"
 pg = Formula[name]

Leveraging that prior work I was able to get a full Pry Byebug session to pop.

$ brew postgresql-upgrade-database

From: /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/cmd/brew-postgresql-upgrade-database.rb @ line 10 :

     5: #:                    files. Same as the pg_ctl -D flag.
     6: Homebrew.install_gem_setup_path! "pry"
     7: Homebrew.install_gem_setup_path! "pry-byebug", executable: "pry"
     8: require "pry-byebug"; binding.pry
     9:
 => 10: name = "postgresql"
    11: pg = Formula[name]
    12: bin = pg.bin
    13: var = pg.var
    14: version = pg.version
    15: data_dir = options[:data_dir] || var/"postgres"

🎉 Huzzah!

And then, a Pull Request

From there it was a matter of stepping through, line by line, to understand how the command worked. Then some hackity-hacking to allow an optional data directory to be passed in. The net result was a Pull Request allowing you to upgrade a PostgreSQL database with a custom data directory.

$ brew postgres-upgrade-database --D ~/.pg-data
==> Upgrading postgresql data from 11 to 12...
==> Moving postgresql data from ~/.pg-data to ~/.pg-data.old...
==> Creating database...
==> Migrating and upgrading data...
==> Upgraded postgresql data from 11 to 12!
==> Your postgresql 11 data remains at ~/.pg-data.old

🤞 Fingers crossed that PR is merged.

Either way we (or at least I) learned how to debug Homebrew itself. Neat.