It is a common scenario to use Azure DevOps to build, sign and release iOS applications. Most of the tasks related to that can be handled by the Xcode@5 task, which provides support for all kinds of build activities around Xcode workspaces, and which is a de-facto shortcut for invoking xcodebuild command line tool.
The task is quite well documented, but it is not entirely obvious how to use it for building Swift Packages, as those come without Xcode workspaces. In this post we will quickly explore how to achieve that.
Using Xcode@5 for building Swift Packages 🔗
The simplest solution for running Swift Package tests would be to just invoke swift test from the command line, though that would run the tests on the macOS build agent directly. Instead, it usually make sense to use an execution environment that is closer to what we are aiming to release our package for, such as the iOS simulator. Thankfully, as it turns out, xcodebuild can also easily run Swift Package tests.
One of the main issues with the Xcode@5 is that the setting xcWorkspacePath (so the workspace that should be used) is implicitly set to */.xcodeproj/project.xcworkspace. Since Swift Packages have no workspaces, simply omitting it will fall back to the default value and search for a workspace that might not be there in the case of our package, or, even worse, if we are building a bigger project consisting of different packages and apps, it might end up finding a workspace from a different application altogether. The trick here is to set the workspace path to empty, and change the working directory to the one of the Swift Package.
The other challenge is how to choose the destination to run the tests on correctly - this is somewhat convoluted and not documented well. For example in order to run the tests with xcodebuild on the iOS simulator for iPhone 15, with iOS 17.2, we should pass -destination platform=iOS Simulator,name=iPhone 15,OS=17.2 to the CLI. The way it is broken up in the Xcode@5 are the following properties:
- destinationPlatformOption - which should be iOS in our case (not iOS Simulator)
- destinationTypeOption - which we want to be simulators, though it’s also the default value so can be omitted
- destinationSimulators - which is where we need to pass our data like iPhone 15,OS=17.2 (notice the omission of =name)
The rest is pretty much straightforward - the action to choose should be test, the configuration should match the desired one (typically Release) and we have to choose the SDK and scheme for our package as well. If we want to export nicely formatted test results the options publishJUnitResults and useXcpretty allow for just that, and then the test results will be parsed by Azure DevOps and published into the build run’s “tests” tab as well.
The final configuration is shown below:
- task: Xcode@5
inputs:
actions: 'test'
configuration: 'Release'
sdk: 'iphoneos'
scheme: 'MySchemeName'
xcWorkspacePath: ''
workingDirectory: 'path/to/my/package'
useXcpretty: true
publishJUnitResults: true
destinationPlatformOption: 'iOS'
destinationTypeOption: 'simulators' #this is also the default and can be skipped
destinationSimulators: 'iPhone 15,OS=17.2'
testRunTitle: 'My Swift Package Tests on iOS Simulator'
displayName: Test Swift Package
Which resolves to the following command being executed on the build agent (the change of current working directory happens before that):
/usr/bin/xcodebuild -sdk iphoneos -configuration Release
-scheme MySchemeName -destination platform=iOS Simulator,name=iPhone 15,OS=17.2
test CODE_SIGNING_ALLOWED=NO | /usr/local/lib/ruby/gems/3.0.0/bin/xcpretty -r j
I hope that you will find this helpful and that it will save you some time!