> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/continuous-integration/3.0/troubleshooting-and-resources/tutorials-and-code-samples/mobile/ios.md).

# iOS/macOS

You can build and test [iOS](https://developer.apple.com/ios/) and [macOS](https://developer.apple.com/macos/) applications using a macOS platform on [Harness Cloud](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/use-harness-cloud-build-infrastructure.md), a [self-managed macOS VM](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/vm-build-infrastructure/define-macos-build-infra-with-anka-registry.md), or a [local runner](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/define-a-docker-build-infrastructure.md) build infrastructure.

{% hint style="info" %}
Harness recommends running macOS/iOS builds on Harness Cloud.
{% endhint %}

The examples in this guide use [Xcode](https://developer.apple.com/xcode/). You can also use [Fastlane](https://docs.fastlane.tools/) to build and test your iOS and macOS apps.

This guide assumes you've [created a Harness CI pipeline](/continuous-integration/3.0/use-harness-ci/use-harness-ci/prep-ci-pipeline-components.md).

### Specify architecture <a href="#specify-architecture" id="specify-architecture"></a>

{% tabs %}
{% tab title="Harness Cloud" %}
To use M1 machines with Harness Cloud, use the `Arm64` architecture.

```yaml
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](https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment) 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.
{% endtab %}

{% tab title="Self-managed" %}
To configure a self-managed macOS build infrastructure, go to [Set up a macOS VM build infrastructure with Anka Registry](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/vm-build-infrastructure/define-macos-build-infra-with-anka-registry.md) or [Set up a local runner build infrastructure](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/define-a-docker-build-infrastructure.md).

This example uses a VM build infrastructure:

```yaml
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](https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment) is not already installed on your build infrastructure machines, you can use a **Run** step to [install this dependency](#install-dependencies). Keep in mind that running apps through Rosetta can impact performance. Use native Apple Silicon apps whenever possible to ensure optimal performance.
{% endtab %}
{% endtabs %}

### Install dependencies <a href="#install-dependencies" id="install-dependencies"></a>

Use [Run steps](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-step-settings.md) to install dependencies in the build environment.

{% tabs %}
{% tab title="Harness Cloud" %}
[Homebrew](https://brew.sh/) and [Xcode](https://developer.apple.com/xcode/) are already installed on Harness Cloud macOS machines. For more information about preinstalled tools and libraries, go to the [Harness Cloud image specifications](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/use-harness-cloud-build-infrastructure.md#platforms-and-image-specifications).

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

You can [add package dependencies](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) in your Xcode project and then run Xcode commands in **Run** steps to interact with your project's dependencies.

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

{% endtab %}

{% tab title="Self-managed" %}
Due to the long install time, make sure [Xcode](https://developer.apple.com/xcode/) is pre-installed on your build infrastructure machines. If [Homebrew](https://brew.sh/) is not already installed, use **Run** steps to install it and any other dependencies.

```yaml
- 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](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) in your Xcode project and then run Xcode commands in **Run** steps to interact with your project's dependencies.

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

{% endtab %}
{% endtabs %}

### Cache dependencies <a href="#cache-dependencies" id="cache-dependencies"></a>

Add caching to your stage.

{% tabs %}
{% tab title="Cache Intelligence" %}
Use [Cache Intelligence](/continuous-integration/3.0/use-harness-ci/use-harness-ci/caching-ci-data/cache-intelligence.md) by adding `caching` to your `stage.spec`:

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

{% endtab %}

{% tab title="Save and Restore Cache steps" %}
You can use built-in steps to:

* [Save and Restore Cache from S3](/continuous-integration/3.0/use-harness-ci/use-harness-ci/caching-ci-data/saving-cache.md)
* [Save and Restore Cache from GCS](/continuous-integration/3.0/use-harness-ci/use-harness-ci/caching-ci-data/save-cache-in-gcs.md)

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

```yaml
            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
```

{% endtab %}
{% endtabs %}

### Build and run tests <a href="#build-and-run-tests" id="build-and-run-tests"></a>

Add [Run steps](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-step-settings.md) to [run tests in Harness CI](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-tests/run-tests-in-ci.md).

{% tabs %}
{% tab title="Harness Cloud" %}

```yaml
- 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](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-tests/viewing-tests.md), make sure your test commands produce reports in JUnit XML format and that your steps include the `reports` specification. The following example uses [xcpretty](https://github.com/xcpretty/xcpretty) to produce reports in JUnit XML format.

```yaml
- 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"
```

{% endtab %}

{% tab title="Self-managed" %}

```yaml
- 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](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-tests/viewing-tests.md), make sure your test commands produce reports in JUnit XML format and that your steps include the `reports` specification. The following example uses [xcpretty](https://github.com/xcpretty/xcpretty) to produce reports in JUnit XML format.

```yaml
- 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"
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
You can use [test splitting (parallelism)](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-tests/speed-up-ci-test-pipelines-using-parallelism.md) to improve test times.
{% endhint %}

### Specify version <a href="#specify-version" id="specify-version"></a>

{% tabs %}
{% tab title="Harness Cloud" %}
Xcode is pre-installed on Harness Cloud machines. For details about all available tools and versions, go to [Platforms and image specifications](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/use-harness-cloud-build-infrastructure.md#platforms-and-image-specifications).

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

```yaml
- 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
```

{% endtab %}

{% tab title="Self-managed" %}
If your build infrastructure machines have multiple versions of Xcode installed, you can use `xcode-select` in a **Run** step to switch versions.

```yaml
- 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
```

{% endtab %}
{% endtabs %}

### Deploy to the App Store <a href="#deploy-to-the-app-store" id="deploy-to-the-app-store"></a>

The following examples use [Fastlane in a Continuous Integration setup](https://docs.fastlane.tools/best-practices/continuous-integration/) to deploy an app to the Apple App Store. The environment variables in these examples use [secrets](/harness-ai/use-harness-platform/secrets.md) and [expressions](/harness-ai/use-harness-platform/variables-and-expressions/harness-variables.md) 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](https://developer.apple.com/documentation/xcode/distribution).

{% tabs %}
{% tab title="Harness Cloud" %}

```yaml
- 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
```

{% endtab %}

{% tab title="Self-managed" %}

```yaml
- 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
```

{% endtab %}
{% endtabs %}

### Full pipeline examples <a href="#full-pipeline-examples" id="full-pipeline-examples"></a>

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

{% tabs %}
{% tab title="Harness Cloud" %}
This pipeline uses [Harness Cloud build infrastructure](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/use-harness-cloud-build-infrastructure.md) and [Cache Intelligence](/continuous-integration/3.0/use-harness-ci/use-harness-ci/caching-ci-data/cache-intelligence.md).

If you copy this example, replace the placeholder values with appropriate values for your [code repo connector](/continuous-integration/3.0/use-harness-ci/use-harness-ci/codebase-configuration/create-and-configure-a-codebase.md#code-repo-connectors), repository name, and other applicable values. Depending on your project and organization, you may also need to replace `projectIdentifier` and `orgIdentifier`.

```yaml
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>
```

{% endtab %}

{% tab title="Self-managed" %}
This pipeline uses a [self-managed VM build infrastructure](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/vm-build-infrastructure/define-macos-build-infra-with-anka-registry.md) and [Save and Restore Cache from S3 steps](/continuous-integration/3.0/use-harness-ci/use-harness-ci/caching-ci-data/saving-cache.md).

If you copy this example, replace the placeholder values with appropriate values for your [code repo connector](/continuous-integration/3.0/use-harness-ci/use-harness-ci/codebase-configuration/create-and-configure-a-codebase.md#code-repo-connectors), repository name, and other applicable values. Depending on your project and organization, you may also need to replace `projectIdentifier` and `orgIdentifier`.

```yaml
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>
```

{% endtab %}
{% endtabs %}

### Next steps <a href="#next-steps" id="next-steps"></a>

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

* Create [triggers](/harness-ai/use-harness-platform/triggers.md) to automatically run your pipeline.
* Add steps to [build and upload artifacts](/continuous-integration/3.0/use-harness-ci/use-harness-ci/build-and-upload-artifacts.md).
* Add a step to [build and push an image to a Docker registry](/continuous-integration/3.0/use-harness-ci/use-harness-ci/build-and-upload-artifacts/build-and-push/build-and-push-to-docker-registry.md).
