Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Multiple Xcode versions or Why `xcrun` is your friend

Multiple Xcode versions or Why xcrun is your friend

The story

I recently spent a few hours helping a friend of mine investigate a weird issue in their Continuous Development infrastructure. Builds were failing with different fatal errors mostly related to SDK paths and .platform directory locations. At first sight, it was clear that something is wrong with the current selected Xcode, but all our initial attempts to catch the problem failed.

In the end, we isolated the problem; one of the tools they use changes PATH silently for the environment to simplify access to Xcode tools. Due to their internal logic, the CD pipeline changes a current selected Xcode a few times on the way within the same script. In some cases, the pipeline ended with xcodebuild in the environment's PATH that does not reflect the expected version after the xcode-select --switch command.

How? Pretty easy, actually. A simplified sequence looked like this:

# Innocent tool extends the `PATH`
# With the `bin` directory within the current selected Xcode
% export PATH="/Xcode_12.5_beta_3.app/Contents/Developer/usr/bin:${PATH}"

# We change Xcode in a proper way
% sudo xcode-select --switch /Xcode_12.1.app/Contents/Developer

# Due to the overridden `PATH`, `xcodebuild` points to an incorrect version
% xcodebuild -version 
Xcode 12.5
Build version 12E5244e

# `xcrun` knows the truth
% xcrun xcodebuild -version
Xcode 12.1
Build version 12A7403

xcrun - Invoke Xcode tools in a safer way

According to the documentation:

xcrun provides a means to locate or invoke developer tools from the command-line, without requiring users to modify Makefiles or otherwise take inconvenient measures to support multiple Xcode toolchains.

The tool xcode-select(1) is used to set a system default for the active developer directory and may be overridden by the DEVELOPER_DIR environment variable.

In real life, it means that you can prefix all the calls to Xcode tools with xcrun on CI/CD to avoid the situation I described in the beginning. Want to run xcodebuild – call it via xcrun xcodebuild command, need to run/invoke simulators, use the xcrun simctl command.

It can save a few hours for your RE/DevOps team at the end of the day. And, depends on the company size, from 5 to 100 hours for your engineers.

What else?

xcrun has a few additional commands.

find – enables "find" mode, in which the resolved tool path is printed instead of the tool being executed.

$ xcrun --find xcodebuild
/Xcode_12.1.app/Contents/Developer/usr/bin/xcodebuild

$ xcrun --find clang
/Xcode_12.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang

A few helpers to print paths/versions of the selected SDKs.

# Print the path to the selected SDK
$ xcrun --show-sdk-path
/Xcode_12.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk

# Print the version number of the selected SDK
$ xcrun --show-sdk-version
10.15.6

# Print the build version number of the selected SDK
$ xcrun --show-sdk-build-version
19G68 

# Print the path to the platform for the selected SDK
$ xcrun --show-sdk-platform-path
/Xcode_12.1.app/Contents/Developer/Platforms/MacOSX.platform

# Print the version number of the platform for the selected SDK
$ xcrun --show-sdk-platform-version
10.15.6

verbose option shows the logic behind the xcrun. For example, you can see that all the mappings are stored within a temporary xcrun_db and populated on the stage when you select a different Xcode version.

% xcrun --verbose --find git                                              
xcrun: note: PATH = '/Xcode_12.5_beta_3.app/Contents/Developer/usr/bin/:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
xcrun: note: SDKROOT = '/Xcode_12.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'
xcrun: note: TOOLCHAINS = ''
xcrun: note: DEVELOPER_DIR = '/Xcode_12.1.app/Contents/Developer'
xcrun: note: XCODE_DEVELOPER_USR_PATH = ''

# The database with all the links
xcrun: note: xcrun_db = '/var/folders/gn/8j0pyrwj5j3ggxprkczkpwtc0000gn/T/xcrun_db'

xcrun: note: xcrun via git (xcrun)
xcrun: note: database key is: git|/Xcode_12.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk||/Xcode_12.1.app/Contents/Developer|
xcrun: note: lookup resolved in '/var/folders/gn/8j0pyrwj5j3ggxprkczkpwtc0000gn/T/xcrun_db' : '/Xcode_12.1.app/Contents/Developer/usr/bin/git'
/Xcode_12.1.app/Contents/Developer/usr/bin/git

Side notes

  • Check the previous note about xedXcode invocation tool
  • Create a minor task for your DevOps team to prefix all the calls to Xcode tools with xcrun

Follow me on Twitter – @justsitandgrin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment