Vim
Makefile
Swift

Xcodeなしで楽しむSwift (REPLからライブラリ生成まで)

More than 1 year has passed since last update.

この記事は 慶應義塾大学SFC村井&徳田研 Advent Calendar 2015 の15日目の記事です。


Swiftはソースコード公開と共にLinuxへ正式対応しました。
しかし、Mac OS XでSwiftを書くときの定番IDEであるXcodeは提供されていません。
というかそもそも、XcodeはiOSやwatchOSようアプリケーションの開発には便利かもしれませんが、小さなCUIアプリケーションの開発のためにわざわざあの重たいIDEを起動するのは気が引けます。

というわけで、ここではXcodeを使用しないSwiftプログラミングのための各種コマンドの使い方について、REPLやデバッガからライブラリの生成、その他のTipsまでをまとめます。
iOSアプリなどの開発に関わる話は一切出てきません。飽くまでも、C++やJavaのような汎用言語の一員としてSwiftを利用する方法だけを解説しています。

なお、ここでの想定環境はMac OS X El Capitanです。
Linux上でももちろんエディタの設定方法や各コマンドの使い方は変わりませんが、Mac OS X上の方が自分でコマンドをコンパイルしない分その場所などがわかりづらいので、その見つけ方などまで含めて紹介します。
ちなみに、もちろんXcodeとXcode Command Line Toolsはインストール済みであることが前提です。

シェルからSwiftコンパイラを呼び出す

まずはXcodeを開かずに、シェルから直接swiftコンパイラを使ってみましょう。
ソースコードからコンパイルした場合はbuildとかいうディレクトリに作成されるSwiftコンパイラの実行ファイルですが、Xcodeをダウンロードして使用する場合はXcode.appの中に含まれているコンパイラを呼びださなければなりません。
そのためにべんりなコマンドがxcrunです。
何も引数を付けずに実行すると使い方が表示されます。

$ xcrun
Usage: xcrun [options] <tool name> ... arguments ...

Find and execute the named command line tool from the active developer
directory.

The active developer directory can be set using `xcode-select`, or via the
DEVELOPER_DIR environment variable. See the xcrun and xcode-select manual
pages for more information.

Options:
  -h, --help                  show this help message and exit
  --version                   show the xcrun version
  -v, --verbose               show verbose logging output
  --sdk <sdk name>            find the tool for the given SDK name
  --toolchain <name>          find the tool for the given toolchain
  -l, --log                   show commands to be executed (with --run)
  -f, --find                  only find and print the tool path
  -r, --run                   find and execute the tool (the default behavior)
  -n, --no-cache              do not use the lookup cache
  -k, --kill-cache            invalidate all existing cache entries
  --show-sdk-path             show selected SDK install path
  --show-sdk-version          show selected SDK version
  --show-sdk-build-version    show selected SDK build version
  --show-sdk-platform-path    show selected SDK platform path
  --show-sdk-platform-version show selected SDK platform version

例えば、xcrun swiftとすれば現在アクティブになっているXcodeのSwiftのインタプリタを起動することができますし、xcrun swiftcとすれば同じようにSwiftのコンパイラを起動できます。

ちなみに、「現在アクティブになっているXcode」は

$ xcode-select -s /Applications/Xcode.app/Contents/Developer/

のようにして指定することができます。現在どこにあるものが使用されているかはxcode-select -pで表示できます。

では次に、開発で中心的に使用するコマンドを詳しく見ていきましょう。

swift - Swiftインタプリタ

xcrun swift hoge.swiftとするとhoge.swiftを即座に実行できます。
また、xcrun swiftとするとREPLモードとなり、PythonやHaskellなどのようにすぐに結果を確認できる対話環境が開始します。

$ xcrun swift
Welcome to Apple Swift version 2.1.1 (swiftlang-700.1.101.15 clang-700.1.81). Type :help for assistance.
  1> print("Hello, World!")
Hello, World!
  2>

REPLはLLDBをベースに作られているため、中でデバッガのさまざまなコマンドを利用できます。

今はどうなのかよく知りませんが、β版の頃のPlayGroundはバグが多すぎて使い物にならなかったので、ずっと自分はこのインタプリタで動作の確認などしていました。
ただし、このインタプリタもたまに本来コンパイラを通した時にはエラーになるコードがエラーにならなかったり、エラーにならないはずのコードで止まってしまったりすることがあるので、飽くまでも電卓代わりか、構文の確認程度に使用する形になるかと思います。

swiftc - Swiftコンパイラ

xcrun swiftcとして起動できるのが一番のお目当て、Swiftコンパイラです。
GCCやClangと近いオプションを持っているので、それらでのコンパイルに慣れていれば、特に使い方がわからないということもないかと思います。

コンパイルを行うには、単にコマンドライン引数としてSwiftソースコードのファイル名を与えます。

$ xcrun swiftc hello.swift

生成される実行ファイルはソースコードのファイル名から拡張子を取ったものとなるため、上記の場合はhelloという実行ファイルが生成されます。
これを自分で指定したい場合はGCCなどと同様に-oオプションを使います。

また、XcodeでiOSアプリなどの開発をしているだけではあまり意識しない点として、複数ファイルをまとめてコンパイルする際にはメイン・エントリ・ポイントを持つかファイルをmain.swiftという名前にする必要があります。
そのため、複数ファイルをコンパイルする場合には、特に指定しなければ出力ファイルはmainという名前になります。

最適化付きでコンパイルを行うには-Oオプションを指定します。GCCのように-O2-O3といったレベルはありませんが、-Ouncheckedというオプションが別に用意されており、このオプションをつけると実行時に起こった問題のトラップなどの確認用コードまで全て取り去ります。

別にコンパイルされたライブラリをリンクする場合は-Iでライブラリの.swiftmoduleファイルがあるディレクトリを指定し、動的リンクライブラリがあるディレクトリを-Lで指定した上で、読み込むモジュールの名前を-lで指定します。

ライブラリの読み込みの具体例やライブラリの生成方法などについては後述します。

lldb - デバッガ

いくらSwiftのコンパイラが強力でコンパイル時にかなり多くのエラーを見つけてくれると言っても、ゆくゆく必ず必要になってくるのがデバッガです。
SwiftはLLVMベースで開発されているため、LLDBというLLVMのデバッガを利用することができ、これもxcrun lldbとすることで起動できます。

LLDBを利用するためには、コンパイル時に-gオプションをつけ、実行ファイルにデバッグ用の情報を付加します。
LLDBのコマンドライン引数として実行ファイル名を渡すと、デバッグ用の対話環境が起動します。

$ xcrun swiftc -g hello.swift
$ xcrun lldb hello
(lldb) target create "hello"
Current executable set to 'hello' (x86_64).
(lldb)

LLDBの使い方については他に解説している記事がたくさんありますのでここでは書きませんが、ブレイクポイントの設定やコールスタックのバックトレースなどかなり充実したデバッグ環境となっていますので、複雑なプログラムの挙動を確かめるときや、実行時エラーにあたった時などには是非活用してみてください。

ライブラリの作成

Swiftのライブラリを開発して配布する際や大規模なプログラムをモジュール化する際にはそのためのオプションを知っておく必要があります。
これらを把握するためにはまず、Swiftコンパイラが出力するファイルの形式と、その形式にするためのオプションの対応がわかっていなければなりません。
逆に言えば、それさえわかっていれば使うべきオプションは簡単に判断がつきます。

そこで、まずは普段の開発で使用することになるオプションとファイル形式の対応を表にまとめてみました。

コンパイラオプション 生成されるファイル 用途
-emit-ir LLVM-IR 生成される低レベルコードを確認するために利用できます
-emit-assembly (-S) アセンブリ言語 (.s) 生成される低レベルコードを確認するために利用できます
-emit-object (-c) オブジェクトファイル (.o) 複数ファイルを別々にコンパイルしてから、後でまとめて1つの実行ファイルにする際などに利用できます
-emit-library 動的リンクライブラリ (.dylib) 動的リンクライブラリとして利用できます
-emit-module モジュールファイル (.swiftmodule) クラスや関数などの定義が書かれたインクルードファイルとして利用できます
-emit-library -emit-object ライブラリオブジェクトファイル (.o) メイン・エントリ・ポイントを含まないオブジェクトファイルとして、他のメイン・エントリ・ポイントを持つオブジェクトファイルとリンクできます

実際に例を見てみましょう。以下の様なmodule.swiftファイルとmain.swiftファイルがある場合を想定します。

// module.swift
public let hello = "Hello, World!"
// main.swift
import Module
print(hello)

