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.
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
- Harness Cloud
- Self-managed
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.
To configure a self-managed macOS build infrastructure, go to Set up a macOS VM build infrastructure with Anka Registry or Set up a local runner build infrastructure.
This example uses a VM build infrastructure:
stages:
  - stage:
      name: build
      identifier: build
      description: ""
      type: CI
      spec:
        cloneCodebase: true
        infrastructure:
          type: VM
          spec:
            type: Pool
            spec:
              poolName: YOUR_VM_POOL_NAME
              os: MacOS
If you need to use Intel-based architecture and Rosetta is not already installed on your build infrastructure machines, you can use a Run step to install this dependency. 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.
- Harness Cloud
- Self-managed
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
Due to the long install time, make sure Xcode is pre-installed on your build infrastructure machines. If Homebrew is not already installed, use Run steps to install it and any other dependencies.
- step:
    type: Run
    identifier: dependencies_install_brew
    name: dependencies-install-brew
    spec:
      shell: Sh
      command: |-
        /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- 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.
- Cache Intelligence
- Save and Restore Cache steps
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
You can use built-in steps to:
Here's an example of a pipeline with Save Cache to S3 and Restore Cache from S3 steps. It also includes a Run step that creates the .ipa archive with xcodebuild archive and xcodebuild --exportArchive.
            steps:
              - step:
                  type: RestoreCacheS3
                  name: Restore Cache From S3
                  identifier: Restore_Cache_From_S3
                  spec:
                    connectorRef: AWS_Connector
                    region: us-east-1
                    bucket: your-s3-bucket
                    key: cache-{{ checksum "cache.ipa" }}
                    archiveFormat: Tar
              - step:
                  type: Run
                  ...
              - step:
                  type: Run
                  ...
              - step:
                  type: Run
                  identifier: create_cache
                  name: create cache
                  spec:
                    shell: Sh
                    command: |-
                      xcodebuild archive
                      xcodebuild -exportArchive
              - step:
                  type: SaveCacheS3
                  name: Save Cache to S3
                  identifier: Save_Cache_to_S3
                  spec:
                    connectorRef: AWS_Connector
                    region: us-east-1
                    bucket: your-s3-bucket
                    key: cache-{{ checksum "cache.ipa" }}
                    sourcePaths:
                      - "/Users/anka/Library/Developer/Xcode/DerivedData"
                    archiveFormat: Tar
Build and run tests
Add Run steps to run tests in Harness CI.
- Harness Cloud
- Self-managed
- 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"
- 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"
You can use test splitting (parallelism) to improve test times.
Specify version
- Harness Cloud
- Self-managed
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
If your build infrastructure machines have multiple versions of Xcode installed, you can use xcode-select in a Run step to switch versions.
- 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.
- Harness Cloud
- Self-managed
- 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
- 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="801...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='-...*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.
- Harness Cloud
- Self-managed
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>
This pipeline uses a self-managed VM build infrastructure and Save and Restore Cache from S3 steps.
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: macos-test-vm
  identifier: macostestvm
  projectIdentifier: default
  orgIdentifier: default
  tags: {}
  stages:
    - stage:
        name: build
        identifier: build
        description: ""
        type: CI
        spec:
          cloneCodebase: true
          execution:
            steps:
              - step:
                  type: RestoreCacheS3
                  name: Restore Cache From S3
                  identifier: Restore_Cache_From_S3
                  spec:
                    connectorRef: YOUR_AWS_CONNECTOR_ID
                    region: us-east-1 ## Use your S3 bucket's region.
                    bucket: YOUR_S3_BUCKET
                    key: cache-{{ checksum "cache.ipa" }}
                    archiveFormat: Tar
              - 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
              - step:
                  type: Run
                  identifier: create_cache
                  name: create cache
                  spec:
                    shell: Sh
                    command: |-
                      xcodebuild archive
                      xcodebuild -exportArchive
              - step:
                  type: SaveCacheS3
                  name: Save Cache to S3
                  identifier: Save_Cache_to_S3
                  spec:
                    connectorRef: YOUR_AWS_CONNECTOR_ID
                    region: us-east-1 ## Use your S3 bucket's region.
                    bucket: YOUR_S3_BUCKET
                    key: cache-{{ checksum "cache.ipa" }}
                    sourcePaths:
                      - /Users/anka/Library/Developer/Xcode/DerivedData
                    archiveFormat: Tar
          infrastructure:
            type: VM
            spec:
              type: Pool
              spec:
                poolName: YOUR_VM_POOL_NAME
                os: MacOS
  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:
- Create triggers to automatically run your pipeline.
- Add steps to build and upload artifacts.
- Add a step to build and push an image to a Docker registry.