A surprise when moving to a Mac M1

Khang Vu Tien
6 min readDec 15, 2021

Something funny happened to me yesterday in the way of moving my Flutter code to the new Apple M1 architecture. I’ll explain it here in a non-geek way. The lesson for the project manager is “Expect surprises if you change tools in the middle of a project”. The lesson for developers is “The worst bug is the one you didn’t code”. And the lesson for the risk manager is “Real-life problems always come from the simultaneous occurence of events that are separately innocuous”.

For developers, the solutions to this bug are given at the end of the article (15 December 2020).

TL; DR (Too Long; Didn’t Read)

Graphical summary of the bug

Even for a computer manufacturer like Apple, switching from a CPU to another is not a simple task. The whole software base has to be recompiled for the new CPU. Apple did that once in 2007 when they moved from Power PC to Intel. They did it again in 2020, moving from Intel to ARM. During a transition period, they have a tool named Rosetta to execute legacy code in the new CPU architecture.

For the casual user, the magic is seamless but a programmer who works at the bleeding edges may find surprises. An existing code might break for reasons that are hard to discover.

The facts

Launch TextEdit. Create a text file with the following code and store it on your Desktop. Name it for example “rubyM1.rb”. It prints out some infos about the CPU architecture for which the Ruby interpreter has been built.

require 'rbconfig' 
OSVERSION = RbConfig::CONFIG['host_os']
ARCH = RbConfig::CONFIG['arch']
HOSTCPU = RbConfig::CONFIG['host_cpu']
BUILDCPU = RbConfig::CONFIG['build_cpu']
TARGETCPU = RbConfig::CONFIG['target_cpu']
puts "OS: #{OSVERSION}"
puts "Arch: #{ARCH}"
puts "Host CPU: #{HOSTCPU}"
puts "Build CPU: #{BUILDCPU}"
puts "Target CPU: #{TARGETCPU}"

Open the Terminal (in the Finder, menu Go > Utilities) and type the following 2 commands to run the code:

cd ~/Desktop 
ruby rubyM1.rb

The output on your Terminal will be

OS: darwin2 
Arch: universal-darwin21
Host CPU: x86_64
Build CPU: x86_64
Target CPU: universal1

This means that the Ruby interpreter provided by Apple has been compiled and built on a Mac with an Intel CPU (x86_64), although it is meant for a “Target CPU: universal”. Actually, it should have been compiled and built on a Mac M1 (arm64): first hiccup.

Second hiccup: I ported quite easily my Flutter development environment from Mac Intel to Mac M1. But when I did non-regression tests, most of my Flutter projects failed when building the mobile app for iOS. They all ran well when building the mobile app for Android.

After one full day of debugging, I found one workaround and one solution.

The solutions

The workaround

The workaround was to execute the following command in Terminal:

arch -x86_64 sudo gem install ffi

The arch command forces the use of Rosetta. Under Rosetta, the workaround reinstalls as superuser (sudo) the Ruby package ffi (Foreign Functions Interface), to make it recognise Intel code.

Principle of the workaround

The workaround corrects half of the bug by having a faulty version of Ruby call the Rosetta version of ffi, that in turn can build the iOS mobile app simulator for x86 CPU. However, the second half of the monster is still there. It can bite you at any opportunity.

The full correction

The full correction is almost as simple:

  • on your Mac M1, use Homebrew to build your own version of Ruby and change the PATH so that all your development tools use your version instead of the version provided by Apple.
  • use your own Ruby to install again CocoaPods.

Now your Flutter development environment looks like this

Principle of the complete solution

For developers, the hand-holding step-by-step solution, provided by the guru who gave me the solution (Valentin Briand), is here: https://stackoverflow.com/questions/64901180/running-cocoapods-on-apple-silicon-m1/65334677–65334677

More explanations

As in many complex issues, the problem was caused by 2 non-bugs that escape the tests when taken separately (https://en.wikipedia.org/wiki/Swiss_cheese_model):

  • building an “universal” Ruby on an Intel Mac should nonetheless make a workable Ruby interpreter.
  • ffi alone does call the correct iOS simulator code for the correct CPU it is running on.
  • but because ffi relies on Ruby metadata of “host CPU” instead of “target CPU” to invoke the iOS simulator code, and because the Ruby of Apple has been built on an Intel Mac, the result is a bug that is very hard to find.

Fortunately, the consequences are less dramatic that the bug of the Boeing 737 Max ( https://en.wikipedia.org/wiki/Boeing_737_MAX_groundings).

Christmas bonus, of December 23, 2021: the incredible Homebrew team published end of November their arm64-native Java Development Kit: https://formulae.brew.sh/formula/openjdk

I tried it: now it is up to 30% faster than the initial Rosetta JDK. To install the latest Long Term Support (LTS) version, have Homebrew installed and just type brew install openjdk

At the end of the installation, follow the Homebrew instructions to make your JDK visible to all system Java wrappers.

Or type brew install openjdk@11 if you need backward compatibility, for example to sign Android bundles with Android Studio. I’ll make a special post for this topic but you may want to read this one in the meanwhile: https://medium.com/@riz_maulana/setting-android-studio-and-jdk-for-apple-silicon-architecture-m1-chip-3c0c082078e.

Tip to increase virtual device memory: The current Android arm64 simulator is using a lot of memory for its system, up to 1.8 GB of the 2 GB defined for its default virtual device. As a result, very simple apps that build well for iOS hit the wall on Android with the dreaded error message:

Error: ADB exited with exit code 1.

Performing Streamed Install

adb: failed to install [your project]/build/app/outputs/flutter-apk/app.apk: Failure [INSTALL_FAILED_INSUFFICIENT_STORAGE]

Error launching application on sdk gphone64 arm64.

There are still very few posts on Stack Exchange that give hints for addressing issues that are specific to arm64, so here are some screen shots to help you increase the memory allocated to the Android virtual device.

  • In Android Studio, launch the AVD Manager:
  • In the AVD Manager screen, edit the Virtual Device with the rightmost icon. In previous versions of Android Studio, it’s the middle icon.
  • Click on “Show Advanced Settings” and scroll down to the section “Memory and Storage”. Increase the RAM, and eventually the Internal Storage.
  • Click on “Finish”.

Run a build, observe that the “Insufficient storage” message is gone. Hope it helps.

Originally published at https://www.linkedin.com.

--

--