複数モジュールから成る1つの実行ファイルを作る場合

まずは、複数のモジュールから成るソフトウェアの開発を行うときのようにmodule.swiftの内容をモジュールModuleとしてコンパイルし、その後Mainというモジュール名を持つmain.swiftの内容とリンクして1つの実行ファイルにする場合のコマンドを見てみます。

$ xcrun swiftc -emit-module -module-name Module module.swift
# Module.swiftmodule と Module.swiftdoc が生成される
$ xcrun swiftc -emit-library -emit-object -module-name Module module.swift
# module.o が生成される
$ xcrun swiftc -I . -emit-object main.swift
# main.o が生成される
$ xcrun swiftc module.o main.o
# main が生成される
$ ./main
Hello, World!

ポイントは、module.swiftのオブジェクトファイル生成時に-emit-library -emit-objectと指定するところです。単に-emit-objectだけでコンパイルすると、メイン・エントリ・ポイントが生成されてしまうので、最後のmodule.omain.oをリンクするタイミングでmainという名前の関数が二重定義されている旨のエラーとなってしまいます。

また、main.swiftのコンパイル時には-I .としてカレントディレクトリのModule.swiftmoduleを探し出せるようにしておく必要があります。
しかし、リンク時には全てのオブジェクトファイルを静的にリンクするため、-L-lによる指定は必要ありません。

動的リンクライブラリを生成する場合

次に、動的リンクライブラリを生成する場合の手順を見てみます。

$ xcrun swiftc -emit-module -module-name Module module.swift
# Module.swiftmodule と Module.swiftdoc が生成される
$ xcrun swiftc -emit-library -emit-object -module-name Module module.swift
# libModule.dylib が生成される
$ xcrun swiftc -I . -L . -lModule main.swift
# main が生成される
$ ./main
Hello, World!

この場合は動的リンクライブラリを生成するので、module.swiftのコンパイルコマンドから-emit-objectオプションがなくなっており、libModule.dylibが生成されるようになっています。

また、main.swiftのコンパイル時にはライブラリを検索するパスにカレントディレクトリを追加するよう-L .と指定されており、その中のModuleというライブラリを使用することが-lModuleオプションによって明示されています。

このように、swiftcコマンドでは他のコマンドを一切使うこと無く、動的リンクライブラリの生成なども簡単に行うことができてしまうのです。

その他のTips

Swiftならではの機能をコマンドから使うためのオプションを紹介します。

モジュールのテストを書けるようにする

Swift 2.0からはモジュールのインポート時に@testableという属性を付与することでinternalなメンバにもアクセスし、テストを書けるようになっています。
しかし、この@testableを付けてインポートする機能はデフォルトではONになっていません(効率の面を考えればあたり前ですが...)ので、これを有効化するには@testableを使ってインポートしたいモジュールのコンパイル時に-enable-testingオプションを付ける必要があります。

$ xcrun swiftc -enable-testing -emit-module -module-name Module module.swift

ただ、なぜかこのオプション、コンパイラのヘルプに書かれていないんですよね。。
自分は自作のテストフレームワークを使ってテストをしているのでよく活用しているのですが、Xcodeなしでは使わないだろうと思われているのか...不思議です。

Objective-CやCをブリッジする

Objective-CやCのブリッジもサードパーティのライブラリが充実していない現状ではよく利用するオプションの1つです。
これも何故かヘルプに書かれていないのですが、import-objc-headerというオプションでブリッジングヘッダファイルを指定することで利用できます。

$ xcrun swiftc -import-objc-header bridging_header.h hello.swift

ちなみに、ブリッジングヘッダファイルの名前は確かXcodeでは特定の名前にしていないと起こられるようになっていたかと思いますが、オプションで指定する際には特にどんな名前でも構いません。

おわりに

Swiftコンパイラをシェルから使用するための最低限のコマンドについてまとめました。
自分はSwift大好きな癖にiOSやMac OS Xなどのアプリケーションは一切開発したことがなく、Xcodeの使い方もよくわからない、という人間なので普段からこれらのコマンドを使い、Makefileを書いてSwiftアプリケーションを作成しています。
みなさんもLinuxへ移植されたこのタイミングを機に、あの異様に重たいIDEから開放されたSwiftプログラミングを楽しんでみてはいかがでしょうか。