Chapters

Hide chapters

iOS App Distribution & Best Practices

First Edition · iOS 14.4 · Swift 5.3 · Xcode 12.4

Section I: iOS App Distribution & Best Practices

Section 1: 17 chapters
Show chapters Hide chapters

12. Build Automation
Written by Pietro Rea & Keegan Rush

The world of computing has come a long way in the last century.

The ancient predecessor to a modern computer is an abacus, used to add numbers. This became the inspiration for more sophisticated adding machines, and then calculating machines that could add, subtract, multiply and divide. But, until the twentieth century, computer programming was a non-existent profession.

In 1936, the first programmable computer was invented, and soon the modern age of computing was underway. In the early days, programmers had to work directly with machine code that was unique to each type of computer. It wasn’t until the invention of assembly language that programmers could worry less about the intricacies of ones and zeros and focus more on the software they were writing.

Eventually, in the 1950s, the industry took another leap forward with the invention of the first high-level programming language: FORTRAN. With each step to higher level, more abstract programming languages, a programmer has to worry less about how to write the code and more about the problems they’re trying to solve. The journey towards higher-level programming languages continues even today, with you writing Swift in Xcode’s cushy interface.

Before the adoption of integrated development environments (IDEs) like Xcode, programming was done in text editors, and before that, on the command line. Without an IDE, you have to invoke compilers from the command line to build apps.

Using Xcode means you get to worry less about the technical details and more about making an app that people will love. But, sometimes, you have to take a step back to take another gigantic leap forward.

In this chapter, you’ll step outside of Xcode and learn how to leverage the command line to build your apps. You’ll automate Emitron by creating a simple build script that does the following:

  1. Builds and archives Emitron.
  2. Exports the archive into an .ipa.
  3. Uploads the exported archive to App Store Connect.

In doing so, you’ll learn how you can automate the distribution process.

With automation, you can let your Mac do the work for you while you brew yourself some coffee and catch up on the latest news. :]

Note: This chapter requires some basic knowledge of the command line.

Also, if you’re already sold on the benefits of automation, feel free to skip to the next chapter. There, you’ll learn how to use fastlane, a powerful tool for automating builds and more.

Why automation?

Imagine the following manual release process:

You need to release a new build of your app to the App Store. The code is ready, but you need to build the app for distribution using Xcode’s Product ▸ Archive menu option. Then, you sign the archive for App Store distribution and upload it to App Store Connect.

On App Store Connect, you have to add in all the metadata for your release: your app categories, copyright info, marketing copy, review information and more.

Next up is one of the most tedious parts of releasing an app: the screenshots. You’ll need to go back to Xcode, run your app in a handful of different simulators to take a handful of different screenshots. If your app is localized into other languages, don’t forget to take screenshots for those languages, too.

Once you’ve added your metadata and screenshots, you’re ready to submit your app for review.

From build to App Store

Now, consider the alternative automatic release process.

The code is ready, so you run your automation script. While that’s running, go catch up with a friend or take your dog for a walk, because your Mac is about to handle everything for you.

First, script builds your app for the App Store and signs it.

Next, your automation script uploads the archive to App Store Connect. Then, the script takes the screenshots for you. It spins up five different device simulators to take screenshots across all the languages you support. Afterward, it updates your build on App Store Connect with your screenshots and app metadata.

Once everything is ready, the automation script submits the build for review automatically. All you have to do is sit back and wait for that glorious email from Apple telling you that your app is ready for sale.

Other benefits of automation

Besides the automation script handling all the steps from build to App Store, there’s a myriad of other benefits to automating your build. This includes:

  • Validation by testing
  • Consistency
  • Shared knowledge
  • Frequent builds
  • Continuous integration

Validation by testing

The benefits of automated testing are well known, but that doesn’t mean developers remember to run tests before every release build. By including tests in an automated build, you have the assurance that everything works as expected. Any failing tests will stop the build to let you catch bugs before they hit the App Store.

You can take your testing even further by incorporating static analysis in your build script. Before even compiling your code, static analysis tools inspect your app for code smells and bad patterns and let you know where you can improve. It’s like an automated code review to catch issues before they become a problem.

Consistency

Leaving release builds as a manual process opens it up to human errors if you don’t pay enough attention. You could forget to run your automated tests, or compile the app with the wrong build configuration or provisioning profile. Or even worse, you might end up mistakenly releasing a feature that you haven’t finished yet!

Automating your build is sort of like following a recipe. You’re the chef writing the recipe, and the computer doing the build is a line cook that executes the recipe perfectly every time. With automation, the steps you follow for a new release build is the same as the steps for the last one.

By writing a script to automate all the steps needed to go from building the app to putting it on the App Store, you know that every step will be followed exactly as intended. You only have to build that perfect recipe once, and your computer will always follow it line by line without mistakes.

