Skip to main content

M1 stacks

Abstract

Bitrise offers the Apple silicon M1 stacks for all credit-based accounts. We recommend using the latest version of our Steps on the M1 stacks.

Apple announced the Apple silicon M1 chip, in 2020. This represents a fundamental change in hardware architecture and it means that in the future, all iOS and macOS development will take place on Macs with Apple silicon. As such, Bitrise is offering a virtualized M1 environment for CI/CD purposes.

Credit-based accounts only

The M1 stack isn't available to legacy, concurrency-based accounts.

Selecting an M1-based stack for your build

You have two options for selecting an M1-based stack, just like with any other stack:

  • Selecting it in the Workflow Editor.

  • Adding it as a meta property in the bitrise.yml file.

Setting an M1 stack in the Workflow Editor

  1. Open your app on Bitrise.

  2. Click the Workflows button on the main page.

    opening-workflow-editor.png
  3. Find the relevant stack selector menu:

    • If you want to set the default stack for all Workflows, find the Default stacks section.

    • If you want to set Workflow-specific stacks, find the Workflow Specific Stacks section and choose a Workflow.

  4. In the dropdown menu, select an Xcode stack with Xcode version 13 or higher.

  5. In the Machine type for the default stack section, select the M1 machine.

    infrastructure-m1-stack-machine-type-selector.png
  6. Click Save in the top right corner.

Setting an M1 stack in the bitrise.yml file

We'll go through how to set an M1 stack as the default stack for your app.

  1. Open your app's bitrise.yml file.

  2. Add a meta entry that will specify the stack ID and the machine type.

    You can find the stack IDs on the system reports page: the filenames without the .log extension are the stack IDs. To use an M1-based stack, you must select one of the stacks with Xcode version 13 or higher. The machine id is g2-m1.8core.

    You can set a default stack or Workflow-specific stacks:

    • For the default stack, add the meta property at the top level of the structure. It should be on the same level as, for example, format_version. We recommend putting it at the end of the file:

      meta:  
        bitrise.io:       
          stack: osx-xcode-13.4.x    
          machine_type_id: g2-m1.8core
      
    • If you want to set a Workflow-specific stack, you need to add the meta entry under the Workflow itself:

      workflows:
        deploy:  
          meta:      
            bitrise.io:       
              stack: osx-xcode-13.4.x
              machine_type_id: g2-m1.8core

      In this example, we're setting a Workflow-specific stack for the deploy Workflow.

Transitioning to M1 from Intel-based stacks

Switching your development environment from Intel-based architecture to Apple silicon is a major change and it affects CI/CD as well. At Bitrise, we've tried to make sure the transition is as seamless as possible: we've updated our Steps and we've installed M1-compatible versions of the most important tools on the M1 stacks.

This means that for the most part, if you use the official Bitrise Steps, your builds should work as they always did. Still, we strongly recommend testing everything locally, on your own M1 machines. You can use the Bitrise CLI to do so.

Bitrise Step versions

Always use the latest version of any Bitrise Step in your Workflow on the M1 stacks! They are designed to be backwards compatible with the Intel-based stacks, too.

There are also some limitations, and some changes to be aware of when it comes to transitioning from Intel-based stacks to M1. We'll go through the most important ones.

Using Rosetta

Rosetta is an emulator/translator designed by Apple to bridge the compatibilities between Intel and Apple silicon processors. It translates Intel-targeting executables so that they can run on M1-equipped machines.

Rosetta has been installed and enabled on our stacks. To use it, add the arch -x86_64 prefix in front of each command you run in your scripts. This tells the system to use x86_64 instructions.

Rosetta limitations

Rosetta can't translate:

  • Kernel extensions.

  • Virtual Machine apps that virtualize x86_64 computer platforms.

Xcode and MacOS version requirement

The most important requirement is that you need to use Xcode 13.0 or above. It is highly recommended to transition to Xcode 13.0 or higher anyway, as Apple no longer accepts apps submitted to the App Store if they were built with older Xcode versions.

As for the operating system, M1 requires MacOS Monterey. Big Sur isn't compatible with Apple silicon. As such, our M1 stacks run on Monterey.

Android SDK version requirement

All Xcode stacks include the Android SDK, too, as part of our support for cross-platform apps.

On M1 stacks, Android 32 is installed, as Google hasn't introduced M1 compatibility to earlier versions.

Changes to Homebrew's default path

Homebrew is the most popular package manager for macOS and it is of course available on Bitrise, on both Intel-based and M1 stacks.

There is one important difference: the default path for packages has changed:

  • On Intel-based Macs, the default path for packages is /usr/local/.

  • On M1-based Macs, the default paths for packages are /usr/local/bin/, /usr/local/share/, and /usr/local/lib/.

This is only important if your build uses hard-coded paths to access Homebrew packages. In that case you need to change the path to the M1 version.

Caching

Caching on Bitrise works on the basis of branches: running the same Workflow on a different branch of a repository creates a different cache archive.

The Cache:Pull Step makes sure that the downloadable cache was generated on the same stack and CPU architecture as the currently running build. If, for example, you are trying to run a build on an Intel-based stack and the Step only finds a cache archive that was generated on an M1-based stack, it won't download the cache. This is in order to avoid stability problems that comes from using cache that was generated on the other CPU architecture.

This means, in practice, that there is only ever one active cache on a given branch: it's either an M1-based or an Intel-based cache but never both. If you run the Cache:Push Step on a Workflow that has a cache archive from a different architecture, the Step will overwrite that cache archive.

Caching Step version requirements

The logic of differentiating between cache archives based on the CPU architecture doesn't work on older versions of the caching Steps:

  • The Cache:Pull Step must be version 2.6.0 or higher. Older versions will download any cache archive they find, regardless of CPU architecture.

  • The Cache:Push Step must be version 2.7.0 or higher. Older versions won't add metadata related to the CPU architecture.

Other known issues and limitations

M1 has some other limitations and known issues. We're working hard at making sure we have a working solution for most use cases.

Android emulation is unavailable

The M1 stacks don't support using an Android emulator in your builds. This is because Bitrise runs your builds on virtual machines and the Apple silicon architecture doesn't allow nested virtualization.

You can, however, still use Firebase Test Lab to run Android device tests on Bitrise: Device testing for Android.

Hanging builds issue

There is a bug in Apple's hypervisor framework that can cause issues in virtualized environments like ours. The issue can occur whenever a simulator is launched: for example, our Xcode Test for iOS Step uses an iOS simulator to run tests. Once the simulator starts, your build can hang indefinitely. This doesn't happen every time a simulator is launched so the majority of your builds will be unaffected but the issue can and does happen.

The issue has been reported to Apple and a fix coming out with macOS Ventura is being tested. In the meantime, we recommend using a workaround.

Detecting and aborting hanging Steps without output

If you are experiencing hanging Steps other than the Start Xcode simulator Step, check out our guide on Detecting and aborting hanging Steps without output.

The workaround is simple: you launch an Xcode simulator at the start of the build, check whether it hangs and if it does, restart the Workflow so that the hanging issue doesn't waste your time and credits.

  1. Add the Start Xcode simulator Step as the very first Step Workflow.

    It's important to launch the simulator before anything else, even before cloning the repository. If the issue doesn't occur, booting up the simulator should only take a few seconds and you can use it later in the Workflow to run your tests.

  2. Add a 90-second boot timeout to the Step. You can do it either in the Workflow Editor or in the bitrise.yml file:

    Destination device

    Make sure to also set the simulator destination in the Device destination specifier input of the Step. In the bitrise.yml file, the input's name is destination.

    You can find the available destination devices in Xcode.

    • On the graphical UI of the Workflow Editor: find the Simulator boot timeout (in seconds) input and set it to 90.

    • In the bitrise.yml, you need to set the wait_for_boot_timeout input to 90:

      - xcode-start-simulator:
          inputs:
          - destination: platform=iOS Simulator,name=iPhone 8,OS=latest
          - wait_for_boot_timeout: 90
  3. Add the Trigger Bitrise workflow Step to your Workflow right after the Start Xcode simulator Step.

    We will use this Step to detect whether the previous Step hung and if it did, to restart your build.

  4. Set the Step to run even if a previous Step failed. You can do it either in the Workflow Editor or in the bitrise.yml file

    • In the graphical UI of the Workflow Editor: find the Run if previous Step failed toggle and switch it on.

    • In the bitrise.yml file: set the is_always_run attribute to true.

      - trigger-bitrise-workflow:
          is_always_run: true
  5. Set the run_if attribute: the Step should only run if the Start Xcode simulator ended up with a hanged status.

    The Start Xcode simulator Step exports an Env Var that stores the status of the simulator. This is what we'll use to detect whether the build hung.

    YAML mode only!

    You can only do this in the bitrise.yml file of your app.

    - trigger-bitrise-workflow:
        is_always_run: true
        run_if: '{{enveq "BITRISE_SIMULATOR_STATUS" "hanged"}}'
  6. Configure the API token and the Workflow ID:

    • Build Trigger API token: You can find it on the Code tab of your app's page on Bitrise.

    • Workflow ID: The name of the Workflow that you want to trigger. In this case, it should be the same Workflow.

    In the bitrise.yml file, the Step should look something like this:

    - trigger-bitrise-workflow:
        is_always_run: true
        run_if: '{{enveq "BITRISE_SIMULATOR_STATUS" "hanged"}}'
        inputs:
        - api_token: $TRIGGER_TOKEN
        - workflow_id: my-workflow-name
Example 1. Full YAML example
- xcode-start-simulator:
    inputs:
    - destination: platform=iOS Simulator,name=iPhone 8,OS=latest
    - wait_for_boot_timeout: 90
- trigger-bitrise-workflow:
    is_always_run: true
    run_if: '{{enveq "BITRISE_SIMULATOR_STATUS" "hanged"}}'
    inputs:
    - api_token: $TRIGGER_TOKEN
    - workflow_id: my-workflow-name

That's it. If your build hangs at the simulator stage, it will automatically restart itself within 90 seconds, ensuring that the issue doesn't end up wasting a huge amount of time.