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

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

# やらないこと
* 自分で作ったコマンドを`brew install`出来るようにする

# Github
https://github.com/gin0606/SugoiTool

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

# XcodeでProjectを作る
![名称未設定.png](https://qiita-image-store.s3.amazonaws.com/0/3622/63bdebc0-74cd-2a48-47d5-dacb029629f6.png)

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

![名称未設定.png](https://qiita-image-store.s3.amazonaws.com/0/3622/2ebccb7a-7281-aec3-c3d1-97ec54930142.png)

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

## `Cocoa Framework`のTargetを追加する
![名称未設定.png](https://qiita-image-store.s3.amazonaws.com/0/3622/4f71f123-9d41-0a4d-ef1f-e1cc7b589eac.png)

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

## 作ったTargetのschemeを共有設定にする
方法は適宜ぐぐって下さい。

# 全てをまとめるWorkspaceを作る
さっき作ったxcodeprojを一旦閉じて`SugoiTool.xcworkspace`を作り、`SugoiTool.xcodeproj`を追加します。

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

# Carthage
```swift:Cartfile
github "Carthage/Commandant"
```

```zsh
$ carthage update --use-submodules --platform mac
```

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

## `SugoiToolKit`にFrameworkを追加する
*Carthage/Build/Mac*の中の*\*.framework*を`SugoiToolKit`の**General -> Linked Frameworks and Libraries**にドラッグアンドドロップする。

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

![スクリーンショット 2016-02-12 16.59.33.png](https://qiita-image-store.s3.amazonaws.com/0/3622/84f225d7-7d91-f966-4263-36befd747a03.png)

# `SugoiTool`のファイルを一通り消す
プロジェクトをここまで手順通りにやっていれば`Info.plist`を残して全て消す。

# `main.swift`を追加する
`SugoiTool`に`main.swift`を追加する。

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

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

## `SugoiToolKit`にコマンドの中身を実装する
```swift:Sugoi.swift
public struct Sugoi {
  public let isSugoi: Bool
  public init(isSugoi: Bool) {
    self.isSugoi = isSugoi
  }

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

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

### テストを実行する
```swift: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(), "普通")
  }
}
```

`SugoiToolKitTests`の**General -> Host Application**を`None`にするとテスト実行できるようになる

## `SugoiTool`にコマンドを実装する
```swift: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`でコマンドを登録する
```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

これを追加します。

## `SugoiTool`に`Components.plist`を追加する
```xml: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`してみる
```zsh
$ 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`してみる
```zsh
$ 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`を編集して使った