At some point in your iOS development career you will want to automate app building and distribution when there are new changes on your version control system. At the company I work for we did just that - whenever a pull request is merged our CI/CD system is notified and it starts to build and test the app with the newest changes. The successful builds are then deployed to QAs and they are ready for manual testing and verification. This can save you a lot of time in your development workflow. In this article I will go through the process on how to create .ipa file from your project that is ready for testing or distribution using only terminal.
Tools
You will need to have Xcode Command Line Tools installed on the machine that will be building the app. If you already have Xcode it is very likely that you have Xcode Command Line Tools installed. Specifically, we will be using the xcodebuild tool . To see if you have it installed you can run:
xcodebuild -help
If you have Xcode and don’t have this tool you can install it by running this in the terminal:
xcode-select --install
Or you can download the command line tools from developer.apple.com.
Building
Building iOS apps is a two step process. First we compile and link the compiled code using archive step and then we export and sign that archive to create the .ipa file.
Choosing what to build
Open the terminal and navigate to the root of the folder of your project. We need to know what will we build so let’s see what options we have:
# If you are using Xcode project:
xcodebuild -list -project <project-name>.xcodeproj
# or if you are using Xcode workspace:
xcodebuild -list -workspace <project-name>.xcworkspace
You should get information about available targets, build configurations and schemes. Schemes contain data about which targets and configurations to use when building so remember what scheme you want to use.
Clean
Before building anything that will be distributed internally or externally I always like to perform clean operation. You can do that via xcodebuild like this:
xcodebuild clean
Archive
The first step in the actual building is to create an app archive. It will compile your code and sign it with you development certificate. This step is analogous to Archiving via Xcode. Xcode generated archives can be accessed in Xcode’s Organizer.
xcodebuild archive -scheme <scheme-that-you-want-to-use> -sdk iphoneos -allowProvisioningUpdates -archivePath <path-and-name-for-archive>.xcarchive
In the example above I included the flag -allowProvisioningUpdates and this will generate or modify provisioning profiles if you use Automatic signing. This option is not very reliable and in most of my apps I’ve downloaded and imported manually provisioning profiles or certificates to Keychain.
Documentation for allowProvisioningUpdates:
Allow xcodebuild to communicate with the Apple Developer website. For automatically signed targets, xcodebuild will create and update profiles, app IDs, and certificates. For manually signed targets, xcodebuild will download missing or updated provisioning profiles. Requires a developer account to have been added in Xcode’s Accounts preference pane or an App Store Connect authentication key to be specified via the -authenticationKeyPath, -authenticationKeyID, and -authenticationKeyIssuerID parameters.
Because I am building for iOS I provided -sdk iphoneos
and if you want to see what other sdks are available or if you want to build your app for another platform you can use:
xcodebuild -showsdks
Export
In export step we need to decide how are we gonna distribute the .ipa file that we will generate and we need to sign it with distribution certificate. If you already archived and exported the app from your computer using Xcode you have the distribution certificate in Keychain and you will be able to complete this step. If you don’t have the distribution certificate you can generate a new one or you can export existing one from your development computer and import it into the computer that will be doing the export step.
We can run the export operation with the command below:
xcodebuild -exportArchive -archivePath <path-and-name-for-archive-from-the-previous-step>.xcarchive -exportOptionsPlist exportOptions.plist -exportPath <path-where-ipa-file-will-be-saved>
The export options plist file plays an important role in this step. It is an xml formatted file that contains all the necessary information for xcodebuild
so that the .ipa file can be generated.
Export options
If you don’t know how to generate your export options plist file you can see how Xcode does it for your app. For example, archive the app inside Xcode and then proceed with exporting it from the Xcode Organizer. Choose Ad-Hoc
distribution and export it wherever you like. Navigate to the folder where your app .ipa file was exported and in it you will find the ExportOptions.plist file that Xcode internally generated. This could be a great starting point for manually building your apps because we can use that file for builds from command line.
This is how the export options file looked for one of my apps:
<?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>
<true/>
<key>destination</key>
<string>export</string>
<key>method</key>
<string>ad-hoc</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>YOUR_TEAM_ID</string>
<key>thinning</key>
<string><none></string>
<!-- If you need to provide provisioning profiles you can do it like this: -->
<key>provisioningProfiles</key>
<dict>
<key>APP_IDENTIFIER</key>
<string>Ad Hoc Distribution</string>
</dict>
<!-- I didn't need to provide provisioning profiles for this app
because its signing style is set to automatic -->
</dict>
</plist>
There are a lot of more options which you can find by running xcodebuild -help
.
Export method
Documentation for export method from xcodebuild
:
Describes how Xcode should export the archive. Available options: app-store, validation, ad-hoc, package, enterprise, development, developer-id, and mac-application. The list of options varies based on the type of archive. Defaults to development.
Mostly used ones for iOS are:
- app-store: Generates an .ipa signed with your distribution certificate that is ready for production or deploying to TestFlight.
- ad-hoc: Generates an .ipa signed with Ad-Hoc distribution certificate which will enable users that have their device identifiers registered in your Developer portal to install the app.
- enterprise: Generates an .ipa signed with your Enterprise Distribution certificate which will enable you to distribute the app in your organisation. For this option you need to have Enterprise Apple developer account.
- development: Generates an .ipa signed with your Development certificate which will enable your account members to install and use the app.
At the company I work for we use Ad-Hoc
export method for distributing apps to our QAs and we use enterprise
export method for sending custom builds with debug options so that we can resolve tricky issues.
Building with different versions of Xcode
In some cases we need to build apps with different version of Xcode. This might happen when a new Xcode with new iOS version and SDK is announced but there are some breaking changes that will take some time to fix. To build the app with an older Xcode version we need to change what Xcode our command line tool uses.
- To see the current Xcode path we can enter this command:
xcode-select -p
- To change the path to custom version of Xcode we can enter this command:
xcode-select -switch <path>
where <path>
is the path to Xcode.app
package.
- If you want to verify that correct version is being used by command line tool you can use this command:
xcodebuild -version
Issues that can happen
- There might be an error with unverified .framework files and to fix this issue, this command should be entered before the build process:
sudo spctl --master-disable
Deploying and installing
Now that you have the .ipa file, you can upload it to App Store for production and TestFlight or you can distribute it to your organization or QAs.
Uploading to App Store
You can upload the generated .ipa file to App Store from terminal or using the Transporter, an app from Apple that is made for uploading builds.
Uploading from the command line can be done using xcrun altool
. To read more about it you can run man altool
and get the manual for using it. Example for uploading the app to the App Store using this tool coud look like this:
xcrun altool --upload-app --type ios --file <path-to-your-package-ipa> --username <user-name> --password <app-specific-password>
You need to provide the altool
with username and app specific password. The username is your Apple ID and app specific password can be generated in App Store Connect for this app to authorize it.
Installing to devices
This procedure depends on the operating system that the person who wants to install the app is using. The prerequisite for this step is to connect your iOS device to the computer.
- macOS users can use Apple Configurator 2 which is an app from Apple. This is my preferred way of installing the apps via macOS.
- Linux users can use ideviceinstaller which I tested and it worked great.
- Windows users can use iMobileDevice builds for Windows. I haven’t tested this approach but after researching it a bit it seems to work good.
Distributing to your organisation
If you have the Enterprise Apple Developer account the best way to distribute the apps is by using the manifest file that Xcode can generate for you when you export the app via Xcode. You can use that manifest file to host the .ipa on some web server and allow the users of your organisation OTA updates.
Manifest can also be generated when you use xcodebuild
. This is what the documentation says:
manifest : Dictionary
For non-App Store exports, users can download your app over the web by opening your distribution manifest file in a web browser. To generate a distribution manifest, the value of this key should be a dictionary with three sub-keys: appURL, displayImageURL, fullSizeImageURL. The additional sub-key assetPackManifestURL is required when using on-demand resources.
This is a really great way to ease the app installation for enterprise users.
Conclusion
Although you might never need to build your apps manually from the command line because there are great tools like fastlane and bitrise it will be very beneficial to understand what happens under the hood. When things go wrong you will have the knowledge to fix the issues that happen by automating app building.
By knowing this you can even create your own CI/CD tool. Add a git hook
to run tests and builds for your app in a script that contains the contents of this post, expose the generated .ipa file via some web server and you have a great in-house solution!
I hope that this article has helped you to understand how the build process for iOS apps work. If I forgot to add something or made a mistake somewhere please let me know in the comments!
Join the discussion
comments powered by Disqus