Skip to main content

iOS/macOS

You can build and test iOS and macOS applications using a macOS platform on Harness Cloud, a self-managed macOS VM, or a local runner build infrastructure.

info

Harness recommends running macOS/iOS builds on Harness Cloud.

The examples in this guide use Xcode. You can also use Fastlane to build and test your iOS and macOS apps.

This guide assumes you've created a Harness CI pipeline.

Specify architecture

To use M1 machines with Harness Cloud, use the Arm64 architecture.

stages:
- stage:
name: build
identifier: build
type: CI
spec:
cloneCodebase: true
platform:
os: MacOS ## selects macOS operating system
arch: Arm64 ## selects M1 architecture
runtime:
type: Cloud
spec: {}

If you need to use Intel-based architecture, Rosetta is pre-installed on Harness Cloud's M1 machines. If you need to use it, add the prefix arch -x86_64 to commands in your scripts. Keep in mind that running apps through Rosetta can impact performance. Use native Apple Silicon apps whenever possible to ensure optimal performance.

Install dependencies

Use Run steps to install dependencies in the build environment.

Homebrew and Xcode are already installed on Harness Cloud macOS machines. For more information about preinstalled tools and libraries, go to the Harness Cloud image specifications.

- step:
type: Run
identifier: dependencies_ruby_gems
name: dependencies-ruby-gems
spec:
shell: Sh
command: |-
brew install fastlane

You can add package dependencies in your Xcode project and then run Xcode commands in Run steps to interact with your project's dependencies.

- step:
type: Run
identifier: dependencies
name: Dependencies
spec:
shell: Sh
command: |-
xcodebuild -resolvePackageDependencies

Cache dependencies

Add caching to your stage.

Use Cache Intelligence by adding caching to your stage.spec:

- stage:
spec:
caching:
enabled: true
paths:
- /Users/anka/Library/Developer/Xcode/DerivedData
sharedPaths:
- /Users/anka/Library/Developer/Xcode/DerivedData

Build and run tests

Add Run steps to run tests in Harness CI.

- step:
type: Run
name: Test
identifier: test
spec:
shell: Sh
command: |-
xcodebuild
xcodebuild test -scheme SampleApp

If you want to view test results in Harness, make sure your test commands produce reports in JUnit XML format and that your steps include the reports specification. The following example uses xcpretty to produce reports in JUnit XML format.

- step:
type: Run
name: Test
identifier: test
spec:
shell: Sh
command: |-
brew install xcpretty
- step:
type: Run
name: Test
identifier: test
spec:
shell: Sh
command: |-
xcodebuild
xcodebuild test -scheme SampleApp | xcpretty -r junit
reports:
type: JUnit
spec:
paths:
- "build/reports/junit.xml"
tip

You can use test splitting (parallelism) to improve test times.

Specify version

Xcode is pre-installed on Harness Cloud machines. For details about all available tools and versions, go to Platforms and image specifications.

Use xcode-select in a Run step to switch between pre-installed versions of Xcode.

- step:
type: Run
name: set_xcode_version
identifier: set_xcode_version
spec:
shell: Sh
command: |-
sudo xcode-select -switch /Applications/Xcode_15.1.0.app
xcodebuild -version

Deploy to the App Store

The following examples use Fastlane in a Continuous Integration setup to deploy an app to the Apple App Store. The environment variables in these examples use secrets and expressions to store and recall sensitive values, such as FASTLANE_PASSWORD=<+secrets.getValue('fastlanepassword')>.

To learn more about app distribution, go to the Apple Developer documentation on Distribution.

- step:
type: Run
name: Fastlane Build
identifier: Fastlane_Build
spec:
shell: Sh
command: |-
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

export APP_ID="osx.hello-harness"
export APP_STORE_CONNECT_KEY_ID="FW...CV3"
export APP_STORE_CONNECT_ISSUER_ID="80...e54"
export APP_STORE_CONNECT_KEY_FILEPATH="/tmp/build_certificate.p12"

export FASTLANE_USER=sample@mail.com
export FASTLANE_PASSWORD=<+secrets.getValue('fastlanepassword')>
export BUILD_CERTIFICATE_BASE64=<+secrets.getValue('BUILD_CERTIFICATE_BASE64')>
export BUILD_PROVISION_PROFILE_BASE64=<+secrets.getValue('BUILD_PROVISION_PROFILE_BASE64')>
export P12_PASSWORD=<+secrets.getValue('certpassword')>
export KEYCHAIN_PASSWORD=admin
export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=<+secrets.getValue('fastlaneapppassword')>
export FASTLANE_SESSION='-..._at: *1\n'
export APP_STORE_CONNECT_KEY_BASE64=<+secrets.getValue('appstoreapikey')>

sudo xcode-select -switch /Applications/Xcode_14.1.0.app
cd hello-harness

CERTIFICATE_PATH=/tmp/build_certificate.p12
PP_PATH=/tmp/profile.mobileprovision
KEYCHAIN_PATH=/tmp/app-signing.keychain-db
KEY_FILE_PATH="/tmp/app_store_connect_key.p8"

echo "$BUILD_CERTIFICATE_BASE64" >> ce
base64 -i ce --decode > $CERTIFICATE_PATH

echo "$BUILD_PROVISION_PROFILE_BASE64" >> prof
base64 -i prof --decode > $PP_PATH

echo "$APP_STORE_CONNECT_KEY_BASE64" >> key_base64
base64 -i key_base64 --decode > $KEY_FILE_PATH
export APP_STORE_CONNECT_KEY_FILEPATH="$KEY_FILE_PATH"

security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles

gem install bundler
bundle install

bundle exec fastlane beta
echo $ABC
envVariables:
ABC: samples
- step:
type: Run
name: Run_2
identifier: Run_2
spec:
shell: Sh
command: echo $ABC

Full pipeline examples

The following pipeline examples install dependencies, cache dependencies, and build and test an Xcode project.

This pipeline uses Harness Cloud build infrastructure and Cache Intelligence.

If you copy this example, replace the placeholder values with appropriate values for your code repo connector, repository name, and other applicable values. Depending on your project and organization, you may also need to replace projectIdentifier and orgIdentifier.

pipeline:
name: macostest
identifier: macostest
projectIdentifier: default
orgIdentifier: default
tags: {}
stages:
- stage:
name: build
identifier: build
description: ""
type: CI
spec:
cloneCodebase: true
caching:
enabled: true
paths:
- /Users/anka/Library/Developer/Xcode/DerivedData
sharedPaths:
- /Users/anka/Library/Developer/Xcode/DerivedData
platform:
os: MacOS
arch: Arm64
runtime:
type: Cloud
spec: {}
execution:
steps:
- step:
type: Run
identifier: dependencies
name: dependencies
spec:
shell: Sh
command: xcodebuild -resolvePackageDependencies
- step:
type: Run
name: Run xcode
identifier: Run_xcode
spec:
shell: Sh
command: |-
xcodebuild
xcodebuild test -scheme SampleApp
properties:
ci:
codebase:
connectorRef: YOUR_CODE_REPO_CONNECTOR_ID
repoName: YOUR_REPO_NAME
build: <+input>

Next steps

Now that you have created a pipeline that builds and tests an iOS/macOS app, you could: