How to build an iOS app archive via command line
In my previous post, I detailed “How to export an Ad Hoc iOS ipa using Xcode” however there are advantages to exporting an iOS app archive using the command line. Top of mind reasons include:
- faster than using Xcode with a mouse
- can automate the build process (e.g. with GitHub Actions)
Make sure you followed along in the previous post so all prerequisites are met or have an active iOS app that you’ve successfully built and exported at least once. Also, shout out to Tarik Dahic as much of this blog is based on his excellent 2021 blog “Build iOS apps from the command line using xcodebuild”.
Note: you need to be enrolled in the Apple Developer Program (vs. the free Apple ID account) to create an Ad Hoc distribution directly from Xcode (there is a command line work around but your app won’t be properly signed). You can still use the free Apple ID account with Xcode, the iOS Simulator and to deploy the app to an Apple device physically connected to your computer.
1. Code setup
Make sure you have the iOS app source code on your computer:
Fork and clone the repo
$ mkdir -p ~/spfexpert; cd $_
$ git clone git@github.com:ahoog42/iamgroot.git
$ cd iamgroot
and change the build identifier.
2. Env setup
Next, assuming Xcode is installed, we will then install the Xcode Command Line Tools.
Install Xcode Command Line Tools
While you can install Xcode Command Line Tools from XCode, I find it easier to just kick off the install command line:
$ xcode-select --install
which will then prompt you to install the tools:
If you already have the command line tools installed, you get this error message (which you can ignore):
xcode-select: error: command line tools are already installed, use "Software Update" to install updates
3. Build the iOS app command line
To build an iOS app via the command line, there are a few steps:
- List the iOS app build information
- Optional - Remove previous build artifacts
- Build iOS app command line
- Archive iOS app command line
- Export iOS app archive command line
List the iOS app build information
For an iOS project, you can run the following command (if the app is more complex and uses workspaces, simple swap out -project
for -workspace <project>.xcworkspace
):
$ xcodebuild -list -project I\ am\ Groot.xcodeproj
which will list of key information you need to choose the correct app configuration to build:
Command line invocation:
/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -list -project "I am Groot.xcodeproj"
User defaults from command line:
IDEPackageSupportUseBuiltinSCM = YES
Information about project "I am Groot":
Targets:
I am Groot
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then "Release" is used.
Schemes:
I am Groot
For a simple “hello world” app, we can pretty much get away with just the defaults but as you work on more complex project, you will have options regarding the targets, configurations and schemes you want to build.
Optional - Remove previous build artifacts
If you want to ensure a clean slate, you can optionally choose to clean up previous build artifacts with this one liner:
$ xcodebuild clean
and you’ll see something along the lines of:
Command line invocation:
/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild clean
User defaults from command line:
IDEPackageSupportUseBuiltinSCM = YES
** CLEAN SUCCEEDED **
Build iOS app command line
Next, we’ll build the iOS app with the following command:
$ xcodebuild build -scheme "I am Groot" -sdk iphoneos -destination generic/platform=iOS
which will generate significant output to the terminal (truncated below):
Command line invocation:
/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild build -scheme "I am Groot" -sdk iphoneos -destination generic/platform=iOS
User defaults from command line:
IDEPackageSupportUseBuiltinSCM = YES
Build settings from command line:
SDKROOT = iphoneos16.2
Prepare packages
Computing target dependency graph and provisioning inputs
Create build description
Build description signature: acd074eace0504e5cf9c89f62ea1af42
Build description path: /Users/hiro/Library/Developer/Xcode/DerivedData/I_am_Groot-gfxpkzbpazztxzabhewsezcfrozd/Build/Intermediates.noindex/XCBuildData/acd074eace0504e5cf9c89f62ea1af42-desc.xcbuild
note: Building targets in dependency order
CreateBuildDirectory /Users/hiro/Library/Developer/Xcode/DerivedData/I_am_Groot-gfxpkzbpazztxzabhewsezcfrozd/Build/Products/Debug-iphoneos
cd /Users/hiro/spfexpert/iamgroot/I\ am\ Groot.xcodeproj
builtin-create-build-directory /Users/hiro/Library/Developer/Xcode/DerivedData/I_am_Groot-gfxpkzbpazztxzabhewsezcfrozd/Build/Products/Debug-iphoneos
<snip>
Touch /Users/hiro/Library/Developer/Xcode/DerivedData/I_am_Groot-gfxpkzbpazztxzabhewsezcfrozd/Build/Products/Debug-iphoneos/I\ am\ Groot.app (in target 'I am Groot' from project 'I am Groot')
cd /Users/hiro/spfexpert/iamgroot
/usr/bin/touch -c /Users/hiro/Library/Developer/Xcode/DerivedData/I_am_Groot-gfxpkzbpazztxzabhewsezcfrozd/Build/Products/Debug-iphoneos/I\ am\ Groot.app
** BUILD SUCCEEDED **
As you can see, the iOS app build process will create files in the DerivedData folder managed by Xcode (~/Library/Developer/Xcode/DerivedData/
).
Archive iOS app command line
Then we’ll create the iOS app archive which is an intermediary bundle that includes your app binary and symbols for debugging and crash reporting. To do this, issue the following command:
$ xcodebuild archive -scheme "I am Groot" -sdk iphoneos -destination generic/platform=iOS -archivePath ./build/iamgroot.xcarchive
which will create a directory in the current directory called build/iamgroot.xcarchive
and like before, will generate verbose output:
Command line invocation:
/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild archive -scheme "I am Groot" -sdk iphoneos -destination generic/platform=iOS -archivePath ./build/iamgroot.xcarchive
User defaults from command line:
IDEArchivePathOverride = /Users/hiro/spfexpert/iamgroot/build/iamgroot.xcarchive
IDEPackageSupportUseBuiltinSCM = YES
Build settings from command line:
SDKROOT = iphoneos16.2
Prepare packages
Computing target dependency graph and provisioning inputs
Create build description
Build description signature: 2e45f1ed16bb0b981d471b57cb445da6
Build description path: /Users/hiro/Library/Developer/Xcode/DerivedData/I_am_Groot-gfxpkzbpazztxzabhewsezcfrozd/Build/Intermediates.noindex/ArchiveIntermediates/I am Groot/IntermediateBuildFilesPath/XCBuildData/2e45f1ed16bb0b981d471b57cb445da6-desc.xcbuild
note: Building targets in dependency order
<snip>
Touch /Users/hiro/Library/Developer/Xcode/DerivedData/I_am_Groot-gfxpkzbpazztxzabhewsezcfrozd/Build/Intermediates.noindex/ArchiveIntermediates/I\ am\ Groot/InstallationBuildProductsLocation/Applications/I\ am\ Groot.app (in target 'I am Groot' from project 'I am Groot')
cd /Users/hiro/spfexpert/iamgroot
/usr/bin/touch -c /Users/hiro/Library/Developer/Xcode/DerivedData/I_am_Groot-gfxpkzbpazztxzabhewsezcfrozd/Build/Intermediates.noindex/ArchiveIntermediates/I\ am\ Groot/InstallationBuildProductsLocation/Applications/I\ am\ Groot.app
** ARCHIVE SUCCEEDED **
Export iOS app archive command line
Finally, you can create the iOS app .ipa
file but first you’ll need an ExportOptions.plist
file. In my previous post, this file is actually created by Xcode when you manually export an iOS app archive with “Automatically Manage Signing” enabled.
So instead of creating this file by hand, you could simply locate the plist
file in the directory where you saved the iOS app archive. Alternatively, here’s what a basic file looks like and if you update the teamID
with your team, you should be able to use this file as is:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>compileBitcode</key>
<false/>
<key>method</key>
<string>development</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>YOUR-TEAM-ID-HERE</string>
<key>thinning</key>
<string><none></string>
</dict>
</plist>
You then simply provide the exportOptions.plist
file as a parameter to export the iOS app archive:
$ xcodebuild -exportArchive -archivePath ./build/iamgroot.xcarchive -exportOptionsPlist exportOptions.plist -exportPath ./build
This will output status as follows:
2023-02-14 16:45:46.227 xcodebuild[24792:218357] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path "/var/folders/s0/ps95pmts5_93qf5ms7zv9rz00000gn/T/I am Groot_2023-02-14_16-45-46.226.xcdistributionlogs".
Exported I am Groot to: /Users/hiro/spfexpert/iamgroot/build
** EXPORT SUCCEEDED **
The resulting .ipa
file will be in the ./build
directory:
$ ls -ltrh build
total 8112
drwxr-xr-x 5 hiro staff 160B Feb 14 16:44 iamgroot.xcarchive
-rw-r--r-- 1 hiro staff 3.9M Feb 14 16:45 I am Groot.ipa
-rw-r--r-- 1 hiro staff 16K Feb 14 16:45 Packaging.log
-rw-r--r-- 1 hiro staff 466B Feb 14 16:45 ExportOptions.plist
-rw-r--r-- 1 hiro staff 1.4K Feb 14 16:45 DistributionSummary.plist
as well as various plist
and log files. You can then use the .ipa
file as need, for example uploading it to NowSecure Platform for automated mobile app security and privacy testing. :-)