Swift on ARM - Supporting ARM in Swift Package CI

In a previous post, I already talked about how to setup your Swift package for CI success. However, I recently came across how to run your CI in Travis and GitHub Actions to test on Linux ARM machines. In this article, I'm going to go over how to setup your Linux CI for ARM architecture as well as how to install Swift on those ARM machines.

ARM in Travis-CI

Currently on Travis-CI, different CPU architectures are supported in an alpha stage. However it is fairly easy to setup. Within your

travis-ci.yml file, simply specify your CPU architecture. For instance, if you'd like to test on both with a build matrix, you can start your file with:

arch:
  - amd64
  - arm64
os: linux  # different CPU architectures are only supported on Linux

As you can see we are using the default version of Linux supported by Travis-CI. If you wish to expand your matrix, and specify different versions of Ubuntu, you can by:

jobs:
  include:
    - os: linux
      dist: bionic
      arch: amd64
    - os: linux
      dist: focal
      arch: amd64
    - os: linux
      dist: bionic
      arch: arm64
    - os: linux
      dist: focal
      arch: arm64

This will allow us to test on four VMs each using Ubuntu 18.04 and Ubuntu 20.04 while testing both x86/AMD64 and 64-bit ARM.

ARM in GitHub Actions

Likewise, in GitHub Actions, you can do this, however you'll need to use QEMU to emulate the ARM architecture. Luckily there's a GitHub Action to simplify this. Using this run-on-arch-action, we then add a script to install Swift and run our test.

For instance, I will setup a separate workflow file for ARM arm.yml. Inside I setup a build matrix for each version of Ubuntu as well as for ARM. As we'll see there are separate procedures for installing Swift on ARM, which we'll need to follow. Therefore, it's best to keep a separate workflow file for arm as opposed to standard x86.

Inside the workflow file arm.yml, I start off with:

name: arm

on: [push]

jobs:
  build:
    env:
      PACKAGE_NAME: {{ Insert Package Name Here }}
      SWIFT_VER: 5.2.4

    strategy:
      matrix:
        architecture:  [aarch64]
        distribution: [ubuntu16.04,ubuntu18.04,ubuntu20.04]
    steps:
    - uses: actions/checkout@v2
    - name: Build with Swift on arm
      uses: uraimo/run-on-arch-action@master
      env:
         {{ any environment keys here }}
      with:
        architecture: ${{ matrix.architecture }}
        distribution: ${{ matrix.distribution }}
        run: |
...

Now we have the beginning of the setup for Travis-CI and GitHub for ARM on Linux. However, the challenge will be installing Swift.

Preparing Swift on ARM

Before running your test and building your Swift package, you need make sure your CI instance is ready. This includes installing prerequisites and installing Swift. There are a few differences in preparing your instance on GitHub and Travis-CI due to how they implement the different CPU architectures. However I will do my best to note those differences below.

Installing Prerequisites on GitHub

Since Travis-CI uses LXD and the GitHub Action uses QEMU, it seems GitHub requires more prerequisites to install Swift.

With Travis-CI, we can simply follow guidelines of what required for various Linux distributions. However GitHub needs a few other requirements:

Therefore before installing our prerequisites we need to do this:

# tell the installation this is non-interactive
          export DEBIAN_FRONTEND=noninteractive
# set your time zone here
          ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime
# install sudo, curl, lab-release, and clang
          apt update
          apt install -y curl lsb-release sudo clang
# grab the release name, number in dot (18.04) and non-dot (1804 format
          RELEASE_DOT=$(lsb_release -sr)
          RELEASE_NUM=${RELEASE_DOT//[-._]/}
          RELEASE_NAME=$(lsb_release -sc)

Installing Swift Prerequisites

Following the guidelines provided by the Swift team, we can simply check the Ubuntu version and install our prerequisites. Here's a snippet which checks for Ubuntu Focal Fossa 20.04, and installs the required packages:

           if [[ $RELEASE_DOT == "20.04" ]]; then
            sudo apt-get -y install \
                    binutils \
                    git \
                    gnupg2 \
                    libc6-dev \
                    libcurl4 \
                    libedit2 \
                    libgcc-9-dev \
                    libpython2.7 \
                    libsqlite3-0 \
                    libstdc++-9-dev \
                    libxml2 \
                    libz3-dev \
                    pkg-config \
                    tzdata \
                    zlib1g-dev
            elif [[ $RELEASE_DOT == "16.04" ]]; then
...

On GitHub, after installing tzdata make sure run:

dpkg-reconfigure --frontend noninteractive tzdata

Now we can move forward with actually installing Swift.

Installing Swift on ARM

Installing Swift on ARM will simply need adding the repository and installing via apt:

curl -s https://packagecloud.io/install/repositories/swift-arm/release/script.deb.sh | sudo bash
apt-get install -y swift5

While typically, you may need to add the PATH, since we are using

apt-get install swift is already added to our path.

If you are using a single script on Travis-CI for both amd64 and arm64, you can use some logic to differentiate the installation process using the TRAVIS_CPU_ARCH variable:

   if [[ $TRAVIS_CPU_ARCH == "arm64" ]]; then
    curl -s https://packagecloud.io/install/repositories/swift-arm/release/script.deb.sh | sudo bash
    sudo apt-get install swift5
  else 
     wget https://swift.org/builds/swift-${SWIFT_VER}-release/ubuntu${RELEASE_NUM}/swift-${SWIFT_VER}-RELEASE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz
     tar xzf swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz
  fi 

For this point, you can run swift build or swift test easily in your CI's build process.

Getting Ready for Swift on ARM

With the increased use of ARM on a variety of devices, it's important to make sure your Swift package works everywhere. For instance, you may want to build an IOT device and make sure your package works on a Raspberry PI. If that's the case, this is great way to get started. If you'd like more guidance, feel free to look at my current example on EggSeed and other Swift Packages. Once this is implemented on your CI setup, you can be confident your Swift package is ARM-ready.