Shared knowledge

Have you ever taken over development of an old app that someone else built?

For a complex legacy app, the setup process can be more involved than just downloading the code from GitHub. You might need to have certain tools installed on your laptop, or maybe you need to edit some configuration. You might be subjected to a lot of back and forth trying to build the app with the correct dependencies and linked frameworks.

This isn’t a problem for the developers that built the app originally. For new developers on the team, however, it can be tiring. For these old apps, there’s a good chance that the knowledge you’d need to get up and running is implicit. The knowledge is in the heads of the developers that came before, but it’s not written down anywhere.

By writing down all the steps to build an app in a recipe like a build script, that knowledge becomes explicit. The knowledge leaps out of the heads of developers and into the world, where a new developer can follow the steps with ease.

The idea of an automated build means that any authorized team member on any Mac can get your app’s code and build it at the click of a button. The build script acts as a living document of everything that’s needed to get an app up and running on devices.

Frequent builds

Because it’s so easy to release new builds when you’ve automated the process, builds can come more frequently. The lower barrier to building means getting your latest work into the hands of testers and the general public much sooner.

One benefit of frequent builds is how easy it becomes to identify and fix issues. If you’re only making new alpha builds every three months, it can be difficult to know the cause of a problem when something goes wrong. Was it a change you made a week ago, or a month ago? When your alpha builds come regularly and in small chunks, testing becomes more manageable.

Your users will thank you, too. The quicker you can release a new version of your app, the sooner everyone gets a chance to play with your shiny new features. :]

Continuous integration

The pinnacle of frequent builds, continuous integration lets you build and deploy your app as soon as you push your changes to GitHub. You don’t even have to run the automation script yourself.

Rather than deploying directly from your development machine, continuous integration services connect to your Git repository in the cloud.

To find out more, have a look at Chapter 14, “Continuous Integration”.

Setting up the starter project

Before you create a build script for Emitron, you’ll need to configure the project for your Apple Developer account.

Open the starter project. In Xcode, follow these steps to find the code signing settings that you need to change:

  1. Navigate to the project screen.
  2. In the left sidebar, click on the emitron target.
  3. Change to the Signing & Capabilities tab.

Firstly, under Signing (Debug), change the Team to the correct team for your Apple Developer account.

Next, under Signing (Release), make sure that Provisioning Profile matches the profile you use to upload release builds of Emitron to App Store Connect.

Note: To learn more about provisioning profiles, or if you need a refresher on configuring them, refer to Chapter 4, “Code Signing & Provisioning”.

Lastly, under Signing (Alpha), change the Team to the correct team for your Apple Developer account. This is the same as what you did for Signing (Debug).

When you’re done, you won’t see any more errors in the Signing & Capabilities tab.

Now that code signing is set up, you’re ready to build Emitron on the command line.

Building with xcodebuild

You’re not far away from automatic builds in the cloud that run all your tests for you. But first, at the heart of every automation script is the need to actually build your app.

Beneath Xcode’s surface lies the Xcode toolchain, a set of tools including a compiler and debugger. When you build an app in Xcode, it’s the toolchain that does the heavy lifting.

When building a project, Xcode uses a tool from the toolchain named xcodebuild to compile your apps.

xcodebuild isn’t only available from within Xcode, however. All the tools in Xcode’s toolchain are available for you to use via the the terminal.

Just like building an app in Xcode, using xcodebuild lets you build, query, analyze, test or archive any of your project’s schemes from the command line.

To give xcodebuild a try, open a terminal window.

The starter project has an Emitron folder that contains Emitron.xcodeproj. In the terminal, cd to the Emitron folder.

Once there, call xcodebuild with the following command:

xcodebuild build -scheme Emitron

Here, you use xcodebuild to compile the Emitron scheme. This is the same as using Xcode’s Build action, accessible by the Product ▸ Build menu option.

The rest of Xcode’s actions are also available in xcodebuild.

xcodebuild syntax

When calling xcodebuild, you specify an action and a list of options.

Actions

There’s a variety of actions you can use with xcodebuild. Most of them correspond to actions available in Xcode, such as build, clean, archive or test.

Not every command uses an action – sometimes, you’ll call xcodebuild with only a set of options.

Options

Options are like parameters you use to configure your chosen action. Earlier, you used -scheme Emitron to specify that xcodebuild should build the Emitron scheme. Here are some other available options:

  • -project to specify the project if there’s more than one.
  • -target to build a particular target instead of a scheme.
  • -alltargets to build all the targets in the project, instead of just one.
  • -configuration to override the build configuration.

xcodebuild still has a few more tricks up its sleeves when it comes to actions and options, which you can discover by running man xcodebuild from your terminal.

To upload Emitron to App Store Connect, you’ll need to start with the archive action, just like you’d do in Xcode.

Archiving Emitron

You’ve learnt how to create a regular build of Emitron, but that’s not enough for your automation script. If you want to automatically upload Emitron to App Store Connect, you need to create an archive build first.

When you archive an app for distribution in Xcode, you use Xcode’s Product ▸ Archive menu option. In xcodebuild, you have an equivalent archive action. You’ll use that here to archive Emitron.

In the terminal, run the following:

xcodebuild archive -scheme Emitron \
  -configuration Release \
  -archivePath "Emitron.xcarchive"

To make things easier to read, the command you entered is split over multiple lines by adding a backslash at the end of each line, except the last.

Wait until it’s done building:

This uses xcodebuild’s archive action to compile the Emitron scheme, using the Release configuration. Then, it archives the build into an .xcarchive and saves it as Emitron.xcarchive in the current directory.

With an archived build of Emitron, your next step is to export it as an .ipa destined for App Store Connect.

Exporting Emitron

So far, you’ve archived Emitron into an .xcarchive file. An .xcarchive isn’t signed; so, it’s not ready for upload to App Store Connect. To get a signed binary, you need to export your archive into an .ipa.

When you export an archive in Xcode, your project file determines the appropriate signing certificate and provisioning profile.

When you export an archive using xcodebuild, you use an export options file to set all the configuration you need.

Creating an export options file

Since you don’t have a fancy UI to guide you through exporting an archive in xcodebuild, all the configuration needs to be set up front. That’s where the export options file comes in.

In an export options file, you provide all the information xcodebuild needs to package your app, such as:

  • The provisioning profile and signing certificate to use.
  • Which Apple Developer team to assign this build to.
  • Whether you want to package the archive for the App Store, ad-hoc, or enterprise distribution.

With all your options in the export options file, xcodebuild will run silently without any need to prompt you. That’s exactly what you want if you’d like to go out for a coffee when waiting for a build.

Back in your terminal, run this command:

touch ExportOptions.plist

That creates a new file in the current directory named ExportOptions.plist.

The export options file is a .plist, similar to an iOS project’s Info.plist.

Next, in your favorite text editor, open the empty ExportOptions.plist. Add this to its contents:

<!-- 1 -->
<?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">
<!-- 2 -->
<dict>
  <!-- 3 -->
  <key>method</key>
  <string>app-store</string>
  <!-- 4 -->
  <key>provisioningProfiles</key>
  <dict>
    <key>com.raywenderlich.emitron.pias</key>
    <string>Emitron App Store</string>
  </dict>
</dict>
</plist>

Ensure the release provisioning profile key-value pair matches the one in your developer account. This means the provisioning profile’s app identifier and name have to match.

At its core, a .plist file is just XML. Here’s what’s going on in ExportOptions.plist:

  1. Every .plist starts with this standard metadata.
  2. The file is a key-value store of different configurations, indicated by the dict element.
  3. The first entry in the dictionary has a key set to method and a value set to app-store. This means that xcodebuild will export Emitron for the App Store. The options are the same as those you get when distributing an app from Xcode.
  4. Here, you set the provisioning profiles used for signing your archive. An archive can have more than one app inside, such as an iOS app bundled with a Watch app. So, you provide the provisioning profiles in the form of a dictionary with an entry for each bundle identifier in your archive.

You’ve created an export options file in its simplest form: with the options you specified, xcodebuild will package Emitron for App Store distribution with the Emitron App Store provisioning profile.

Exporting the archive

With an export options file, you’re ready to export Emitron into an .ipa that you can upload to App Store Connect.

In the terminal, run this command:

xcodebuild -exportArchive \
  -archivePath Emitron.xcarchive \
  -exportOptionsPlist "ExportOptions.plist" \
  -exportPath .

Using the -exportArchive option of xcodebuild, you’ve packaged an archive into an .ipa along with ExportOptions.plist. With that, you’re ready to upload Emitron to App Store Connect via the command line!

Uploading Emitron to App Store Connect

Using the command line, xcodebuild can upload Emitron straight to App Store Connect. With one new option in ExportOptions.plist, xcodebuild will upload your .ipa instead of leaving it in the project folder. That means there’s no need to use any other tools to upload your build.

Back in your text editor, open ExportOptions.plist once again.

After the provisioningProfiles dictionary, add this:

<key>destination</key>
<string>upload</string>

The destination option has two possible values: export or upload. Here, you set it to upload to instruct xcodebuild to upload your .ipa straight to App Store Connect.

The alternative, export, is the default: it’ll create raywenderlich.ipa like earlier.

That’s the only change you need to go from exporting Emitron.xcarchive to uploading it.

