The deployment of an iOS app to iTunes Connect involves various things and it’s time-consuming and fragile activity. It’s very common practice for iOS developers use Xcode to build, test, archive and upload the app to iTunes Connect. Fortunately, Apple has command line tools to do all these things and one of the most popular utility is xcodebuild. There is a fancy wrapper on Apple Developer tools called Fastlane which can be used for scripting deployment of an iOS application. You might be wondering why we need to use the command line to deploy the iOS application. Can you imagine a day without Xcode? Or Can you think of deploying an app from the server where you don’t have GUI access? Or What if you don’t have scripting skills in Ruby to use Fastlane?
We must know what’s happening behind the scene when we build, test, archive and upload an app. We should aware of the native Apple developer tools used for all these activities. By learning command line interaction, we can get following benefits
- In-Depth knowledge of underlying Apple technologies used while building, deploying an iOS application.
- Ease of automating iOS development task for continuous integration or cloud-based servers
- Better understanding of what tools like Fastlane are doing in the background.
In this post, we will see how we can build, test, archive and deploy a sample iOS app to iTunes Connect.
Pre-requisite
In order to understand the command line deployment process, we should have following things setup in advance.
- Sample app with Xcode workspace. e.g CLI.xcworkspace
- Xcode Scheme with Release configuration: CLI-Release
- Certificates, AppID, Provisioning profiles setup for production in iTunes Connect. Use Apple’s documentation to setup all the things. We will assume provisioning profile CLI Distribution profile for this tutorial.
- macOS with Distribution certificate in the keychain
We will cover the cycle of the iOS app from analysing app till deploying to iTunes Connect using xcodebuild.
Analysing
One of the sensible things to do before we build and test an iOS app is performing static analysis of the source code. The xcodebuild has an ability to clean and analyse the source code for any common syntax errors. We can pass our Xcode project e.g CLI.xcodeproj
1 |
$ xcodebuild -project CLI.xcodeproj -scheme CLI -sdk iphonesimulator10.3 clean analyze |
Building
We can build an iOS app using xcodebuild ‘build’ action which generates the derived data for our iOS app. Once the app is built, it can run inside the simulator or can be used by test bundle.
build for running
We can simply build our app to run inside simulators by using simple command
1 |
$ xcodebuild -scheme CLI -workspace CLI.xcworkspace/ build |
This will create a derived data inside ~/Library/Developer/Xcode/DerivedData/ directory. Thre are various options that we can pass to override the default settings so that we can control the artefact e.g -destination or -derivedDataPath etc
build for testing
Xcode 8 has introduced this nice feature to allow building once and use the derived data .xctestrun file to test multiple time on different destinations.
We can build for testing using the command:
1 2 |
$ xcodebuild build-for-testing -workspace CLI.xcworkspace -scheme CLI -destination generic/platform=iOS |
Now that, we can use test-without-building action to run tests without building an app.
Testing
Apple has XCTest framework to perform unit and UI testing of an iOS app. We can update our release scheme to include a suite of tests. Assuming our ‘CLI’ scheme is configured to run tests, then we can run tests associated with scheme using
1 |
$ xcodebuild -scheme CLI -workspace CLI.xcworkspace/ test |
This will build our scheme and start executing tests for that particular tests.
test-without-building
If you have build app using ‘build-for-testing’ option mentioned above, we can perform testing on multiple destinations using same derived data.
1 2 3 4 |
$ xcodebuild test-without-building -workspace CLI.xcworkspace -scheme CLI -destination 'platform=iOS Simulator,name=iPhone 7' -destination 'platform=iOS,name=My iPad' |
This will execute tests for iPhone as well as on iPad. We don’t have to build app twice to run the tests on different destinations.
Archiving
The most of the iOS engineers find the process of archiving an iOS app is very painful as it involves dealing with provisioning profiles, certificates and build configurations. In order to upload the app to iTunes Connect or deploy it on to the provisioned devices, we need to build and app for generic iOS device destination as well as export it in IPA format.
Let’s build our app with release configuration scheme ‘CLI’ with generic iOS device destination.
1 2 3 |
$ xcodebuild -workspace CLI.xcworkspace \ -scheme CLI \ -destination generic/platform=iOS build |
xcodebuild has a new -exportArchive option to create an IPA that works more like Xcode Organizer.
There are two steps
- build an archive with xcodebuild archive
- create the .ipa with xcodebuild -exportArchive
We now build the archive like this:
1 |
$ xcodebuild -workspace CLI.xcworkspace -scheme CLI -sdk iphoneos -configuration AppStoreDistribution archive -archivePath $PWD/build/CLI.xcarchive |
We now export the .ipa like this:
1 |
$ xcodebuild -exportArchive -archivePath $PWD/build/CLI.xcarchive -exportOptionsPlist exportOptions.plist -exportPath $PWD/build |
This two command create the file build/myApp.xcarchive and build/myApp.ipa
Note that above command requires a -exportOptionsPlist argument that points to a .plist file with export options. For a complete list of what you can put in that plist, run xcodebuild -help . The minimal contents of the file look like this:
1 2 3 4 5 6 7 8 9 10 |
<?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>method</key> <string>app-store</string> <key>teamID</key> <string>YOUR_TEN_CHARACTER_TEAM_ID</string> </dict> </plist> |
we run this command successfully, we will have IPA file created using provisioning profile ‘CLI Distribution Profile’. At the end, we will have our IPA, CLI.ipa binary ready to upload to iTunes Connect.
Uploading IPA to iTunes Connect
The last step of the deploying our app is to upload the binary to iTunes Connect. Usually Xcode has ‘Application Loader‘ app to perform this task. Fortunately, we have command line interface for the Application loader app called ‘altool’. The binary file for altool comes up with Xcode so no need to download it separately. The binary is usually located at the path
1 |
/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool |
It would be a great idea to export that binary to $PATH so that it can be accessed from anywhere. We can use this binary to upload our IPA to iTunes Connect. We need our Apple ID username and password to upload, assuming we have those credentials in the keychain or have it as ENV Variables $USERNAME and $PASSWORD
1 |
$ altool --upload-app -f "CLI.ipa" -u $USERNAME -p $PASSWORD |
You can explore other command line options for ‘altool’ on the Apple official documentation. It will take a huge amount of time to upload but be patient and enjoy your IPA uploaded to iTunes Connect.
What Next
Now that, we have successfully uploaded an IPA to iTunes Connect, we can distribute to testFlight for internal testing or submit it to Apple for review from iTunes Connect
At this stage, you might have a brief idea about the internal tools that Apple use to build, archive and deploy an iOS app to iTunes Connect. There are various other open-source wrappers available to make our life easy like Fastlane, ios-deploy etc. Please comment if you think something is missing
Happy Continuous delivery!