Skip to main content
  1. Posts/

Swift Command Line Application - Joy's Soul Lies In the Doing

·4 mins

I am getting deeper into Swift the programming language. I am not into iOS or Apple Development Ecosystem. I am super intrigued about Swift’s Server Side Support. I am beginning to feel that Swift is a well-rounded language for backend development.

So, I bit the bullet, and started exploring how Swift applications are structured beginning with command line applications. While Swift’s website has a great starter template for starting out with a command line application, I felt it lacked facets that I lean towards when starting out.

I am not a huge fan of the starter template’s structure. It assumes that you will be writing your code in one module. I usually structure the command line applications like so:

  • Main Module The Entry-point module. This module contains the Tool-like aspect of the program, like gather user input, configuration etc.
  • Core Module This module is responsible of housing the core api of the applications, and last but not the least -
  • Tests I usually write tests for the code module.

Let’s begin with the starter template and modify it the way I like to structure the code. We will create a SampleTool application. SampleTool is the entry point module, SampleCore is going to be the API Core, and SampleCoreTests is for testing the SampleCore module.

We begin by generating the code using Swift Package Manager:

mkdir SampleTool
cd SampleTool
swift package init --name SampleTool --type executable
Creating executable package: SampleTool
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/main.swift

This will generate the following structure:

├── Package.swift
└── Sources
    └── main.swift

2 directories, 2 files

The Package.swift file contains the following text:

import PackageDescription

let package = Package(
    name: "SampleTool",
    targets: [
        .executableTarget(
            name: "SampleTool"),
    ]
)

While this works, I noticed the swift command line options also supports adding a tool type package. The tool package type includes the ArgumentParser dependency, an important support package that makes it easier to add support for options and flags.

swift package init --name SampleTool --type tool
Creating tool package: SampleTool
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/SampleTool.swift

If we look at the contents of Package.swift now, it will include ArgumentParser dependency.

import PackageDescription

let package = Package(
    name: "SampleTool",
    dependencies: [
        .package(
          url: "https://github.com/apple/swift-argument-parser.git", 
          from: "1.2.0"),
    ],
    targets: [
        .executableTarget(
            name: "SampleTool",
            dependencies: [
                .product(name: "ArgumentParser", 
                 package: "swift-argument-parser"),
            ]
        ),
    ]
)

Now, lets add a target for our SampleCore module, this module will house the core api.

swift package add-target SampleCore --type library
swift package add-target-dependency SampleCore SampleTool

If we look at the structure of our project it looks like so:

.
├── Package.swift
└── Sources
    ├── SampleCore
    │   └── SampleCore.swift
    └── SampleTool.swift

3 directories, 3 files

And, our Package.swift looks like this:

import PackageDescription

let package = Package(
    name: "SampleTool",
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
    ],
    targets: [
        .executableTarget(
            name: "SampleTool",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
                .target(name: "SampleCore"),
            ]
        ),
        .target(name: "SampleCore"),
    ]
)

Lastly, lets add the test target:

swift package add-target SampleCoreTests --type test
swift package add-target-dependency SampleCore SampleCoreTests

With these changes, our Package.swift looks like this:

import PackageDescription

let package = Package(
    name: "SampleTool",
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
    ],
    targets: [
        .executableTarget(
            name: "SampleTool",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
                .target(name: "SampleCore"),
            ]
        ),
        .target(name: "SampleCore"),
        .testTarget(name: "SampleCoreTests",dependencies: [
    .target(name: "SampleCore"),]),
    ]
)

When I run the build command, I get this error:

error: 'sampletool': Source files for target SampleTool should be located under 'Sources/SampleTool', 
or a custom sources path can be set with the 'path' property in Package.swift

We get this error message because SampleTool.swift should be in its own module directory. So we move the file to its own directory, and finally our structure looks like this:

.
├── Package.resolved
├── Package.swift
├── Sources
│   ├── SampleCore
│   │   └── SampleCore.swift
│   └── SampleTool
│       └── SampleTool.swift
└── Tests
    └── SampleCoreTests
        └── SampleCoreTests.swift

6 directories, 5 files

And, that’s it! Now, that I have setup the project the way I like, I can start developing.

Swift Package Manager is a wonderful tool that takes a getting used to. If you are like me, you might get confused between module, target, and product, here’s its proposal