Build iOS apps from the command line using xcodebuild
Tags: ios, xcode, automation, tools
Published: 2021-10-27 18:06:53

---
title: Build iOS apps from the command line using xcodebuild
published: true
date: 2021-10-27 18:06:46 UTC
tags: ["ios", "xcode", "automation", "tools"]
canonical_url: https://tarikdahic.com/posts/build-ios-apps-from-the-command-line-using-xcodebuild/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nl9cltvyneqa7q2fk7ey.jpg
---

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:

```sh
xcodebuild -help
```

If you have Xcode and don’t have this tool you can install it by running this in the terminal:

```sh
xcode-select --install
```

Or you can download the command line tools from [developer.apple.com](https://developer.apple.com/download/all/).

## 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.

![Build process](https://tarikdahic.com/images/xcodebuild-build-process.png)

#### 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:

```sh
# 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:

```sh
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_.

```sh
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:

```sh
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:

```sh
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
<?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>&lt;none&gt;</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:

```sh
xcode-select -p
```

- To change the path to custom version of Xcode we can enter this command:

```sh
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:

```sh
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:

```sh
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](https://apps.apple.com/ba/app/transporter/id1450874784?mt=12), 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:

```sh
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](https://apps.apple.com/us/app/apple-configurator-2/id1037126344?mt=12) which is an app from Apple. This is my preferred way of installing the apps via macOS.
- Linux users can use [ideviceinstaller](https://github.com/libimobiledevice/ideviceinstaller) which I tested and it worked great.
- Windows users can use [iMobileDevice](https://www.quamotion.mobi/docs/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](https://fastlane.tools/) and [bitrise](https://www.bitrise.io/) 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!