How to Install (Or Get Rid Of) therubyracer on M1 or M2 Macs

Published by Moncef Belyamani on
Updated on

If you’ve been banging your head against a wall trying to install therubyracer gem on an Apple Silicon Mac (M1 or M2), you’ve come to the right place.

From talking to Ruby on Mac customers and other Ruby developers, a common pain point is not being able to run legacy Rails apps on M1 Macs. This is due to the projects using old versions of Ruby, and old versions of gems that might not be maintained anymore and that aren’t supported on the M1/M2 chip.

The gem most people complain about is therubyracer. They spend days trying to install it, and either end up giving up, or using workarounds they’re not happy with. The reason why you can’t install it natively is because it hasn’t been updated to support the ARM architecture that the Apple Silicon chips use. If you look it up in rubygems.org, you’ll see that it hasn’t been updated since January 5, 2017.

Errors installing therubyracer on Apple Silicon Macs

If you try to run gem install therubyracer or bundle install in a project that has therubyracer in the Gemfile, you’ll end up with errors like these:

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

libv8 requires python 2 to be installed in order to build, but it is currently 
not available (RuntimeError)

An error occurred while installing libv8 (3.16.14.19), and Bundler cannot continue.

This error message says that therubyracer depends on version 3.16.14.19 of libv8, which in turn requires version 2 of Python. Both of those are ancient versions that are no longer supported. Python 2 reached end of support on January 1, 2020, almost 3 years ago!

This should set off alarm bells in your mind, and encourage you to look into how to get rid of or replace therubyracer, because using ancient and unsupported tools poses a security risk. Unfortunately, many people don’t take a step back, and think that they’re stuck with the gems they currently have.

What does therubyracer do, and why do so many old Rails apps still use it?

Rails uses JavaScript to compress assets, and so it requires you to have a JavaScript runtime available on your system. macOS and Windows usually come with a JavaScript runtime installed. If not, the easiest one to install on a Mac is Node, either directly with Homebrew via brew install node (not recommended), or via a Node version manager such as nodenv (which is what Ruby on Mac installs and configures for you).

Because most app hosting providers use Linux servers, which don’t have a JS runtime installed by default, Rails wanted to make it as easy as possible to deploy apps to production. So they added therubyracer gem to the production group in the Gemfile. therubyracer is one of many JS runtimes supported by ExecJS. It embeds Google’s V8 JavaScript engine inside Ruby via the libv8 gem.

To find out when Rails added therubyracer, I cloned the rails/rails GitHub repo, then ran git log -S "therubyracer". The oldest commit listed was from 2011, when they added instructions for adding therubyracer gem to their asset pipeline documentation. Then on January 12, 2012, they added the gem automatically to the generated Gemfile, but it was commented out by default.

On May 30, 2017, they replaced therubyracer with mini_racer due to security issues. And then on October 1, 2018, they merged the change to replace mini_racer with webpacker, meaning all you need is Node.

So, it has now been 4+ years since Rails stopped using either therubyracer or mini_racer, yet people are still using them, even in apps that already use Node, and that have been updated to newer versions of Rails.

Do you even need therubyracer?

Most likely not. If it’s listed as a dependency in your Gemfile, try removing it or commenting it out, then run bundle, then see if your app still works and if your tests still pass.

Whenever I come across an old Rails project that has therubyracer in the Gemfile, the first thing I try is to remove it. 99% of the time, that’s all that’s needed, assuming Node is installed.

As for production, if you’re using a service like Heroku or Render, Node gets automatically installed for you when deploying a Rails app, so there’s no need for therubyracer.

If you do have Node properly installed and configured, and removing therubyracer breaks your app, try replacing it with mini_racer, which works fine on Apple Silicon natively. In most cases, all you would need is to replace “therubyracer” with “mini_racer” in your Gemfile, then run bundle.

If you’re still having issues running your old app, the problem is most likely due to another outdated gem. Look at the gems referenced in any error messages, and make sure they’re up to date, or replace them with alternatives that are still maintained.

Updating gems to their latest version resolves most issues with old projects on Apple Silicon Macs.

What if it’s another gem that requires therubyracer?

If therubyracer is not listed in your Gemfile, check in your Gemfile.lock to see which gem depends on it, and if there’s a replacement for it. In some cases, therubyracer might not be listed at all, but might be indirectly required. For example, if you see less in your Gemfile.lock, then you’ll probably get an error like this when running Rails commands:

[WARNING] Please install gem 'therubyracer' to use Less.

cannot load such file -- v8 (LoadError)

