Creating a CLI executable in an artifact bundle
Posted Tuesday, September 5, 2023.Introduction
An SPM build plugin often needs an executable target to run its functionality. While you can co-locate the package plugin's source code with the executable code in the same package, this will cause an extra build step for all consumers as the executable needs to be built before it can be used in a plugin.
If your cli tool has no external dependencies, then the executable might be able to be part of the same package as the plugin and any consuming targets. However, I encountered a build problem when building an iOS project that was consuming a library that used a build plugin. When all the code (cli, plugin, library) were part of the same package, the build failed. Referencing the CLI as a binary target instead of a package that needed to be built fixed the problem (this might have been fixed since I ran into that problem).
We can mitigate these problems by taking our CLI and building it into an artifact bundle to be consumed by the package plugin as a binary target. In this post we will detail the steps to create an executable CLI in an artifact bundle.
We will be creating a CLI called MyCLI
. It will only have one command which will generate some swift code that will access the contents of some json files. This generated code and resources will go into the the consuming target's build plugin directory and shows how we can generate swift code and also include non-swift resources which the bundle can then access as normal (albeit read-only) code/resources.
The benefit of making this as a standalone CLI is that it is more modular and easier to test, and if applicable, it can be used elsewhere.
Package Structure
The finished file structure of this MyCLI
package will look as such (when you are starting out, MyCLI.artifactbundle
will not exist):
Package.swift
We will use the ArgumentParser
library to build our CLI. Package.swift
will contain the following:
CLI Command
Our MyCLI
command will takes 3 URLs as arguments:
- json file containing inputs to read
- output file to write the swift code to
- resources folder to write resource files to
In real usage, these URLs will come from the package plugin we will write in a future post
In our case, we are going to have the generated code be an extension on some type that will exist in the target that utilizes the package plugin. This is not a requirement.
A few things to note...
- The "keys" in this case would need to be values that are safe to be file names
- In real life we would have proper error handling and not all the force unwrapping!
- We use
Bundle.module
because these text files are going to be located in the generated bundle - This is a super contrived example
Creating an Artifact Bundle
We are only going to support macOS in this example. If you end up using this for iOS purposes or on mac only hardware, this will be enough because Xcode doesn't exist on Linux platforms. In a future post we can explore how to also generate for linux platforms.
An artifact bundle contains an info.json
file along with any built binaries. The example below shows a single CLI executable with support for x86_64
and arm64
macOS support.
Create a folder MyCLI.artifactbundle
, and a file in it info.json
with the contents of the above example.
In order to actually create the executable binary and move it into the archive, we can run the following commands
As you change/update this CLI you would probably update the version number/path/etc - this example just assumes
0.0.1
Now we have an executable that can be run on both x86_64 and arm64 macs! We can use this as a binary target in our SPM package plugin implementation as well.
In a future post we will explore how to use this artifact bundle in a package build plugin.
Tagged With: