Guest Blog Post By Peter Wiesner, Senior Software Engineer @Skyscanner
Every year Apple releases a new version of Xcode. CI systems for iOS application development need to adopt it, so developers can take advantage of the new iOS features. CI systems using Anka Build have a head-start here. For folks not familiar with Anka Build, read more details here.
You only need to create a new tag for the current Anka macOS virtual machine with the new Xcode installed. Anka ships with features helping to automate this process.
In this blog post, I will show you how to do this. We will use the following solutions:
- anka create to create the macOS virtual machine from a script
- Jenkins pipeline to run the jobs on demand
- anka run to execute batch commands on the virtual machines
About the prerequisites:
- Jenkins with pipeline plugin installed
- a mac with anka-create label connected to Jenkins. We will use this native node to create and provision the virtual machine
- Anka Build package version 1.4 current release installed on the mac
Jenkins pipeline
Let’s create a Jenkins pipeline project with following script. The key tricks are:
- use Jenkins parameters to provide configuration (Xcode version to install)
- use a separate repo for the actual provisioning files
- always save the state of the VM on pipeline failure so next iteration is faster
pipeline {
agent none
options {
timeout(time: 240, unit: 'MINUTES')
}
environment {
// Provide credentials here (anka username/pwd, keychain passwords,
credentials
for tool packages etc.)
}
parameters {
//Provide mutable configuration for scripts (anka VM name, anka
tag,
Xcode version)
string(name: 'ANKA_VM_NAME', defaultValue: 'anka_VM_node',
description: 'Name of the Anka VM')
//...
}
stages {
stage("anka-create") {
agent {
node {
// There is a native mac connected to Jenkins with this
label
label 'anka-create'
}
}
steps {
create_task()
}
post {
always {
// Create a vm that stores the state of the VM on exiting. When fixing
failures,
this can speed up the process a lot
sh ""
"#!/bin/bash
if [\$(anka list | grep current_state_vm | wc - l) == 1]
then
anka delete--yes current_state_vm
fi
anka stop\ $ANKA_VM_NAME
anka clone - c\ $ANKA_VM_NAME current_state_vm
if [\$(anka list | grep\ $ANKA_VM_NAME | wc - l) == 1]
then
echo "Deleting Anka VM: \$ANKA_VM_NAME"
anka delete--yes\ $ANKA_VM_NAME
fi
""
"
cleanWs()
}
}
}
}
}
def checkout_provisioning_scripts() {
// It is useful to decouple the actual provisioning script from this
pipeline script
// Easy to do updates and helps reading the pipeline script
// Clone the provisioning script from a GitHub repo
timeout(15) {
checkout([$class: 'GitSCM', branches: [
[name: 'master']
], ,
extensions: [
[$class: 'CloneOption', depth: 1, noTags: false,
shallow: true, timeout: 30
]
],
userRemoteConfigs: [
[name: 'origin', refspec:
'+refs/heads/*:refs/remotes/origin/*', url: 'SSH_GIT_URL'
]
]
])
}
}
def create_task() {
cleanWs()
credentials.withAllCredentials {
checkout_provisioning_scripts()
// Once we cloned the repository, print the environment variables
to see
if job parameters and variables are correctly passed to this task
// provision.sh comes from the repo
sh ""
"#!/bin/bash
printenv
sh. / provision.sh
""
"
}
}
Creating the VM
The Jenkins pipeline calls out to a provision script. This is responsible to utilize anka for provisioning. The key tricks are:
- collecting all necessary packages beforehand
- allowing the script to use previously saved virtual machine as starting point (`anka create` can take up to 20 mins)
#!/bin/bash # Let's fail on error set -e # Retrieve macOS installer and necessary packages # It's beneficial to store them on a NAS that can be connected to the mac node # We can start the process with previous state of vm if we would like to # This saves a lot of time if [ "$START_FROM_BEGINNING" -eq 0 ] then #Create VM from the Installer on NAS ANKA_DEFAULT_USER=$USER_NAME anka --debug create --ram-size $VM_RAM --cpu-count $VM_CPU --disk-size $VM_DISK --app "$PATH_TO_OS_INSTALLER" $ANKA_VM_NAME anka modify $ANKA_VM_NAME add port-forwarding --host-port 0 --guest-port 22 --protocol tcp ssh else #Clone VM anka clone current_state_vm $ANKA_VM_NAME fi # Iterate on the setup/install scripts for file in $files_to_execute do # Run script as root anka run --env $ANKA_VM_NAME sudo -E bash $file # Run script as created anka user #anka run --env $ANKA_VM_NAME sudo -E -H -u $USER_NAME bash $file done # Stop the VM after we are done, start it up and suspend it, so CI can use the super fast start-up feature of Anka VMs. anka stop -f $ANKA_VM_NAME anka run $ANKA_VM_NAME ls anka suspend $ANKA_VM_NAME # Save the new baseline virtual machine anka registry -a $REGISTRY_IP_ADDRESS push $ANKA_VM_NAME -t $ANKA_TAG
Installing necessary tools
The actual provisioning happens in the `for loop`, where we use `anka run`. These scripts install the tools like Xcode, Xcode CLI, Ruby, git or any packages. The content and order of the scripts are company/case specific, but let me list some general tricks. Keep in mind, when running the `anka run` command, the current working directory will be mounted to the VM and the files can be accessed with a relative path (sweet!).
Installing certificates
$P12_PATH contains the path to the exported p12 files location.
echo $USER_PWD | sudo -S security import $P12_PATH -k "/Library/Keychains/System.keychain" -P "$P12_PASSWORD" -A
Installing Xcode CLI
$XCODE_CLI_PATH contains the path to the Xcode Command Line Tools package location.
MOUNTDIR=$(echo `hdiutil mount $XCODE_CLI_PATH | tail -1 | awk '{$1=$2="";
print $0}'` | xargs -0 echo)
echo $USER_PWD | sudo -S installer -pkg "${MOUNTDIR}/"*.pkg -target /
hdiutil unmount "${MOUNTDIR}"Installing Xcode
You need to install the xcode-install gem for this. $XCODE_APP_VERSION contains the version number of the Xcode you want to install (like 10.0) $XCODE_REMOTE_URL is an URL from where the Xcode xip can be downloaded (it is worth to download it and upload to a private remote server to have bigger download speed).
xcversion install $XCODE_APP_VERSION --url="$XCODE_REMOTE_URL" --verbose








