Adapptor - Perth App Developers

View Original

React Native on Apple M1 Silicon without Rosetta-2

With just two Intel-based computers remaining in Apple’s stable (Mac mini and Mac Pro), if you’re an app developer your next Mac purchase will likely be powered by the M1 chip. In case you missed it, the M1 is Apple’s own CPU, and while the move to the new architecture won’t be noticed by many users, developers have some work to do. Apple has done a fantastic job of making the transition smooth with its Rosetta-2 emulator, but it isn’t perfect. Here I’ll describe the steps I took to set up my dev environment for React Native to make the most of the M1’s promised power, and avoid gotchas with lingering Intel dependencies.

M1 Dev, Almost as Advertised

I recently received an M1 Mac from Adapptor because my Intel Mac was struggling in the summer heat. I am a strong proponent of RISC architectures, and also keen to adopt new technologies and deprecate old ones. The M1’s advantages were apparent out of the box—the UI feels more responsive, the laptop is quieter, and the device emulators fly. For iOS development, it’s possible to run Xcode and the terminal in Rosetta, but this will not unlock the full potential of the M1 chip.

As a mobile app developer, Adapptor maintains some native iOS and Android projects, but mostly uses the cross-platform library React Native. I have read several guides on React Native development without Rosetta, but they all have shortcomings which I hope to address. Some do not attempt to build for the arm64 simulator, which means the “no Rosetta” guide actually still needs Rosetta. Others instruct developers to run pod under Rosetta. This is unnecessary (even if running Xcode under Rosetta) and if you follow the instructions below, the pod binary will be arm64 only (as opposed to universal). Here I’ll cover as much as possible from unboxing your new M1 Mac to getting React Native development going without Rosetta.

Sidebar: How do I tell if Rosetta is running?

Before we get into the weeds, it’s useful to know how to tell if you have Rosetta processes running. For that, open the Activity Monitor, and sort processes by Kind. Rosetta processes will be indicated as Intel.

Rosetta processes are ‘Intel’ kind.

Getting started

So you have a new M1 Mac. Starting out, do not install Rosetta, even if prompted. Set everything up without it, as an assurance that you are not accidentally running x86 code, and then only install Rosetta at the end when there are things which are not ready to run without it. Rosetta cannot be easily uninstalled once it has been installed, as its files are guarded heavily by the system.

Install Apps

Do the following before installing CLI Tools, because Xcode (in particular) is large and you will want it downloading in the background.

  • Get Xcode from the App Store, provided you are happy to get automatic updates. You can back up Xcode periodically so that if it upgrades your version and breaks something, you can revert it. You can do this by copying the application in your Applications directory. E.g., Xcode => Xcode-12.5.1. When the store updates it, it will only overwrite the application with the original name.

  • Grab Android Studio, making sure to select the Apple option when you download.

  • Optionally, install Microsoft Visual Studio Code and Google Chrome browser. Both are available in arm64 releases and are commonly used by developers.

Install CLI Tools

You will want to install nvm to use to install other tools and packages. Note that, while I use Homebrew to install dependencies below, you can instead allow React Native to install these dependencies (e.g., with npx react-native init MyApp or npx react-native doctor).

  • In a terminal, install Homebrew, the third-party package manager for Mac, with /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

  • Read the installer output, add any required paths to .bashrc, and restart the terminal

  • Get Ruby with brew install ruby

  • Check that which gem and which ruby point to paths containing “homebrew”, as the Ruby bundled with MacOS is unsuitable for our use. Execute the gem and ruby commands and check they run fine.

  • Get Cocoapods, the dependency manager for Xcode, with brew install cocoapods

  • gem install ffi will install ffi

  • curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash will install nvm

  • nvm install node

  • Restart terminal, then run nvm use node

  • Run nvm -v and check that nvm runs okay.

  • npm install -g yarn will get the yarn command.

  • brew install react-native-debugger for React Native Debugger

If everything here works without Rosetta then you are probably set up. Try creating a new project:

  • Run npx react-native init MyApp

  • cd MyApp

  • yarn

  • npx pod-install

  • yarn ios

The Simulator should now be running MyApp.

Android Tweaks

Android Studio comes ready for M1 development. There is a need for a few workarounds, but it is unclear if they are due to M1 or to the new release. But the emulators, in my opinion, run a lot better on M1 than Intel, with a lot less messing around.

Android Studio might attempt to capture the emulators and place them in a window pane, which hangs and causes the qemu process to become unresponsive. If so, you can uncheck “Launch in a tool window” in Preferences to solve this.

Don’t forget to run adb reverse tcp:8081 tcp:8081 if the emulator or device is unable to reach the Metro bundler.

