LinuxでSwiftのDispatchを静的リンクしたい

  • 1
    Like
  • 0
    Comment

Linuxでも使えるSwift。
-statis-executable というヤケクソ気味なビルドオプションで静的にリンクされた実行ファイルを作ることもできます。
そしてSwiftではDispatchを使って簡単に並列処理をすることもできます。
しかしこれを書いた時点でのリリース版 Swift 3.1.1 ではなぜか libdispatch.a が入っていないため静的リンクされた実行ファイルを作ることができませんでした。
そこでどうにかしてみた、という記事です。
ただ思い出しながら書いているのでどこか不完全かもしれません。

Swiftのインストール

まずは肝心のSwiftのインストールです。
swift.org から環境に合わせたアーカイブをダウンロードできます。
今回使用したのは Ubuntu 16.04 の Swift 3.1.1 です。
具体的なインストール方法は DOWNLOAD の Using Download の Linux の箇所に書いてある通りにすれば大丈夫です。
GETTING START のところをみれば動作の確認ができます。

libdispatchのセットアップ

Swift.org の SOURCE CODE の Core Libraries のところに libdispatch のリポジトリへのリンクがあります。
GitHubにジャンプするので、適当なところにクローンして、swift-3.1.1-RELEASE をチェックアウトします。

$ git clone https://github.com/apple/swift-corelibs-libdispatch.git
$ git checkout tags/swift-3.1.1-RELEASE

多分こんな感じで。
HEADが外れていると言われるでしょうがビルドするだけなので気にしません。
libdispatch のビルド方法は INSTALL.md に書いてあります。
一番下の方に Building and installing for Linux という項目があるので、まずは依存するパッケージを apt-get します。
次にビルドするのですが、configure にオプションを設定します。
まずINSTALL.mdの上の方に書いてあるように、Swiftのツールチェーンの指定とインストール先のオプションにインストールしたSwiftのパスを指定します。
また、静的ライブラリをビルドするためのオプションも設定します。
まとめるとこんな感じになると思います。

$ sh autogen.sh
$ ./configure --enable-static=yes --with-swift-toolchain=<PATH_TO_SWIFT_TOOLCHAIN> --prefix=<PATH_TO_SWIFT_TOOLCHAIN>

これでビルドの準備ができました。
が、その前にインストールしたツールチェーンに入っているlibdispatchを削除します。

Note that once libdispatch is installed into a Swift toolchain, that toolchain cannot be used to compile libdispatch again (you must 'make uninstall' libdispatch from the toolchain before using it to rebuild libdispatch).

とSwiftのツールチェーンを指定するところの説明の下に書いてあるように、すでにインストール済みだとビルドできない仕様になっているからです。
というわけで、続けてビルドする手順は下のようになります。

$ make uninstall
$ make
$ make install

インストールが終わると <インストール先>/lib/swift/linux に libdispatch.a ができているはずです。
この libdispatch.a を <インストール先>/lib/swift_static/linux の中にコピーしておきます。

これでめでたくDispatchを使ったプログラムをstatic-executableにできるかと思いきや、まだできません。
strlcpy() と getprogname() の参照がないと言われてしまいます。
これらは libbsd にあります。

libbsdのモジュールを用意する

ちょうどわかりやすい例があるので、こんな感じのモジュールを用意します。
swift-package-manager のDocumentの Require System Libraries あたりを参考にsystem-moduleを作ります。

module.modulemap
module CBSD [system] {
  header "shim.h"
  link "bsd"
  export *
}

CBSDという名前にしてみました。
また termios.h もインクルードしないとコンパイルエラーが出るので、ヘッダーはこんな風に。

shim.h
#include <termios.h>
#include <bsd/bsd.h>

このモジュールはgitで管理するようにして Dependencies で取り込むようにするか、適当なところに置いて swiftc -I<パス> でパスを通してビルドします。

どこかでstrlcpyとgetprognameを使う

ただ import CBSD しただけではリンクされないようなので、ちゃんとプログラムのどこかで使う必要があります。
無理やりこんなものを書きました。

main.swift
import CBSD

func dummyForDispatch() {
    let pn = getprogname()
    let buf = UnsafeMutablePointer<Int8>.allocate(capacity: 128)
    let r = strlcpy(buf, pn, 128)
    print("pn:\(String(cString: pn!)),buf:\(String(cString: buf)),r:\(r)")
}
dummyForDispatch()

これで swift build -Xswiftc -static-executable が通るようになるはずです。

あとがき

これでめでたくDispatchを使ったプログラムを静的リンクされた実行ファイルにできるようになりました!
ライブラリの依存関係を気にせずLinux間で使い回せるかもしれません。

ちなみに Foundation も似たようなことをすればできるようになるかもしれません。
依存関係が多そうなのでより面倒くさそうですが。