Apple Silicon and Shell Scripts

· 6 min read
Apple Silicon and Shell Scripts
Photo by Dmitry Chernyshov / Unsplash

On November 10, Apple unveiled the latest iteration of MacBook Pro, Air, and Mac Mini with its in-home Apple Silicon M1 chip. Being a longtime Mac user and developer, I was skeptical to move on from Intel-based systems to an ARM-based system due to the lack to support for dev tools like Docker and Homebrew (more on this later). However, due to the technological limitations of my 2012 MBP, I got myself a new Mac that was much more capable than my previous machine.

Fast-forward two weeks, and I am fully satisfied by how stupendously fast this machine is and the battery life it offers. But there was one glaring problem: setting up all the dependencies that I had accumulated over the years in my 2012 MBP. To alleviate this, I took the help of a good old friend: shell — more specifically, zsh.

Terminal has been part of macOS for a long time and this was an opportunity for me to utilize my skills to write a script that would install all the dependencies without aiming to install everything manually.

TL;DR: The following article talks about my experience with writing a shell script to set up dependencies on M1. If you are familiar with shell scripts and want to move forward with the setup, head to this repository.

One of the essential things for setting up the dev environment is Terminal. MacOS ships with the default terminal, but it’s too bland for us developers to use it routinely. My personal choice is iTerm2, which is also available natively on Apple Silicon. Moving ahead, we will now set up ohmyzsh. If you’re not familiar with ohmyzsh, it’s a community-driven framework aimed at improving zsh. This will further help us in setting up the dev environment by adding themes and plugins that allow the autocompletion of commands, custom themes, and many other features.

With that said, let’s start. Open iTerm2 and execute the following commands:

git clone https://github.com/hjoshi123/M1DevSetup
cd M1DevSetup
zsh prereq.sh
zsh deps_part1.sh

Note: The first line of the script says /bin/zsh, which indicates that we are going to use zsh to execute the script and any other shell-like bash would need certain adjustments accordingly. The deps_part1.sh script contains the entire dependencies and this article explains my attempt to write it.

With that, ohmyzsh would have installed and you would have gotten the default theme and plugins installed. There are various customizations that can be done to this, including the theme, plugins, autocompletion, etc. Most of them are beyond the scope of this article, so we will move ahead with the rest of the setup. However, I will attach the theme file that I use and the steps required to change the theme in the repo. As a result of this article, we will set up the following things:

  • Homebrew (under Rosetta 2 since native doesn’t have all the dependencies supported) and some dependencies under it
  • Python 3.9.0 (again under Rosetta since native seems to give some arch errors)
  • Node.JS v15 (this is the only version that runs on an M1 Mac in native mode)

Let’s begin with brew. brew occupies a critical part of a developer’s toolkit in a Mac. With M1, the setup of brew changes quite a bit. Due to the introduction of the new arm architecture, the lion’s share of the dependencies is not yet natively supported (as of this writing). This leads us to use Rosetta 2 (arch -x86_64) to set up Homebrew and for subsequent installs using brew. When the migration of formulae is done, brew will be installed in a different location than its Rosetta brother. This leads us to use both Rosetta and native versions side by side — albeit with a catch.

Note: The $? that is present in the snippet above (and will be present in other snippets) is part of Unix’s command line that tells if the previous command executed was successful or not. If successful, it returns a 0. Otherwise, it returns a non-zero value.

This snippet is part of a bigger script that we will dig into further. It first checks if brew is already installed through command -v brew. If it isn’t, then it moves ahead to install it through the usual command, but with the prefix arch -x86_64 that indicates Rosetta 2 is being used. Once that is done, we set up an alias to indicate that we are using Rosetta 2-based brew every time we install some formula. Now that brew is installed, we can go ahead and quickly install some other formulae (or dependencies) for our dev environment.

This snippet is pretty straightforward. In lieu of installing each formula individually, we could take the advantage of loops in the shell script. Loops help us to break out the repetitive logic and hence make the script much smaller. To drive the loop, we declare an array that contains all the names of the formulae. From lines 12 to 22, we run a loop with each entry of the array. First, we check if the formula is already installed through brew list. If it isn’t installed, we proceed to install the formula. Once installed, we check if it’s successfully installed through the function check_install_result defined in the previous snippet.

In addition to the installations of formulae, there might be certain environmental variables and/or links that require to be initialized before using the tool. This step is essential. Without it, some of the tools could break down or might not be recognized, as they might not be present in the current PATH variable. This is what we have done. For the eccentric case of openjdk, we need to set up a symbolic link (ln) so that the terminal knows the Java location to be used.

Lines 31 to 40 involve installing a formula that is not part of the default Homebrew repository and hence we are tapping into a different repository. The installation involves the monotonic steps we discussed before.

Note: /dev/null is used to limit the output obtained on the screen since we don’t really need all the information. We just need to know if the formula is installed or not.

Up next, we will move ahead to install Node. For M1, the current version supported is v15 and thus we will be installing that. We would be taking the help of nvm (Node version manager) to install the same.

The steps followed here are pretty similar to the ones we saw before. We verify if nvm is already installed. If it isn’t, we move ahead to install nvm, which is mentioned in the repo. Once nvm is installed, we just install Node (latest version is v15.4) and then use it as the default. While we’re using 15.4 in this tutorial, other versions like 12 are supported through Rosetta 2 by attaching arch -x86_64 before installing Node. Now that Node is set up, we will now enter into the Python area.

The code snippet above deals with setting up Python dependencies. We are going to use pyenv to manage the installations and different versions. pyenv allows us to efficiently use different versions of Python both globally and locally in a project/folder.

Note: macOS does ship with default versions of both Python and Python 3, which are located in /usr/bin. But the problem is that it is not feasible to update these manually when the Python version changes. This leads to bad management of Python versions as time progresses. Hence, we use pyenv, which creates configuration directories and files to manage the Python environments. Refer to this article for more details about pyenv and macOS.

Due to native installation failures, we are going to install Python 3.9 using Rosetta 2. This allows us to use Python for a while before official support lands for the same.


Conclusion

That’s it! We are done setting up our basic dependencies for our Apple Silicon M1 Mac. As we saw above, some of the tools aren’t natively supported yet. As and when they do get support for native, the repository will be updated accordingly.

If you have some tools that you use in your routine or any issues regarding the script that could be improved, do let me know through issues in the repo. As usual, comments and suggestions are welcome.

Update: M1 Homebrew has been added as part of the script and Intel Rosetta brew works as an alias ibrew. Theme for iTerm2 has also been updated in the repository.

PS: This article was previously published by me in Better Programming page in medium.com