Back in the terminal, run the same xcodebuild command from earlier:

xcodebuild -exportArchive \
  -archivePath Emitron.xcarchive \
  -exportOptionsPlist "ExportOptions.plist" \
  -exportPath .

You may be asked to sign in to your Apple Developer account. Provide your account details if prompted to do so.

The archive will take some time to upload. Once it’s done, you’ll get a confirmation that Emitron was uploaded.

In App Store Connect, you’ll see your new build uploaded in the Activity tab and ready for you to submit it for app review:

Great job!

Note: If the upload failed with a Redundant Binary Upload error, read on to the next section.

You need to change your project’s build version. Once that’s done, archive Emitron once more with the steps in the Archiving Emitron section and then repeat the upload again.

Now that you’ve uploaded a new build to App Store Connect, don’t forget that you have to manually increase Emitron’s build version before you upload a new build.

Bumping the build version

When you upload a build to App Store Connect, its build version has to be unique. If it isn’t, you’ll get a Redundant Binary Upload error when xcodebuild tries to upload the exported archive.

If you’ve already uploaded a build to App Store Connect, you’ll need to increment Emitron’s build version before uploading another.

Since you just uploaded a build with xcodebuild, now’s a good time to bump the build version before you upload anything else.

You need to ensure that the combination of the app’s Bundle version string (short) and the Bundle version hasn’t already been uploaded to App Store Connect:

In Xcode, open Info.plist.

Increase the Bundle version entry’s value by one:

Now, you have a unique build version. This means you’re ready to upload a new build to App Store Connect using the commands you learned earlier. But, wouldn’t it be easier to have one simple command to run instead of remembering how to manually use xcodebuild?

Tying it all together

Using xcodebuild to manually build and upload Emitron gets you out of Xcode, but there’s not much benefit beyond that. You still haven’t automated the process.

In its current form, you have to enter the correct commands each time. That means all the dangers of human error are still there!

To truly automate Emitron’s build, you need to put all the previous steps into one build script. With your build script, archiving and uploading Emitron will be easy and repeatable with one simple command.

In your terminal, run the following:

touch build.sh

This creates an empty shell script, build.sh. In the script, you can call xcodebuild just like you did on the command line.

In your text editor, open build.sh. Add this to its contents:

#!/usr/bin/env bash

# 1
xcodebuild archive -scheme Emitron \
  -configuration Release \
  -archivePath "Emitron.xcarchive"

# 2
xcodebuild -exportArchive \
  -archivePath Emitron.xcarchive \
  -exportOptionsPlist "ExportOptions.plist" \
  -exportPath .

At the beginning of the file is a shebang, which tells your computer how to execute the script. The shebang always has to be first line of the script.

The rest of the file is a combination of the commands you’ve used so far. Here’s what’s happening, step by step:

  1. Archive Emitron to create Emitron.xcarchive.
  2. Export and upload the archive to App Store Connect.

If you need to, increase the build version following the instructions in the previous section.

You’ve created build.sh, but it’s just a text file at the moment. Your macOS user doesn’t have the permissions to run it as a script yet.

Granting file permissions

Every file has a set of permissions that determine who can read, write or execute it. You need permission to execute build.sh if it’s going to be of any use.

So, in the terminal, run this command:

chmod u+x build.sh

That gives permissions to the current user to execute build.sh.

Running the script

Now that you have the permissions to do so, run the script in the terminal:

./build.sh

Your build script kicks off xcodebuild, first archiving it and then uploading it to App Store Connect.

Once it’s done, you’ll get a confirmation that xcodebuild uploaded Emitron.

You’ll see the build waiting for you on App Store Connect:

Nicely done!

More possibilities with automation

In this chapter, you learned how to build, archive, export and upload Emitron using xcodebuild. It’s a great start, but automation can go much further than creating a build and uploading it to App Store.

Once you’ve uploaded your build, it’s up to you to take your screenshots, manage your metadata and submit the app for review. That is unless you automate the process.

App Store Connect has an API that you can use in an automation script. You can find out more at https://apple.co/2HRAQ6u.

With the tools provided by Apple, you can go from building to submitting your app for review, all in one script. As you dive deeper into automation, your scripting work becomes a lot more complicated.

Luckily, there are tools to make automation a breeze. Read on into the next chapter to supercharge your automation with fastlane.

Key points

  • Automation saves time, shares knowledge and prevents mistakes.
  • Automating your build is the first step towards continuous integration.
  • xcodebuild is the tool that Xcode uses to build your app. You can use it too, via the command line.
  • To upload a new build to App Store Connect, you need to export an archive as an .ipa.
  • xcodebuild needs an export options file to export an archive.
  • By setting destination to upload in an export options file, xcodebuild can even upload an exported archive directly to App Store Connect.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.