That’s because the less gem, which hasn’t been updated since 2014, depends directly on the V8 module from therubyracer, and doesn’t support other JS runtimes like Node, or even more modern gems that provide the V8 engine, like libv8-node and mini_racer.

In this case, I recommend replacing less with LibSass via the sassc or sassc-rails gems. This would involve additional work to rename CSS files and update Less syntax to Sass syntax. You can probably find tools that can automatically convert Less to Sass.

Can’t I install therubyracer using Rosetta?

It’s definitely possible, but I don’t recommend it. As I mentioned earlier, therubyracer depends on outdated tools that have many known security vulnerabilities. If you have people using your app in production, you’re putting their data and your company’s reputation at risk. Do you really want that?

Installing therubyracer on an Apple Silicon Mac is a complicated process that can easily take you 2 or 3 hours to set everything up properly if you were to do it all manually. It requires setting up two separate Ruby environments, which is a headache to maintain. Trust me, you’re better off trying to replace therubyracer using the solutions I mentioned above.

If you’ve already tried to install it without success, all bets are off, so you’ll need to start over with a clean slate. Ruby on Mac Prime and Ultimate have a “reset mode” which will back up your current dev setup, then safely uninstall and clean everything up, all in 60 seconds. Then you can run Ruby on Mac in normal mode to reinstall everything from scratch.

Once you have a proper Ruby dev environment in native mode, you can set up a separate Ruby installation under Rosetta using the i386/x86_64 architecture. But before you start following the steps below, I wanted to let you know that I’m working on automating this entire process as a new feature in Ruby on Mac Ultimate. If that sounds appealing to you, let me know. If enough people express interest, I’ll prioritize this feature.

Setting up Ruby and therubyracer with Rosetta

If you haven’t yet installed Rosetta, you can do it from the command line:

/usr/sbin/softwareupdate --install-rosetta --agree-to-license

Otherwise, if you try to run commands with the arch -x86_64 prefix, you’ll get an error like this one:

arch: posix_spanwp: /bin/bash: Bad CPU type in executable

Then, the most reliable way to switch between both architectures, and easily tell which one you’re currently using, is to set up your shell like Aaron Patterson (aka tenderlove).

If you’re using the fish shell like me and Aaron, you’ll be pleased to know that the chruby-fish bug Aaron mentions in his article has been fixed, so his workaround is no longer necessary. If you were previously using the workaround, you can undo it, then reinstall chruby-fish.

Once you have a proper Ruby setup using Rosetta, you’ll need to install the outdated tools therubyracer depends on:

brew install v8@3.15
brew install pyenv
pyenv install 2.7.18
pyenv global 2.7.18

Then add pyenv to your PATH in your shell file. For example, if you’re using zsh, add this to your ~/.zshrc:

PATH=$(pyenv root)/shims:$PATH

For fish users:

fish_add_path -m --path (pyenv root)/shims

So far, so good, but the big caveat with maintaining two separate Ruby environments is that by default, they will share the same folder for installing gems: ~/.gem/ruby/{version_number}. That means that every time you switch between Rosetta and native, you’ll need to remember to delete the gem folder and reinstall all gems. If you’re adventurous, you can work around that by using Benoit Daloze’s fork of chruby. More details are in this issue in the ruby-install repo.

Next, from the root of your Rails app directory, configure Bundler to always install therubyracer by pointing to the v8 library that Homebrew installed:

bundle config set build.therubyracer --with-v8-dir=$(brew --prefix v8@3.15)

This will create a .bundle/config file in your Rails app that specifies what settings to use when installing therubyracer.

Because therubyracer depends on the libv8 gem, it needs to be told where v8 is installed as well. Otherwise, you’ll get a stacktrace that includes these messages:

Unable to find a compiler officially supported by v8

expected binary v8 archive to exist, but it was not found
(Libv8::Location::Vendor::ArchiveNotFound)

Configuring libv8 is similar to therubyracer:

bundle config set build.libv8 --with-system-v8

Now you should be able to run bundle, and both therubyracer and libv8 should install successfully.

Need more help?

If you’re still stuck getting your old Ruby projects to run, I’d be happy to help you via one of these two options:

  • Buy Ruby on Mac Ultimate, which includes a 30-min consultation with me, worth $150 on its own. For those that need to run Ruby projects on both Intel and ARM architectures, I’m working on a new feature that will automatically set up two separate Ruby installations (native/arm64 and Rosetta/i386) and allow you to easily switch between them. Once it’s ready, the price will go up to $129.
  • I offer advisory retainers, and app maintenance contracts. I’m taking on a limited number of clients at this time. Feel free to email me at my first name @ rubyonmac.dev and we’ll go from there.