🐕

(Swift)CLIツールの作り方

2024/08/25に公開

はじめに

swiftコマンドを利用してCLIツールを作る方法を説明します。操作はすべてターミナルで行います。

環境

以下の環境で動作確認を行いました。通常はXcodeをインストールするとswiftコマンドが同時にインストールされます。

  • macOS
    • Monterey 12.7.6
    • Sonoma 14.5
  • Xcode
    • 14.2 (14C18)
    • 15.4 (15F31d)
  • Apple Swift
    • 5.7.2
    • 5.10

Step 1. プロジェクトの初期化

例としてmytoolコマンドを作ることにします。ターミナルを開き、次のコマンドを実行してください。

$ mkdir mytool
$ cd mytool
$ swift package init --type executable --name mytool

完了すると、以下のようなメッセージが表示されます。

$ swift package init --type executable --name mytool
Creating executable package: mytool
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/main.swift

swiftコマンドのバージョンによって作成されるファイル名やディレクトリの構造が少々変化するかもしれません。

Step 2. ビルド

プロジェクトの初期化が完了した時点でビルドが可能な状態になっています。次のコマンドを実行してください。

$ swift build -c release

完了すると、以下のようなメッセージが表示されます。

$ swift build -c release
Building for production...
[5/5] Linking mytool
Build complete! (7.34s)

Step 3. 実行

ビルドが成功すると、実行可能ファイルは.build/releaseディレクトリに作成されます。試してみましょう。

$ ./.build/release/mytool
Hello, World!

Step 4. コードの編集

SwiftコードはSourcesディレクトリに配置します。なお、以下の点に注意してください。

Swift 5.7の注意点

Swift 5.10であれば解消されているため不要な場合は読み飛ばしてください。

注意点として、ファイルの名前がmain.swiftの場合はエントリーポイントとして扱われます。従って、main.swiftコードに@main属性が設定されているとビルドが失敗します。

まとめると、エントリーポイントを指示する方法として以下のパターンが可能です。

mytool.swift(main.swiftではない任意のSwiftファイル)に@main属性を設定するパターン

@main
struct MyTool {
    static func main() {
        print("Hello, world!")
    }
}

main.swiftを利用するパターン

print("Hello, world!")

Step 5. パッケージの追加

コマンドライン引数を処理するため、Appleが公式に提供しているArgumentParserパッケージを追加することにします。プロジェクトのルートにあるPackage.swiftを次のように変更してください。

// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
  name: "mytool",
  platforms: [
    .macOS(.v12)
  ],
  dependencies: [
    .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0")
  ],
  targets: [
    // Targets are the basic building blocks of a package. A target can define a module or a test suite.
    // Targets can depend on other targets in this package, and on products in packages this package depends on.
    .executableTarget(
      name: "mytool",
      dependencies: [
        .product(name: "ArgumentParser", package: "swift-argument-parser")
      ]),
    .testTarget(
      name: "mytoolTests",
      dependencies: ["mytool"]),
  ]
)

続いて、Sourcesディレクトリ内のSwiftコードを次のように変更します。

import ArgumentParser

@main
struct MyTool: ParsableCommand {
  static var configuration = CommandConfiguration(
    commandName: "mytool",
    abstract: "Print greeting messages many times!"
  )

  @Option(name: .long, help: "Number of greetings")
  var count: Int = 5

  func run() throws {
    let count = self.count < 1 ? 1 : self.count

    for _ in 0..<count {
      print("Hello, World!")
    }
  }
}

以上で準備は完了です。ビルドするには次のコマンドを実行してください。

$ swift build -c release

通常のビルドと同じコマンドですが、プロジェクトが依存するパッケージのダウンロードを行った後にビルドが開始されます。

Discussion