Swift
Carthage

Swiftでコマンドラインツールを作る

More than 1 year has passed since last update.

やること

  • Swiftでコマンドラインツールを作る
  • プロジェクト構成などがCarthageとだいたい同じものを作る
  • 依存管理にCarthageを使用する

やらないこと

  • 自分で作ったコマンドをbrew install出来るようにする

Github

https://github.com/gin0606/SugoiTool

大体同じ手順で作業してcommitしたので、多少参考になるかもしれない。

XcodeでProjectを作る

名称未設定.png

最初に作るのはCocoa Applicationです。

名称未設定.png

今回は凄いツールを作ります。適宜自分が作るプロジェクトに置き換えて下さい。

Cocoa FrameworkのTargetを追加する

名称未設定.png

Frameworkの名前はプロジェクト名にKitを付けたやつにします。適宜変更したりいろいろして下さい。

作ったTargetのschemeを共有設定にする

方法は適宜ぐぐって下さい。

全てをまとめるWorkspaceを作る

さっき作ったxcodeprojを一旦閉じてSugoiTool.xcworkspaceを作り、SugoiTool.xcodeprojを追加します。

ここからはWorkspaceを見て作業します。

Carthage

Cartfile
github "Carthage/Commandant"
$ carthage update --use-submodules --platform mac

carthage updateしてCheckoutされたプロジェクトをSugoiTool.xcworkspaceに追加します。

SugoiToolKitにFrameworkを追加する

Carthage/Build/Macの中の*.frameworkSugoiToolKitGeneral -> Linked Frameworks and Librariesにドラッグアンドドロップする。

Build PhasesCopy Filesを追加してFrameworkがコピーされるようにする。

スクリーンショット 2016-02-12 16.59.33.png

SugoiToolのファイルを一通り消す

プロジェクトをここまで手順通りにやっていればInfo.plistを残して全て消す。

main.swiftを追加する

SugoiToolmain.swiftを追加する。

main.swiftprint("Hello")とか書いて、Cmd+RでHelloって出力されるはず。

コマンドを実装する

プロジェクトの構成的に、SugoiToolKitにコマンドの実装とテストを置いて、SugoiToolの方からそれを使うというような感じにする。

SugoiToolKitにコマンドの中身を実装する

Sugoi.swift
public struct Sugoi {
  public let isSugoi: Bool
  public init(isSugoi: Bool) {
    self.isSugoi = isSugoi
  }

  public func command() -> String {
    if isSugoi {
      return "凄い"
    }
    return "普通"
  }
}

適宜テストを書いて下さい。

テストを実行する

SugoiTests.swift
import XCTest
@testable import SugoiToolKit

class SugoiTests: XCTestCase {
  func testSugoi() {
    let sugoi = Sugoi(isSugoi: true)
    XCTAssertEqual(sugoi.command(), "凄い")
  }

  func testSugokunai() {
    let sugoi = Sugoi(isSugoi: false)
    XCTAssertEqual(sugoi.command(), "普通")
  }
}

SugoiToolKitTestsGeneral -> Host ApplicationNoneにするとテスト実行できるようになる

SugoiToolにコマンドを実装する

SugoiCommand.swift
import Commandant
import SugoiToolKit
import Result

public struct SugoiCommand: CommandType {
  public let verb = "sugoiCommand"
  public let function = "このコマンドが凄いかどうかを表示する"

  let sugoi = Sugoi(isSugoi: true)

  public func run(options: NoOptions<NSError>) -> Result<(), NSError> {
    print(sugoi.command())
    return .Success(())
  }
}

main.swiftでコマンドを登録する

main.swift
import Commandant

let registry = CommandRegistry<NSError>()
registry.register(SugoiCommand())

let helpCommand = HelpCommand(registry: registry)
registry.register(helpCommand)

registry.main(defaultVerb: "help") { error in
  fputs("\(error)\n", stderr)
}

ここまで来たら、ビルドは通るけど実行するとUnrecognized commandとか表示される状態になってると思います。

コマンドラインから実行できるようにする

Makefileを追加する

https://gist.github.com/gin0606/b1903759d9f83d9465ff

これを追加します。

SugoiToolComponents.plistを追加する

Components.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <dict>
        <key>BundleIsVersionChecked</key>
        <false/>
        <key>BundleOverwriteAction</key>
        <string>upgrade</string>
        <key>ChildBundles</key>
        <array>
            <dict>
                <key>BundleOverwriteAction</key>
                <string></string>
                <key>RootRelativeBundlePath</key>
                <string>Library/Frameworks/SugoiToolKit.framework/Versions/A/Frameworks/Commandant.framework</string>
            </dict>
            <dict>
                <key>BundleOverwriteAction</key>
                <string></string>
                <key>RootRelativeBundlePath</key>
                <string>Library/Frameworks/SugoiToolKit.framework/Versions/A/Frameworks/Result.framework</string>
            </dict>
        </array>
        <key>RootRelativeBundlePath</key>
        <string>Library/Frameworks/SugoiToolKit.framework</string>
    </dict>
</array>
</plist>

XcodeからComponents.plistを追加して、↑これと同じ状態になるようにして下さい。

ChildBundlesは適宜変更して下さい。

make installしてみる

$ make install
$ /usr/local/bin/SugoiTool 
dyld: Library not loaded: @rpath/Commandant.framework/Commandant
  Referenced from: /usr/local/bin/SugoiTool
  Reason: image not found
[1]    55628 trace trap  /usr/local/bin/SugoiTool

😵

SugoiTool, SugoiToolKitの設定を変える

SugoiToolKit

Build Settings -> Embedded Content Contains Swift Code

Yesにする

Build Settings -> Runpath Search Paths

  • $(inherited)
  • @executable_path/../Frameworks
  • @loader_path/../Frameworks

の3つを設定する

SugoiTool

Build Settings -> Runpath Search Paths

  • @executable_path/.
  • @executable_path/SugoiToolKit.framework/Versions/Current/Frameworks
  • /Library/Frameworks
  • /Library/Frameworks/SugoiToolKit.framework/Versions/Current/Frameworks
  • $(inherited)

の5つを設定する。SugoiToolKitの部分は適宜変更する

make installしてみる

$ make install
$ /usr/local/bin/SugoiTool 
Available commands:

   help           Display general or command-specific help
   sugoiCommand   このコマンドが凄いかどうかを表示する
$ /usr/local/bin/SugoiTool sugoiCommand
凄い

すごい!!!!!!!!!!!!!!!!!!!!!!!!!!

参考

  • https://github.com/Carthage/Carthage
    • Runpathはまるっとそのままコピペしたので、不要な設定もあるかもしれない
    • Makefileを編集して使った
    • Components.plistを編集して使った