Mike McQuaid

Building Homebrew in Ruby: The Good, Bad and Ugly

Homebrew is a popular macOS package manager in the Ruby community and is also written in Ruby. As Homebrew isn't a web application and doesn't provide a Ruby library, the Ruby ecosystem works great for us in some ways and less great in others. Learn about things we love, hate and struggle with because Homebrew is built in Ruby.

RubyKaigi 2019 https://rubykaigi.org/2019/presentations/MikeMcQuaid.html#apr19

RubyKaigi 2019

00:00:00.030 Hello everyone! Can you hear me up there? I'm going to talk today about Homebrew and how we've built it in Ruby.
00:00:07.020 I'm going to ask for quite a lot of hands in the air because it helps me be sure that you're listening and provides feedback. Plus, it prevents you from falling asleep.
00:00:15.570 So, is anyone here using Homebrew? Awesome! Has anyone ever submitted a PR to Homebrew? Was it merged? That's sad, and it’s probably my fault. Does anyone like our new logo? Can you tell the difference? It's really different from our old logos.
00:00:34.110 In fact, who thinks the new logo is on the left? And who thinks the new logo is the one on the right? There was a designer who worked on Homebrew who explained that the glass on the left isn’t a real beer glass, whereas the one on the right is.
00:00:51.180 So, I'm Mike. I've met some of you already, and if I haven't met you yet, feel free to come and talk to me any time. I’ll be here until Saturday night.
00:01:10.860 I've been working on Homebrew for ten years now, which is quite exciting. I’m the project leader, and I’ve been at GitHub for just over five years, working on improving GitHub for open source.
00:01:22.500 I’m on a team working on some fun secret stuff that will ship in the next few months. If you're interested in talking about GitHub, open source, or Homebrew, then give me a shout. I've got my email address there, and feel free to email me—I'll reply unless you're being mean.
00:01:49.079 My coworker convinced me it's generally better to not reply and avoid arguments with people on the internet. So, feedback is always welcome.
00:02:02.219 As I mentioned, I want to talk today about Homebrew and Ruby.
00:02:07.920 You may or may not know that the first version of Homebrew was written in Ruby right from the outset. A big part of the reasoning for that was that Ruby was one of the languages included with macOS.
00:02:19.640 Max Howell, the creator of Homebrew, wanted to build something that didn’t require any dependencies to run on macOS. He had played around with several different languages and, much like myself, fell in love with Ruby, seeing it as a good fit for this project.
00:02:33.769 However, because I am Scottish, I can't start with nice positive things; we need to start with the negatives.
00:02:41.060 The first problem Homebrew has is a result of our desire to use the Ruby that is provided with macOS, which means we don’t really get to control our own version.
00:02:53.450 So, who here is running Ruby 2.6 in production at the moment? You lucky people! Ruby 2.6 is great—there’s lots of good stuff in there.
00:03:05.090 Unfortunately, until 2016, Homebrew had to work with Ruby 1.8. Even until earlier this year, our installer script, also written in Ruby, had to support Ruby 1.8.
00:03:15.709 Is anyone here still running Ruby 1.8 in production? A couple of people. Shout out to those at the back.
00:03:22.010 Ruby 1.8 is great, to be clear, but you know it’s 2019 now. We’re all meant to be in flying cars and using Ruby 2.x versions.
00:03:27.650 Currently, we are running Ruby 2.3, which is the version that ships with macOS. This is a step up from 2.0, which was the previous version we were using, and it elevated us from 1.8, the version before that.
00:03:45.859 And if you run ‘/usr/bin/ruby’ on macOS, that’s the version you get. We try to use this system version where possible.
00:03:56.060 If you have a sufficiently modern version of macOS, you can benefit from that; however, where you don’t, we have our solution: portable Ruby. This is a binary build of Ruby that we developed to avoid hard-coding paths anywhere.
00:04:20.659 You can install it anywhere on your macOS system, and it will behave nicely and just work.
00:04:26.510 Again, we still prefer to use the system stuff where we can.
00:04:31.640 Unfortunately, there are some quirks associated with this. For instance, people like me have to remember to compile and build this to ensure version compatibility, which can be a bit of a pain.
00:04:48.560 There’s also some weird stuff regarding gem installations. If you install gems with one version of the portable Ruby and then remove that version after upgrading macOS, they don’t always play nicely together.
00:05:10.640 For a while, frozen string literals have been an area of frustration for me, alongside some bugs that were eventually fixed. While I could request inclusion of these fixes, who knows if Apple will actually do it.
00:05:38.960 So for now, I’m living with having several comments at the top of my files for frozen string literals.
00:05:50.390 One of the nice aspects of being within the Ruby ecosystem is that although we are unique and operate in our own space, we rely on many tools that are commonplace.
00:06:06.720 For instance, we use IRB. If anyone here uses it too, please raise your hand. We also utilize the ‘ron’ gem for generating man pages.
00:06:27.400 We also work with ‘rubyprof’, a useful profiler, among other things. We’ve created small wrappers around these tools because of the way Homebrew operates.
00:06:40.220 Since Homebrew runs its own Ruby processes, everything has to be initialized with a variety of environment variables, initially set by bash, followed by executing the Ruby processes in the expected manner.
00:07:11.900 Additionally, who has heard of RSpec? Many of you, I see. Does anyone use Bundler’s standalone install mode? This was a recent revelation for me!
00:07:27.400 While we now use Bundler to install our gems, we previously had a convoluted process of people downloading packages, unzipping them, and adding them to GitHub, leaving us without version control. The initial intent was to ensure that Homebrew users didn’t have to know about Ruby or the process of manually installing gems.
00:08:24.440 Consequently, all Homebrew users should be able to install software seamlessly. It’s only when developers delve deeper into Homebrew or create a formula that they need to be aware of these technicalities.
00:09:00.290 I also learned about ‘gel’, a new tool that allows bundler to run faster, which I’ve recently started to explore to improve our speed.
00:09:12.440 We really want to minimize any manual installation burdens for users, as this can slow down the invocation of the Homebrew process.
00:09:28.959 Ultimately, improving the user experience is paramount, and while we previously had one gem file, we’ve managed to reduce it down to two gem files in our repository.
00:09:57.600 This includes all the necessary runtime dependencies, plus various development tools.
00:10:17.600 Through Bundler, we now generate a file that configures these dependencies seamlessly, without users needing to worry about runtime overhead or gem installations.
00:10:40.820 I should highlight that in 2019, Homebrew operates as a command-line application rather than a web application, which emphasizes the necessity for fast startup times.
00:11:06.920 For us, a quick initialization is essential, especially considering how often command-line tools are run.
00:11:30.220 Unlike Rails projects where overhead isn’t excessive, we need to prioritize speed to create a more efficient user experience, which is why we avoid checking in binaries.
00:11:56.829 Native extensions that require compilation do not go into the repository, which simplifies our gem management. Thus, for user-level operations, we ensure that no gems with native extensions are necessary.
00:12:30.149 For development tasks that require native extensions, Bundler will facilitate installation. This process is an initial setup that is easily manageable after the first run.
00:12:50.919 Thanks to this approach, we can now use ActiveSupport within Homebrew, with features like string presenting. Although it may not be to everyone’s liking, I appreciate these tools.
00:13:21.600 Homebrew's unique structure means that we can utilize many gems in a way that minimizes friction, allowing us to develop with cleaner implementations.
00:13:44.540 Ruby-style coding has significantly improved our approach to development, making upkeep easier and coding cleaner.
00:14:13.770 A significant shoutout goes to RuboCop, which we’ve integrated widely to automate style checks. When someone runs ‘brew style’, it checks for discrepancies in our codebase.
00:14:41.010 By running checks through RuboCop, any issues can be identified by the users before they submit a pull request, ensuring consistent code quality.
00:15:05.320 As our files are within a hierarchy we control, we can integrate tools effectively so that users can reap the benefits without any additional setup.
00:15:37.080 For instance, our developers can rely on IDE integration to get immediate feedback, which facilitates an efficient workflow. This makes the coding experience smoother and quicker.
00:16:03.780 While maintaining a focus on developer experience, it allows us to streamline the contributions and reduce the manual tasks involved.
00:16:25.590 One unique aspect of Homebrew is that, despite its Ruby foundations, we intentionally do not provide a Ruby API. If you're writing a gem or application, you cannot directly interface with Homebrew through Ruby.
00:16:50.730 We have had attempts at establishing Ruby APIs, with some projects that resorted to monkey patching classes and modifying Homebrew indirectly, but this is not sustainable.
00:17:26.309 Our intended API for Homebrew is the command-line interface itself. We maintain stable outputs, treating them as an API, which respects the functional behaviors of our commands.
00:18:03.350 When you run commands like ‘brew search’ without arguments, it lists the installed formulas on your machine.
00:18:42.490 While this may seem odd, there are enough scripts on the internet that rely on this behavior, and we've opted to keep it intact to avoid breaking functionality for users.
00:19:20.389 We do provide APIs such as ‘brew info’, which returns a machine-readable version of the formula file when you pass a JSON argument, allowing for integration with other tools like jq for JSON parsing.
00:19:50.249 Additionally, Homebrew operates online through a static JSON generated by GitHub pages, allowing users to access package information through API endpoints without server maintenance.
00:20:04.780 Regarding performance, we prioritize startup time within Homebrew. Commands like ‘brew shell’ quickly produce output for sourcing in your terminal, which is essential for rapid operations.
00:20:43.020 Such performance is crucial as, with a million users, even a delay of 0.1 seconds can impact overall efficiency.
00:21:09.680 Thus, we constantly assess our performance with benchmarks to refine our commands, so they are efficient and swift.
00:21:40.750 Homebrew’s DSL for formulas allows users to create recipes easily with a simple syntax, making it highly readable and approachable.
00:21:54.369 The formula design is gratifying in its simplicity compared to other package managers I have used over the years. There's less overhead in formulation.
00:22:20.800 Recently, many have been influenced by this model, and other package managers have started adopting similar styles, but the Ruby syntax remains distinct in its beauty.
00:22:45.200 So, I think Homebrew today is a product of the Ruby community and its contributions since inception, leading to success and further development.
00:23:10.940 I’m thankful to everyone here for the roles they have played in Homebrew’s journey and their contributions to the community.
00:23:31.620 Now, I'm going to open the floor to questions.
00:23:48.050 Have you checked CocoaPods, compared how it works with Homebrew and any decisions they made in relation to package management? Have you collaborated or discussed in any way?
00:24:59.300 That's a great question! We have exchanged ideas with CocoaPods regarding managing package managers effectively and identifying pain points that come with it. Although I have limited experience with CocoaPods, I recognize its popularity and well-received nature.
00:25:57.390 Their approach seems inclined towards manual installation steps that involve deeper integration with Ruby, which can be cumbersome for users who just want to use the packages without diving into Ruby.
00:26:36.730 Nonetheless, CocoaPods has made strides towards compatibility with Homebrew to allow installations!
00:27:12.850 As for Homebrew on Linux, our team has taken steps to ensure its use while still needing to continue taking advantage of system-level tools already provided by the operating system package manager.
00:27:53.440 In summary, we adapt to fit the functionality of core system tools while providing users with the most seamless experience possible.
00:28:34.920 Thank you very much for having me!