15
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

XcodeのBuild PhasesでSwiftスクリプトを実行する

Last updated at Posted at 2020-03-08

ビルド前にファイルの自動生成が必要な時はコンパイル前に行っておく必要があります。
人の温かみを感じるために毎度エンジニアが自動生成するスクリプトを実行しても良いのですが、XcodeのBuild Phasesの中に組み込めばビルド時に自動的に行ってくれます。
Build PhasesではBashスクリプトを実行できます。なので基本Bashスクリプトで自動生成するプログラムを書くのですが、BashからSwiftコマンドを実行すればSwiftで書かれたプログラムを実行できます。

Swiftはコマンドラインの実行も対応しているので、PythonやRubyのスクリプト言語がわからないiOSエンジニアでもメンテナンスができるというメリットがあります。

環境

Xcode11.3.1

スクリプトファイルの作成

空のSwiftファイルを生成します。そして1行目に以下のおまじないを挿入してください。

Script.swift
#!/usr/bin/swift

#!/usr/bin/swift はかならず1行目に書きます。コメントの下ではうまく動きません。
これはSwiftコードをコマンドラインから直接実行できるスクリプトであるという宣言になります。
2行目以降は普通のSwiftコードと同じノリで書けます。

Script.swift
#!/usr/bin/swift

import Foundation

print("hello!")

普段UIKitを使っていればあまりimport Foundationすることはないかもしれませんが、Dateを使うには必要なパッケージです。

Build Phasesに組み込む

まずはこのScript.swiftがBuild Phasesで動くようにします。

Xcodeのプロジェクトを開き、ターゲットのBuild Phasesを開きます。
追加ボタンから「New Run Script Phase」を選択します。
Screen Shot 2020-03-08 at 16.37.41.png

新たに追加された「Run Script」を展開し、スクリプト欄に以下のコマンドを追加します。パスやファイル名は実際のパスとファイル名を指定してください。
/usr/bin/env xcrun --sdk macosx swift ${SRCROOT}/CommandSample/Scripts/Script.swift
Screen Shot 2020-03-08 at 17.19.50.png

一度ビルドしてみます。ビルドが終わったらログを見てみましょう。

Screen Shot 2020-03-08 at 16.46.09.png
hello!が出力されていますね!


ところで/usr/bin/env xcrun --sdk macosx swift {Path}という回りくどいやり方ではなく普通にswift {Path}で良いのでは?と思う人もいると思います。
しかしこれではiOSをターゲットとして実行されてしまうのです。環境がiOSになるのでCSVファイルにアクセスする方法が限られてしまうのでmacOSをターゲットとするためにやや回りくどいコマンドとなっています。

macのターミナルから実行する時はターゲットが自動的にmacOSとなっていましたのでやや罠です。

引数を受け取る

ファイルを自動生成するにはCSVのパスと出力パスを引数で受け取れたほうが便利です。SwiftではCommandLineから受け取れます。

Script.swift
#!/usr/bin/swift

import Foundation

// コマンド,引数1,引数2...ととれるので実際に欲しい引数は2番目から。よって最初はDrop
let arguments = CommandLine.arguments.dropFirst()
let inputPath = arguments[0]
let outputPath = arguments[1]

// CSVを読み込む
let csv = try! String(contentsOfFile: inputPath)
// CSVを加工
let output = covertSomething(csv)
// 新しいファイルで保存
try! output.write(toFile: outputPath, atomically: true, encoding: .utf8)

スクリプトに引数を追加すれば完了です。
Screen Shot 2020-03-08 at 17.43.39.png
Xcode10のNew Build SystemからBuild Phaseが並列実行するようになるためInput FilesとOutput Filesを指定します。コンパイル前にこのスクリプトが必ず実行されるようになります。


スクリプトファイルなのである程度はForce unwrapを使って強気に出てもいいと思いますが、万が一エラーが起きた時にどこでコケたのかを把握しやすくするためにNSError形式でエラーを出したり、適宜print関数を出力すると良いでしょう。

let inputPath = arguments[0]
let outputPath = arguments[1]

print("引数OK")

if !FileManager.default.fileExists(atPath: inputPath) {
    throw NSError(domain: "Script", code: 1, userInfo: ["description": "CSVがありません。"])
}

[補足]Playgroundでデバッグする

XcodeはコマンドラインSwiftスクリプトの補完がうまくできないので、Xcodeの補完をよく使う人にとっては非常に書きづらいスクリプトです。Swift Playground上であれば補完も効きますし、直接実行してデバッグができます。

Swift Playgroundを始める時にiOSではなくmacOSを選択しましょう。
Screen Shot 2020-03-08 at 15.38.49.png
Playgroundではコマンドライン引数を渡せませんので、引数から受け取ったあとの処理を試すのがよいでしょう。

// CSVを読み込む
// let csv = try! String(contentsOfFile: inputPath)

// CSVを読み込んだと仮定
let csv = "A,B,C\n1,2,3"
// CSVを加工
let output = covertSomething(csv)
// できてるか目視
print(output)

// 新しいファイルで保存
// try! output.write(toFile: outputPath, atomically: true, encoding: .utf8)
15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?