Ensure platform-tools is at least version 32.0.0 otherwise the adb command will be Intel-only, and Android Studio will not be able to communicate with the devices or install apps. You may also wish to add export PATH="$PATH:$HOME/Library/Android/sdk/platform-tools" to your ~/.bashrc file.

Some developers report needing to run chmod +x /Applications/Android\ Studio.app/Contents/bin/printenv  to allow Android Studio to read environment variables, because it seems that some releases have forgotten to make this executable.

I used Gradle version 6.9 with success. I’m not sure if there is a minimum version for the M1. Even on an M1 Mac with a fast internet connection, gradle syncs are painfully slow (20 minutes or more). Thankfully they are cached and should only take that long the first time for each project. Android Studio only shows the option to restart gradle sync if the sync failed—e.g., if you have a file open. It will appear at the top of the file editing window pane in a bar.

iOS Tweaks

You will have more problems running Xcode than you will for Android Studio, because the Android build process runs in a JVM, which means it is shielded from architecture changes. There is nothing stopping React Native from working without Rosetta, but there are some problematic pods. Pods which are not packaged in the new XCFramework format require Rosetta to run in the iOS Simulators.

Notably, the GoogleMaps pod will not run on an emulator without Rosetta, unless the beta release with the XCFramework format is downloaded from Google and manually installed. If using react-native-maps, download the GoogleMaps-6.0.1-beta archive, drop it into the Xcode project, and patch react-native-maps to use 6.0.1 GoogleMaps pod. Once the Google Maps pod XCFramework feature is moved from beta, and react-native-maps is updated to use the resulting pod, projects that use Google Maps should be able to go without Rosetta-2.

Updating Projects Where All Pods Use XCFramework

Existing projects may be broken due to Xcode 12.5. This introduced arm64 emulator support which broke some projects, encouraging many to add “arm64” to the excluded architectures list to work around the problem. However, the correct solution would have been to build for active architectures only. We need to go through the project, and make sure that there are no excluded architectures, and that we have checked to build active architectures only, for debug configuration. Pods should target iOS 12.1.

Pods target iOS 12.1 at minimum.

Build active architectures only for Debug configuration.

Ensure there are no excluded architectures.

You may need to edit ios/Podfile to have platform :ios, '12.1'. You might get away with lower versions, but make sure it is the same as ‘IPHONEOS_DEPLOYMENT_TARGET’ below:

 post_install do |installer|

   installer.pods_project.targets.each do |target|

     target.build_configurations.each do |config|

       config.build_settings["ONLY_ACTIVE_ARCH"] = 'YES'

       config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = ''

       config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.1'

     end

   end

   installer.pods_project.build_configurations.each do |config|

     config.build_settings["ONLY_ACTIVE_ARCH"] = 'YES'

     config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = ''

     config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.1'

   end

 end

Run pod install and try to build in Xcode.

Updating Projects Where Some Pods Do Not Use XCFramework

If you still have issues after all of this when building, you might have pods which are not yet packaged with the XCFramework format. At this point you can:

  • Install Rosetta-2

  • Uncheck all the “Build Active Architectures Only” in Xcode

  • Add arm64 to excluded architectures in your targets in Xcode

  • Run Xcode under Rosetta

  • Add the following to your Podfile:

 post_install do |installer|

   installer.pods_project.targets.each do |target|

     target.build_configurations.each do |config|

       config.build_settings["ONLY_ACTIVE_ARCH"] = 'NO'

       config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = 'arm64'

       config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.1'

     end

   end

   installer.pods_project.build_configurations.each do |config|

     config.build_settings["ONLY_ACTIVE_ARCH"] = 'NO'

     config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = 'arm64'

     config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.1'

   end

 end

In Xcode, we need to exclude arm64 for the main project for “Any iOS Simulator SDK”:

This should make the project build in arm64 Xcode, but create an x86 simulator binary which will run in Rosetta. Other projects do not apply these three settings to both targets and build configurations, but I have found it is often necessary to apply both.

The Xcode (running in arm64) should now create an Intel simulator binary, which will run in Rosetta. You will get the native performance of Xcode running on arm64 but still be able to use the simulator. When all your pods are updated to use the new XCFramework and support arm64 simulator, you can remove all of the excluded architectures and change ACTIVE_ARCH_ONLY to ‘YES’, resulting in an arm64 build for simulator, thus eliminating the need for Rosetta.

Xcode should never require Rosetta if the project is set up correctly. Xcode running natively can produce Intel Simulator binaries which can run under Rosetta if your project uses pods which are not using the XCFramework format.

The kind of tweaks I’ve described above for straddling the Intel and M1 architectures will hopefully soon become a thing of the past, as Google and Apple improve their tooling, and pod maintainers catch up. In my experience, the benefits of pure M1 are worth the temporary headaches.