Apple Silicon Mac: Tips for developers

I got a fully maxed-out MacBook Air with the shiny new Apple Silicon M1 processor. And, I absolutely love it. It’s fast, snappy, very fluid, and I’m a very big fan of no fans. (Some prefer active cooling, but I prefer how silent a computer is.) It’s a huge change for Apple, moving from x86 Intel processors to their custom ARM chip. But, it’s not the first time: I still have the first Intel MacBook Apple made, when they switched away from PowerPC processors.

Despite Apple’s best efforts, like the Rosetta 2 engine “translating” x86 apps on the fly to run on M1, some apps will not work right away, especially programs used by developers. Therefore, while software authors update their title to work natively with M1, there are some workarounds developers will want to know to help run some apps smoothly.

“What kind of binary is it?”

It’s easy to know if an app or a binary was compiled for Intel processors (x86 architecture) or Apple Silicon (ARM).

For an application, you may find it in the Finder, select it, then from the menu, select File > Get Into (⌘+I). Next to Kind, under General, it will say Application (Intel) or Application (Apple Silicon).

For a single binary, simply use the file command. For example:

file /bin/zsh

It may say Mach-O 64-bit executable x86_64 or Mach-O 64-bit executable arm64e. Many binaries provided by macOS are also “universal,” compiled to run in both architectures, like /bin/zsh:

$ file /bin/zsh
/bin/zsh: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e:Mach-O 64-bit executable arm64e]
/bin/zsh (for architecture x86_64):	Mach-O 64-bit executable x86_64
/bin/zsh (for architecture arm64e):	Mach-O 64-bit executable arm64e

Side note: If you haven’t heard yet, zsh is now the default shell since macOS Catalina, replacing bash. Although, the latter is still available.

Picking an architecture

macOS is pretty good at detecting for what architecture an application was compiled and run Apple Silicon apps natively or Intel apps through Rosetta. But in cases where the architecture can’t be guessed—for example, when running a shell script—the arch command is your best friend. It lets you easily wrap a command in the architecture you want. For example:

arch -x86_64 /path/to/x86_64/binary

Knowing the environment’s architecture

To know the environment’s current architecture, simply run arch without arguments. It will output arm64 or i386. Also, arm64 or x86_64 is set in the CPUTYPE environment variable.

$ arch
arm64
$ echo $CPUTYPE
arm64
$ arch -x86_64 $SHELL
$ arch
i386
$ echo $CPUTYPE
x86_64

Starting a shell in a specific architecture

If you need to run a shell in an environment using a specific architecture, again, you can use arch for this. For example, if you want to start your favourite shell (the default being zsh) in an Intel environment, mimicking close to what you had on an Intel Mac, you can do the following:

arch -x86_64 $SHELL

Homebrew

The team behind Homebrew is working hard in recompiling all the software it distributes for M1. In the meantime, you can have two installations of Homebrew, one for Intel software installed in /usr/local, and another for Apple Silicon in /opt/homebrew. (You can pick whether directory you wish; these are only recommendations by Homebrew.)

To install the Apple Silicon version in /opt/homebrew, just run the normal installation procedure as they suggest:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

To install the Intel version in /usr/local, to the same than the above, but with arch:

arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

While the above installs Homebrew for Intel in a separate directory, there are many cases when you will have to specify both the architecture and the path to brew when you do not want to work with Apple Silicon, like when installing some packages:

arch -x86_64 /usr/local/bin/brew install ...

Node with NVM

Using NVM and installing Node can be a bit tricky to understand at first. NVM, being mostly a shell function, runs just fine in any architecture. However, while Apple Silicon support is in the works in Node v15, Node v14 and older will not work on the new architecture.

Here’s what you want to do to have NVM and its installations of Node work:

  1. Install NVM through the Intel installation of Homebrew:
    arch -x86_64 /usr/local/bin/brew install nvm
  2. Set your shell to use that installation of NVM by adding the following to your ~/.zshrc (or ~/.bashrc) file:
    BREW_PREFIX="/usr/local/bin/brew --prefix"
    [ -s "$BREW_PREFIX/opt/nvm/nvm.sh" ] && . "$BREW_PREFIX/opt/nvm/nvm.sh"
    [ -s "$BREW_PREFIX/opt/nvm/etc/bash_completion.d/nvm" ] && . "$BREW_PREFIX/opt/nvm/etc/bash_completion.d/nvm"
  3. When you need to install a version of Node, you will have to run your shell in the Intel environment, as mentioned earlier under Starting a shell in a specific architecture, then install the Node version in question:
    arch -x86_64 $SHELL
    nvm install 14.15.3

To run Node installed via NVM, fortunately, you do not need to change architecture. As mentioned earlier, macOS will detect it is an Intel binary and will run it in the appropriate environment.

This is it for now. I will try to update this article or write new ones as I learn more from using the new M1 MacBook. Have fun with this!