Andrew Miotke
Posting about IT stuff. Sometimes programming, usually Python or Swift.
Getting started writing a CLI tool with Swift
Start project
- Start by making a new directory,
mkdir <path/to/dir>
- Run
swift package init --type executable
. This creates an executable package that can be opened in Xcode or any text editor
Prepare package.swift
To get your new CLI too ready to start building add the following to your package.swift file.
Add a
products
array the package constant. e.g.products: [ .executable(name: "cli_swift", targets: ["cli_swift"]) ],
If you’re adding any dependencies such as other Swift packages, add a
dependencies
array to the package constant e.g.dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", exact: "1.2.2") ],
Add a
dependencies
array to yourtargets
array. e.g..executableTarget( name: "cli_swift", dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")], path: "Sources" ),
Add the target platforms to the package constant. e.g.
// Sets the target platform to macOS 13+ platforms: [.macOS(.v13)],
Create a basic CLI tool with ArgumentParser
Next we’re going to create a simple CLI tool that takes in 1 required argument and 1 optional requrement using Apple’s ArugmentParser package. Note that this package was already configured in our package.swift file.
Open the project in Xcode and in the Sources directory rename or create a new file; don’t name it main.swift otherwise we won’t be able to use the @main
property wrapper. In this example we’ll name the file CliSwift.swift
CliSwift.swift
Import
Foundation
andArgumentParser
so that we get access to both libraries.Next we’ll create a struct named
CliSwift
and conform toAsyncParsableCommand
. This conformance is necessary for this simple example, however if you want to make a CLI tool that’s more useful, having Swift’s async/await functionality can be useful.Give the
CliSwift
struct the@main
property wrapper. This tells the compiler that this struct is the entry point of your app.Conformance to
AsyncParsableCommand
requires that we have add afunc run() async throws { }
method to our struct.Inside the struct we’ll add
@Argument(help: "")
with a variable ofname
which is a String. The@Argument
property wrapper takes a String, this String gives the user some information about this required argument.e.g. when running
cli_swift -h
ARGUMENTS: <name> Enter the your name
If we want to add an optional argument then we can use the
@Option(help: "")
property wrapper with a variable and it’s type. We will mark the variable as optional using?
lie so@Option(help: "Enter your age") var age: Int?
Inside the
run()
method, we can do any type of operation on those arguments by calling their variable name. See the full code sample below.import Foundation import ArgumentParser @main struct CliSwift: AsyncParsableCommand { @Argument(help: "Enter the your name") var name: String @Option(help: "Enter your age") var age: Int? func run() async throws { print("Your name is \(name)!!!") print("Your age is \(age ?? 0)") } }
Create arguments for testing.
You can run your code at any time but if you have required arguments, you will get an error and the help dialog will be displayed.
Error: Missing expected argument '<name>'
USAGE: cli-swift <name> [--age <age>]
ARGUMENTS:
<name> Enter the your name
OPTIONS:
--age <age> Enter your age
-h, --help Show help information.
Program ended with exit code: 64
To create arguments for testing click the target at the top of Xcode and select Edit Scheme…. Under Run click the + under Arguments Passed On Launch. These arguments need to be in order, top to bottom as they appear in your app with optional arguments above the optional argument value.
Close the Edit Scheme… window and try running your app again. The output should now have your argument values
Your name is gus!!! Your age is 13 Program ended with exit code: 0
Running your CLI tool from the command line.
- During development you can use
swift run cli_swift <argument value> --<optional argument name> <optional argument value>
to build and run on the command line. - You can build using
swift build
in the working directory of your source code. This will put an executable in a hidden .build/debug folder of your current path.
Archiving your build
Once you’re ready for a final release you can Archive the build using Xcode.
- In Xcode, select Product > Archive in the menu bar.
- Once built select Distribute Content and follow the prompts.
- Select Built Produdcts for your method of distribution and select where to save it on your Mac. It will create a new folder with the products name and date of the build. Click Export.
- You can find your new CLI executable binary in the Products/usr/local/bin/ path of your working directory
~/Desktop/cli_swift> tree . . ├── Package.resolved ├── Package.swift ├── Sources │ └── CliSwift.swift └── cli_swift 2023-08-03 15-14-54 └── Products └── usr └── local └── bin └── cli_swift