So like probably more than a few people, we jumped in and bought two of the new Apple silicon M1 Mac Mini’s to replace a couple aging Intel-based build servers at work. Our old servers were running Jenkins with a pretty standard configuration to build and test our iOS apps.
The initial transition was pretty straightforward – install HomeBrew, Xcode, and all of our other tools, copy Jenkins configurations and plugins to the new box, etc. Despite having a mixed Swift/Objective-C app with various dependencies as included frameworks, CocoaPods, and Swift Package Manager frameworks, the initial setup and testing went pretty smoothly for our build servers. Performance was stellar with the new servers running about 66% faster than the old 2015-era Mac Mini’s.
All of our framework and other builds not requiring simulator interaction went fine. Where things started to go poorly was around building our UI testing schemes when simulators were involved (either through Xcode or command-line with xcodebuild). In the hope that it helps someone else, I’ll outline a sample error that we saw, some possible solutions, and the workaround we’ve settled on for now till we can address the problems in full.
Issues and Possible Fixes
Most of the issues that we’ve found to date can be traced back to frameworks and third-party dependencies not built with all of the necessary architectures included. For example, if you have an included framework with an incompatible binary, you might see an error like this:
Could not find module '<module name>' for target 'x86_64-apple-ios-simulator'; found: arm64, arm64-apple-ios-simulator, at: <path to module>.swiftmodule
Other errors can occur and typically reference missing architectures and the like. Most of the fixes that can work and make your project and its dependencies compatible on an Apple silicon macOS machine seem to boil down to either:
- Building for all architectures – By default if you create a new Xcode project only the current active architecture is built for the Debug configuration.
Building only the active architecture lets your project compile faster but doesn’t necessarily support multiple architectures. On actual iOS devices, the Build Active Architecture Only setting hasn’t been a huge deal for quite a few years (32-bit anyone?). It hasn’t been an issue with simulators either lately; but with the advent of Apple silicon chips, we now have the arm64e architecture to worry about on desktops as well.
Some of the issues you may run into can be addressed with Debug schemes by changing the Build Active Architecture Only setting to No for all cases. This can be done through Xcode manually or through podfile configuration if you are using CocoaPods. One very real and frustrating problem that I have not seen a solution for yet however is changing this setting for frameworks integrated via Swift Package Manager.
- Adding missing architectures – Another approach that can work in some situations is adding missing architectures to the Architectures setting above instead of just using Standard Architectures. Adding x86_64 in some cases if you’re on an M1 Mac dealing with an Intel-based project (or arm64e in other cases) can make a difference. I’ve not had much luck with this though and once again this will not help you fix dependencies that are pulled in via SPM.
- Getting the dependency fixed – By far the preferred solution over any of the above for fixing dependencies is to communicate with the framework owner and get them to re-compile and distribute their framework as an XCFramework compatible with all architectures and devices. For code that you own, this should be pretty straightforward; but for many dependencies this can be a significant problem. It’s also not always possible to just dump a core dependency quickly for something more compatible.
Working Around Issues in Xcode
So you’re stuck for one reason or another or just don’t have time to deal with the problem (and won’t let those shiny new Macs go to waste 😀). I’ll freely admit that I’m in the latter category currently, with more work than time. So what to do?
The simplest approach that should fix all of your problems within Xcode itself is running the application with Rosetta (which allows running x86_64 instructions on Apple silicon machines). Turning it on and off is trivial and done via the Get Info menu option under the Xcode application context menu. Note that this setting will obviously only show up on macOS running Apple silicon.
Just set the Open using Rosetta checkbox and you’re off and running. Your builds from the desktop should work as if they were running on an Intel-based machine again. Problem postponed…
Working Around Issues on the Command Line
Now if you’re trying to get continuous integration (Jenkins, etc.) working and xcodebuild is throwing the same errors, you’ll need to use the
arch command to run with the x86_64 architecture (if you’re on an M1 Mac for example running an Intel-compatible project). It’s as simple as
arch -x86_64 xcodebuild test <xcodebuild switches and settings>
This will fix your issues by running the specified command (xcodebuild in this base) with the x86_64 architecture. Once again, problem postponed…
Postponed not Fixed…
I said “postponed” above for a reason. While you might be able to survive for a little while (months, a year, or even longer) with the hacks above, putting a band-aid on your Apple silicon issues are not going to make them go away. Take the time you have bought yourself and start to research and fix the core issues that you are running into. Check out Apple’s extensive documentation for developers.
It shouldn’t matter whether you are on an Intel or Apple silicon machine when you are developing. Fixing your project and your apps so you can run natively anywhere while not losing time on either will only benefit you and your users in the long run.
Thanks for reading and as always please feel free to comment, share, follow. etc.
One thought on “Working Around Xcode and Continuous Integration Issues on Apple Silicon”