Migrating An Xcode Command Line Tool Project to SPM
By Ravi Tripathi
Creating an executable Swift Package is a great way to quickly build a command line tool for folks familiar with the language. Antoine van der Lee has written a great article on getting started with one.
We moved Sonar, an internal command line tool which we use here at Gojek to a Swift Package from an Xcode Project. Moving away from an Xcode project presented some difficulties, requiring some workarounds which we would talk about in this post. But before jumping into that, let’s address a common query.
But why not stick to an Xcode Command Line Target?
Using SPM for building a command line tool instead of an Xcode Command Line target has some benefits:
- Since the Swift Toolchain is available on Windows and Linux, you can compile your Swift Package on these platforms without any Xcode-specific tools. Use
swift build
andswift test
to build and test a swift package respectively. - Adding test cases for Command Line Tool target in Xcode has some caveats, and often involves creating a separate dynamic framework for enclosing your core logic so that it can be unit tested independently. This is not a problem with Swift Packages, where a test target can be added for an executable.
A command line tool is usually a single compiled executable, and having an additional .framework
carrying the core logic makes it difficult to distribute. It also makes further optimizations difficult.
However, tools associated with Xcode are more mature when compared to SPM, which can make it difficult to migrate projects.
Moving to SPM
This was really straightforward. In a new directory, run:
swift package init --type executable <Your_Executable_Name>
This generates the Sources
and Tests
directory, a README.md
and a Package.swift
file. All we had to do now was move the project files into Sources
and the tests into Tests
directory respectively.
Opening the Package.swift
launches Xcode. After adding the dependencies in the Package.swift
file, we were good to go.
Coverage Reports for Swift Packages
While Xcode displays code coverage percentage for both SPM-based and .xcodeproj
projects within the IDE, many teams rely on tools like xcov to generate test coverage reports, and even fail CI/CD pipelines if the total coverage drops below a certain limit. This is where we hit our first wall.
Unlike Xcode Projects, no .xccoverage
files are generated while running tests for a Swift Package. Instead, SPM generates a .json
file containing the coverage data, which is not compatible with Xcov. This prevents us from checking the coverage percentage on CI jobs.
Thankfully, we stumbled upon swift-test-codecov, another command line swift package which can parse this json and generate a table containing coverage results. Coupling this with Mint allowed us to parse the coverage report on CI:
Exporting the final executable
This was the easy part. swift build --configuration release
builds the project and generates the executable inside the .build/release
directory. And that’s it! Copy the executable to /usr/local/bin
and you are good to go.
However, even on release configuration, the final executable size was well above 12 MB. Quite high for a command line utility. We began looking for ways to compress an executable.
UPX FTW 🚀
upx is powerful tool which can compress executables on Windows, macOS and Linux. This was perfect for our use case. Running upx brought down the final executable size from 13.7 MB
to 3.2 MB
, a 76% decrease!!
Ship it 📦
For installing locally, a shell script can encapsulate all the steps needed for building and moving the final executable to /usr/local/bin
:
Since we now had a single executable, we switched over to homebrew
for distributing Sonar. This helped us simplify our release mechanism drastically, eliminating the need for checking in a binary in multiple repositories.
SPM now makes it easy to manage and write tests for a command line tool written in Swift. And with tools like swift-test-codecov, upx, homebrew and Mint, you can generate coverage reports for your project, optimize the size of your executable, and provide an easy way to distribute it.
Find more stories from our vault, here.
Check out open job positions by clicking below: