Switch between ARM and x86 emulation on Apple Silicon!

Setup your .bash_profile and other bash config files properly for your macOS or Linux to make your work comfortable. It is valuable for the owners of Apple Silicon where you need to switch between ARM and x86 architectures.

Ivan Zhdanov
7 min readNov 4, 2021

Table of content

Introduction

I am a Mac M1 user and faced the necessity to get .bash_profile and other bash config files adequately set up to switch between ARMv8 and Rosetta with comfort. This note will serve as a memory note for me, and I hope some of you will find it helpful as well. I write the code as it works for Mac M1. But it will also work for Linux and probably for Windows users with some adoptions required. Your comments and feedback are very welcome since there is always room for improvement and adjustment.

1. Understand the bash profile load sequence

First of all, we need to understand what is the bash profile load sequence and what is the difference between the .bash_profile and .bashrc which are typically stored in the user folder ~/. .

There are two great sources to read:

And here is the summary. Several dotfiles determine the way your system behaves at login time, setting up aliases, setting up environment variables, and so on.

The load sequence of dotfiles is as follows:

  1. The system reads /etc/profile first.
  2. Then, bash looks in your home directory for .bash_profile, and if it finds it, it reads that.
  3. If it doesn’t find .bash_profile, it looks for .bash_login, and if it doesn’t find that, it looks for .profile (the standard Bourne/POSIX/Korn shell configuration file). Otherwise, it stops looking for dotfiles and gives you a prompt.

Notes:

  • On Linux systems, the system will typically source some or all files in /etc/profile.d (as suggested by the Linux Standard Base — generally /etc/profile should include code for this)
  • Many Linux systems also have another layer called PAM, which is relevant here. Before “executing” bash, login will read the /etc/pam.d/login file (or its equivalent on your system), which may tell it to read various other files such as /etc/environment. Other systems such as OpenBSD have a /etc/login.conf file that controls resource limits for various classes of user accounts. So you may have some extra environment variables, process limits, and so on before your shell reads /etc/profile.

You may have noted that .bashrc is not being read in this situation. You should, therefore, always have source ~/.bashrc at the end of your .bash_profile to force it to be read by a login shell. If you use .profile instead of .bash_profile, you additionally need to test if the shell is bash first:

# .profile
if [ -n "$BASH" ] && [ -r ~/.bashrc ]; then
. ~/.bashrc
fi

Why is .bashrc a separate file from .bash_profile, then? There are a couple of reasons. The first is performance — when machines were extremely slow compared to today’s workstations, processing the commands in .profile or .bash_profile could take quite a long time, especially on machines where a lot of the work had to be done by external commands (before Korn/Bash shells). So the difficult initial setup commands, which create environment variables that can be passed down to child processes, are put in .bash_profile. The transient settings and aliases/functions that are not inherited are put in .bashrc to be re-read by every new interactive shell.

The second reason why .bashrc is separate is due to work habits. If you work in an office setting with a terminal on your desk, you probably log in one time at the start of each day and log out at the end of the day. You may put various special commands in your .bash_profile that you want to run at the beginning of each day, when you log in — checking for announcements from management, etc. You wouldn’t want those to be done every time you launch a new shell. So, having this separation gives you some flexibility.

Now you understand the load sequence of bash profile files and why we might need .bashrc additionally to .bash_profile file. For our purpose to make a comfortable switch between ARMv8 and Rosetta settings, we will use two different .bashrc files and .bash_profile file will store the common setting, which has to be loaded once.

2. Understand why we need to switch between ARMv8 and Rosetta emulation for Mac M1

As you know, the newest Apple MacBooks have chips with ARM architecture (M1, M1Pro, M1Max etc.). To enable the usage of software designed for x86 architecture, Apple has introduced a Rosetta 2 emulation layer. And if you are a developer or an advanced user, you will come to the point when you need to switch your terminal between ARM and x86 architectures. You need to understand that sometimes you would need to install an instance of Python built for x86 and another instance of Python built for ARM just because some software will have one or another instance of Python as a dependency.

