Xcode Cloud is now available to all Apple developers and recently I had a chance to try it out in a large project and in my Snippety app. Below I gathered some howtos for a typical setup.
If you are more interested in the pros & cons of Xcode Cloud, you may want to read first Xcode Cloud review – is it ready for commercial projects?
How to set up Xcode Cloud?
Requirements: Xcode 13.4.1 or 14 beta, Apple Developer Program.
Pricing: 25 hours are free (until December 2023, after that it will be $14.99/month).
Xcode Cloud is really easy to set up. It is provided by Apple therefore all headaches with certificates, provisioning profiles, connecting to App Store, and releasing on TestFlight are no longer an issue. At least in terms of a basic configuration.
Below I will show you how to set up Xcode Cloud from scratch based on my Snippety app. It takes just a few clicks.
1. Make sure you are logged in using your Apple ID in Xcode.
2. Find Xcode Cloud and click “Create Workflow…”.
3. Select your product to build and make sure the correct team is selected.
4. Set up your Workflow. For now, you can leave it as it is.
5. Grant access to your repository.
This step will open your browser to grant access to your repository. After that, you should be redirected back to Xcode. Then you should be able to finish the configuration.
Done!
That’s all you need to do. Now you should be able to start your first build.
How to set up a workflow?
Workflow configuration is divided into 5 sections:
- General – just a few basic settings like workflow name, primary repository, and workspace selection.
- Environment – here you can add environment variables, and select the Xcode version, and macOS version.
- Start Conditions – here you set up when a build should be triggered. You have 4 options:
- Branch Changes
- Pull Request Changes
- Tag Changes
- On a Schedule for a Branch
- Actions – this is the core of every CI, unfortunately for now we have only 4 possible actions:
- Build
- Analyze
- Test – you can set up testing on multiple devices and OS versions. Supports Unit Tests and UI Tests.
- Archive – you can choose to publish the app automatically on TestFlight and App Store.
- Post-Actions – you can here select 3 actions:
- TestFlight Internal Testing – to share build with selected internal testers.
- TestFlight External Testing – to share build with selected external testers.
- Notify – to send a notification with build status using email or Slack.
As you can see, this is a very simple CI if you compare it to mature solutions like Bitrise. However, most of the time we just need to build, run tests, archive, and release. For that, it seems to be enough.
On top of that, you can also run some custom scripts. Unfortunately, you can’t manage them from Xcode.
Multiple App Store Connect profiles
You may also need to publish builds on more than one account. Fortunately, Xcode Cloud allows you to add multiple workflows and select a team for each of them. This way you should be able to add workflows for multiple profiles. Although, it will require you to set up Xcode Cloud and pay a subscription fee for each account.
Other repositories
It may happen that your project is using more than one git repository. Fortunately, it is easy to handle. Just run some build and in your App Store Connect all requested repositories will appear.
From there you will be able to grant access and rebuild your project. The procedure is the same as when granting access to your primary repository.
How to set up custom actions?
As we all know, the Apple ecosystem is our best friend and worst enemy at the same time. Nowadays, most projects are still using CocoaPods or Carthage, gems, and custom scripts.
Apple only supports by default Swift Package Manager. And from Xcode you can’t add custom scripts. However, it is possible by creating specific files. Below I will go into detail and show you how to set up the most common things.
Custom scripts
Xcode Cloud allows you to add custom scripts manually by creating scripts with specific names. First of all, you need to create a directory named ci_scripts
in your root source code directory.
Next, you can put there a script for each build phase:
- Post clone:
ci_post_clone.sh
- Pre-build:
ci_pre_xcodebuild.sh
- Post-build:
ci_post_xcodebuild.sh
The last thing is to set executable permission for each script by using the following command:
1 |
chmod +x ci_post_clone.sh |
You can use Shell in your scripts by starting the file with shebang #!/bin/sh
.
You can also use Swift by adding #!/usr/bin/env swift
.
More information about custom scripts you can find here.
Sudo problem
Xcode Cloud allows you to do almost anything you like unless you need sudo
. Sudo is not allowed. Therefore, most likely you will encounter some problems with your dependencies. To solve at least issues with RubyGems and Bundler I recommend you to use the following settings at the beginning of your script:
1 2 3 4 |
echo 'export GEM_HOME=$HOME/gems' >>~/.bash_profile echo 'export PATH=$HOME/gems/bin:$PATH' >>~/.bash_profile export GEM_HOME=$HOME/gems export PATH="$GEM_HOME/bin:$PATH" |
This will prevent installing dependencies in system directories that trigger sudo
requests.
How to set up CocoaPods?
This is most likely the most popular dependency for Xcode projects.
Xcode Cloud comes with pre-installed Homebrew. However, it is quite slow. We can do it better by using RubyGems and optionally Bundler.
Installing CocoaPods using Homebrew
This is officially presented in Apple Developer Documentation. Although, I think it is the worst way because it takes every time over 10 minutes. Probably they want you to migrate to SPM by showing how slow CocoaPods are 😈.
1 2 3 4 5 |
#!/bin/sh brew install cocoapods pod install |
Installing CocoaPods using RubyGems
Much faster solution using RubyGems instead of Homebrew.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/bin/sh cd .. echo ">>> SETUP ENVIRONMENT" echo 'export GEM_HOME=$HOME/gems' >>~/.bash_profile echo 'export PATH=$HOME/gems/bin:$PATH' >>~/.bash_profile export GEM_HOME=$HOME/gems export PATH="$GEM_HOME/bin:$PATH" echo ">>> INSTALL DEPENDENCIES" gem install cocoapods --install-dir $GEM_HOME echo ">>> INSTALL PODS" pod install |
Installing CocoaPods using Bundler
I think this is the most stable, fast, and reliable way to do it.
1 2 |
source "https://rubygems.org" gem 'cocoapods' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/bin/sh cd .. echo ">>> SETUP ENVIRONMENT" echo 'export GEM_HOME=$HOME/gems' >>~/.bash_profile echo 'export PATH=$HOME/gems/bin:$PATH' >>~/.bash_profile export GEM_HOME=$HOME/gems export PATH="$GEM_HOME/bin:$PATH" echo ">>> INSTALL BUNDLER" gem install bundler --install-dir $GEM_HOME echo ">>> INSTALL DEPENDENCIES" bundle install echo ">>> INSTALL PODS" bundle exec pod install |
Other dependencies
Now when you have a basic script, you can easily add some more dependencies. For example, if you use CocoaPods-Keys, Fastlane, Danger, or anything available via RubyGems, you can just add them to your Gemfile
:
1 2 3 4 5 |
gem 'cocoapods-keys' gem 'danger' gem 'danger-swiftlint' gem 'danger-xcov' gem 'fastlane' |
You can even use npm. However, it requires installation via Homebrew which is extremely slow. Below is an example showing how to install AppCenter CLI:
1 2 |
brew install npm npm install -g appcenter-cli |
Generating project using Tuist
If you are using tools to generate projects like Tuist or XcodeGen you will need to add a few extra steps to your script.
First of all, to set up Xcode Cloud you have to have a locally generated project and/or workspace. The Wizzard is reading from those files. Therefore, configuration at the App Store Connect website might not be possible without committing xcodeproj
and xcworkspace
.
Tuist unfortunately is not available via RubyGems which complicates things. You can’t use their command for installation either, because it requires at some point sudo
. Fortunately, Tuist provided a command to include binary in your repository. Just run the following script:
1 |
tuist bundle |
And push changes to your repository including the newly created directory .tuist-bin
.
Now you can include project generation in your ci_scripts/ci_post_clone.sh
:
1 |
.tuist-bin/tuist generate |
Snapshot Testing
If you have snapshot tests using for example SnapshotTesting, you will notice that Xcode Cloud can’t find your snapshot images. This is a known issue caused by the way Xcode Cloud is running tests. It first builds a project and then it runs tests on the pre-built product using a different environment.
This way tests don’t have access to snapshots that are by default located in source code directories. To work around this problem, you must move all your snapshots to the folder ci_scripts/Artifacts
.
However, it is still not enough, because the SnapshotTesting library doesn’t allow you to specify custom paths. Although, here you can find a working solution for that. Just remove all import SnapshotTesting
and add this custom assert to your target with tests.
Using this solution you can set a specific folder to record all snapshots. Then you can create a symbolic link ci_scripts/Artifacts
that points to your folder, or just store snapshots directly there.
Slack/Email integration
Xcode Cloud has built-in Slack and Email notifications that you can enable. Unfortunately, messages are not customizable. You can just select where, when, and how the build status will be published.
Summary
Now you should be able to set up Xcode Cloud in your project. As you can see, Xcode Cloud for now provides only basic tools, but in most cases, this should be enough.
Although, for large projects, Xcode Cloud might be quite limited because there aren’t any custom actions implemented out of the box. Also, Xcode Cloud isn’t very stable. For example, every ~5th build fails in my case, because of some network issues when downloading dependencies. I wasn’t able to set up unit tests for my Snippety app either because the launcher doesn’t start and in logs there is not much info why.
After reading this post you should have some basic idea of how Xcode Cloud works and what it offers. In my next post, I’m sharing some thoughts and pros & cons of using Xcode Cloud: Xcode Cloud review – is it ready for commercial projects?