Homebrew, Rosetta, and Ruby
Jan 7, 2022 @ 12:39 pmHi everyone! I finally upgraded to an M1. It’s really really great, but the main problem is that some projects I work on like TenderJIT and YJIT only really work on x86_64 and these new M1 machines use ARM chips. Fortunately we can run x86_64 software via Rosetta, so we can still do development work on x86 specific software.
I’ve seen some solutions for setting up a dev environment that uses Rosetta, but I’d like to share what I did.
Installing Homebrew
I think most people recommend that you install two different versions of Homebrew, one that targets ARM, and the other that targets x86.
So far, I’ve found this to be the best solution, so I went with it. Just do the normal Homebrew installation for ARM like this:
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Then run the installer again under Rosetta like this:
$ arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
After doing this, I ended up with a Homebrew installation in /opt/homebrew
(the ARM version),
and another installation in /usr/local
(the x86 version).
Configuring your Terminal
I read many places on the web that recommend you duplicate terminal, then rename it and modify the renamed version to run under Rosetta.
I really didn’t like this solution. The problem for me is that I’d end up with two
different terminal icons when doing cmd-tab
, and I really can’t be bothered
to read whether the terminal is the Rosetta one or not. It makes switching to
the right terminal take way too long.
Instead I decided to make my shell figure out what architecture I’m using, then
update $PATH
depending on whether I’m using x86 or ARM. To accomplish this,
I installed Fish (I use Fish shell) in both the x86 and ARM installations of
Homebrew:
$ /opt/homebrew/bin/brew install fish
$ arch -x86_64 /usr/local/bin/brew install fish
If you’re not using Fish you don’t need to do this step. 😆
Next is the “interesting” part. In my config.fish
, I added this:
if test (arch) = "i386"
set HOMEBREW_PREFIX /usr/local
else
set HOMEBREW_PREFIX /opt/homebrew
end
# Add the Homebrew prefix to $PATH. -m flag ensures it's at the beginning
# of the path since the path might already be in $PATH (just not at the start)
fish_add_path -m --path $HOMEBREW_PREFIX/bin
alias intel 'arch -x86_64 /usr/local/bin/fish'
The arch
command will tell you which architecture you’re on. If I’m on i386,
set the Homebrew prefix to /usr/local
, otherwise set it to /opt/homebrew
.
Then use fish_add_path
to prepend the Homebrew prefix to my $PATH
environment
variable. The -m
switch moves the path to the front if $PATH
already contained
the path I’m trying to add.
Finally I added an alias intel
that just starts a new shell but under Rosetta.
So my default workflow is to open a terminal under ARM, and if I need to work
on an intel project, just run intel
.
How do I know my current architecture?
The arch
command will tell you the current architecture, but I don’t want to
run that every time I want to verify my current architecture. My solution was
to add an emoji to my prompt. I don’t like adding more text to my prompt, but
this seems important enough to warrant the addition.
My fish_prompt
function looks like this:
function fish_prompt --description 'Write out the prompt'
if not set -q __fish_prompt_normal
set -g __fish_prompt_normal (set_color normal)
end
if not set -q __fish_prompt_cwd
set -g __fish_prompt_cwd (set_color $fish_color_cwd)
end
if test (arch) = "i386"
set emote 🧐
else
set emote 💪
end
echo -n -s "[$USER" @ (prompt_hostname) $emote ' ' "$__fish_prompt_cwd" (prompt_pwd) (__fish_vcs_prompt) "$__fish_prompt_normal" ']$ '
end
If I’m on ARM, the prompt will have an 💪 emoji, and if I’m on x86, the prompt will have a 🧐 emoji.
Just to give an example, here is a sample session in my terminal:
Last login: Fri Jan 7 12:37:59 on ttys001
Welcome to fish, the friendly interactive shell
[aaron@tc-lan-adapter💪 ~]$ which brew
/opt/homebrew/bin/brew
[aaron@tc-lan-adapter💪 ~]$ arch
arm64
[aaron@tc-lan-adapter💪 ~]$ intel
Welcome to fish, the friendly interactive shell
[aaron@tc-lan-adapter🧐 ~]$ which brew
/usr/local/bin/brew
[aaron@tc-lan-adapter🧐 ~]$ arch
i386
[aaron@tc-lan-adapter🧐 ~]$ exit
[aaron@tc-lan-adapter💪 ~]$ arch
arm64
[aaron@tc-lan-adapter💪 ~]$ which brew
/opt/homebrew/bin/brew
[aaron@tc-lan-adapter💪 ~]$
Now I can easily switch back and forth between x86 and ARM and my prompt tells me which I’m using.
Ruby with chruby
My Ruby dev environment is still a work in progress. I use chruby for changing Ruby versions. The problem is that all Ruby versions live in the same directory. chruby doesn’t know the difference between ARM versions and x86 versions. So for now I’m adding the architecture name to the directory:
[aaron@tc-lan-adapter💪 ~]$ chruby
ruby-3.0.2
ruby-arm64
ruby-i386
ruby-trunk
[aaron@tc-lan-adapter💪 ~]$
So I have to be careful which Ruby I switch to. I’ve filed a ticket on ruby-install, and I think we can make this nicer.
Specifically I’d like to add a subfolder in ~/.rubies
for each architecture,
then point chruby at the right subfolder depending on my current architecture.
Essentially the same trick I used for $PATH
and Homebrew, but for pointing
chruby at the right place given my current architecture.
For now I just have to be careful though!
One huge caveat for Fish users is that the current version of chruby-fish is
broken such that changes to $PATH
end up getting lost (see this issue).
To work around that issue, I’m using @ioquatix’s fork of chruby-fish which can
be found here. I just checked out
that version of chruby-fish in my git project folder and added this to my config.fish
:
# Point Fish at our local checkout of chruby-fish
set fish_function_path $fish_function_path $HOME/git/chruby-fish/share/fish/vendor_functions.d
Conclusion
Getting a dev environment up and running with Rosetta wasn’t too bad, but I think
having the shell fix up $PATH
is a better solution than having two copies of Terminal.app
The scripts I presented here were all Fish specific, but I don’t think it should be too hard to translate them to whatever shell you use.
Anyway, I hope you have a good weekend!