Get more information about the Rosetta 2 here and about the Rosetta 2 limitations here. Yes, Rosetta 2 is not bullet-proof, and it can’t emulate some x86 chip instructions right now. Thus, you can’t always rely on Rosetta 2, and you will need to switch back to ARM architecture and search for an alternative solution.

3. Files structure and examples

Okay, the file structure will be pretty simple, and we will have four files:

  1. ~/.bash_profile which will contain common settings loaded once at the start
  2. ~/.bashrc64 contains ARMv8 settings (it is called arm64 architecture)
  3. ~/.bashrc86 contains x86 settings
  4. ~/.bash_aliases contains useful aliases and is packed in ~/.bash_profile. It is optional, just for better transparency.

The example files are straightforward. Before you touch your files, please check what is inside your files and act correspondently. In my case, I have two instances of Homebrew and two instances of Python, depending on the architecture. The reasoning why you might need two Pythons is well explained here. Both these examples show that these instances are required to be on different paths, and without proper management with bash profile, their usage will be not possible. There will always be a mismatch between your current architecture and the required path to the software.

~/.bash_profile

# path to homebrew arm64 (for x86 we will use an alias ibrew)
export PATH=/opt/homebrew/bin:$PATH
# path to user bin folder
export PATH=/usr/local/bin:$PATH
export UNAME=$(arch)
export PS1="\h($UNAME): \W \u\$"
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('~/miniforge3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
if [ -f "~/miniforge3/etc/profile.d/conda.sh" ]; then
. "~/miniforge3/etc/profile.d/conda.sh"
else
export PATH="~/miniforge3/bin:$PATH"
fi
fi
unset __conda_setup
# <<< conda initialize <<<
# Add aliases for profile
if [ -e ~/.bash_aliases ]; then
source ~/.bash_aliases
fi

~/.bashrc64

The ARM Python is sourced from Miniforge arm64 release

# set explicetely arm64
export UNAME="arm64"
# add architecture to bash prompt
export PS1="\h($UNAME): \W \u\$"
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('~/miniforge3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
if [ -f "~/miniforge3/etc/profile.d/conda.sh" ]; then
. "~/miniforge3/etc/profile.d/conda.sh"
else
export PATH="~/miniforge3/bin:$PATH"
fi
fi
unset __conda_setup
# <<< conda initialize <<<
# switch to arm64 architecture
arch -arm64 /bin/bash

~/.bashrc86

This file determines the path to x86 Python installed with Miniconda.

# set explicetely x86
export UNAME="x86"
# add architecture to bash prompt
export PS1="\h($UNAME): \W \u\$"
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('~/opt/miniconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
if [ -f "~/opt/miniconda3/etc/profile.d/conda.sh" ]; then
. "~/opt/miniconda3/etc/profile.d/conda.sh"
else
export PATH="~/opt/miniconda3/bin:$PATH"
fi
fi
unset __conda_setup
# <<< conda initialize <<<
arch -x86_64 /bin/bash

~/.bash_aliases

This file contains an alias for x86 Homebrew. The Homebrew installation guide is here. It will depend on which current architecture is enabled. To switch the architecture, read this instruction. Now, you will be able to use brew command for arm64 architecture and ibrew for x86 architecture.

alias ibrew='/usr/local/Homebrew/bin/brew'

4. Switch between architectures

Now, it is time to switch between architectures and get your Python correctly changed. Run the following commands.

Switch to x86 architecture

source ~/.bashrc86

Switch back to ARM architecture

source ~/.bashrc64

Enjoy!

Useful Links

For any questions please feel free to post a comment or you can connect with me on LinkedIn.

--

--

Ivan Zhdanov

I am a Power Plant & Digital Industrial Engineer with more than 10 years of experience. I lead the design, tendering and installation of energy projects.