- Sat 29 April 2023
- programming
- Gaige B. Paulsen
- #programming, #xcode, #macintosh, #automation, #ansible
As part of digging through my various problems with Xcode 14.3 (Feedback FB12154691, FB12154887, and some test case issues involving floating point math), I needed to install Xcode 14.2 to move my buildfarm backwards. Although this didn't enitrely fix the problem, it was an essential element of the debugging and remediation.
For manual work, there's a great list of Xcode releases available with direct links to Apple's downloads and release notes.
Since I was in the need to do this across five different machines, automation was on my mind, so I looked for the latest in tooling to help with this install process.
The latest in this field is xcodes
,
open source tooling for installing one or more copies of xcode and
switching between them.
xcodes
Commands
- Use
xcodes installed
to find out which versions are installed - Use
xcodes install XX.YY
to install a specific version - Use
xcodes select XX.YY
to make the specified version the default - Use
xcodes uninstall XX.YY
to uninstall a specific version
Minimizing traffic and logins
To retrieve the xcode installer from Apple, you need to be logged in to a developer account, and that means credentials. Coordinating that with automation is painful and also would result in pulling every installer I need (each time I need it) across the internet.
As I'll be automating installation on multiple systems, I decided that I'd cache the items that I need in order to save time and bandwidth.
To download the packages into your cache:
xcodes download --directory CACHE_DIR 13.2.1
Automating installation
For installation (via Ansible), I'm using the following
(assuming item.version
contains the version on input):
- name: copy xcode installer
copy:
src: "{{ xcode_cache }}/{{ item.package }}"
dest: "{{ root_home }}/Downloads/{{ item.package }}"
owner: "{{ owner }}"
group: "{{ group }}"
mode: '0644'
- name: "install xcode {{ item.version }}"
command:
cmd: "xcodes install --experimental-unxip --path {{ root_home }}/Downloads/{{ item.package }} {{ item.version }}"
become: true
- name: remove installer
file:
path: "{{ root_home }}/.Trash/{{ item.package }}"
state: absent
ignore_errors: true
This uses xcodes
to install without downloading, based on the xip
file
(along with enabling the experimental fast unxip code).
This code is called using include_tasks
from a loop in my main
ansible ci-bot file that installs appropriate versions:
- name: determine current xcodes
shell:
cmd: "xcodes installed | cut -f1 -d' '"
register: xcodes_installed
- name: install xcode if missing
include_tasks: ci-xcode.yml
loop: "{{ xcode_versions }}"
when:
- item.version not in xcodes_installed.stdout_lines
- ansible_distribution_version >= item.min_os
- item.max_os is not defined or (ansible_distribution_version < item.max_os )
The first task gets a list of current xcodes (to keep from reinstalling) and then installs only if it's not already installed and the version is appropriate for the version of macOS that we're installing on.
xcode_versions
looks like this:
xcode_versions:
- version: '13.2.1'
package: 'Xcode-13.2.1+13C100.xip'
min_os: '11.3.0'
max_os: '12.0.0'
- version: '14.3'
package: 'Xcode-14.3.0+14E222b.xip'
min_os: '13.0.0'
# intentionally out-of-order because 14.2 is preferred right now
- version: '14.2'
package: 'Xcode-14.2.0+14C18.xip'
min_os: '12.5.0'