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








