けっこう茨の道です
なんかまだ「初心者でも鼻くそほじりながら日曜日にさらっとね」って感じではないです。なにせコンパイラ、Xcode、Swift Package Manager(以下SPM)すべてが日進月歩でバージョンアップしているのでウェブに書いてあることそのまま試すと全く動かなかったりします。
このブログの内容は2016年11月6日の内容です。Swiftはversion 3.0.1 (swift-3.0.1-RELEASE)を使いUbuntu 14.04上でやりました。
まずは
公式ドキュメント。必読です。私はここを読まずにたくさんの時間を無駄にしました。アホですね。
CとSwift混ぜるとき
モジュールを分ける
SPMはSources下のディレクトリ単位でモジュール扱いをします。Sources直下にあるディレクトリ一つがモジュールという単位になります。
CとSwiftのコードを同じモジュールに混在させるとバリデーションに引っかかります。素直に分けましょう。私のオセロライブラリGrapheneの場合
(Project root) ├── Graphene.xcodeproj ... xcodeprojの中身がドバ~っと ├── LICENSE ├── Package.swift ├── README.md ├── Sources │ ├── Graphene │ │ ├── Board.swift │ │ ├── BoardBuilder.swift │ │ ├── BoardMediator.swift ... ファイルがドバ~っと │ └── Intrinsics │ ├── Intrinsics.c │ └── include │ ├── Graphene-Bridging-Header.h │ ├── Intrinsics.h │ └── Swift_Bridging_Header.h └── Tests ├── GrapheneTests │ └── GrapheneTests.swift └── LinuxMain.swift
こんな感じになっています。Swiftコードは(Project root)/Sources/Graphene
というパッケージ名と同じモジュールにまとめてあります。Cのコードは(Project root)/Sources/Intrinsics
というモジュールにしました。Cモジュールにはinclude
(すべて小文字)が必要です。なので(Project root)/Sources/Intrinsics/include
ディレクトリがあります。モジュール名と同じヘッダファイルが第一に使われます。他のbridging headerは多分必要ないのですが、念のために残してあります。消して壊れたら嫌なので。
Package.swiftにtargetを追加してdependencyを明示する
オフィシャルのドキュメントに書いてあるのですが、dependencyを明示するために依存しているモジュールをPackage.swiftのtargetをきちんと書く必要があります。私のSources/GrapheneはSources/Intrinsicsに依存しています。つまり
import PackageDescription let package = Package( name: "Graphene", targets: [ Target(name: "Graphene", dependencies: ["Intrinsics"]), ] )
こんな感じです。これはCのコードからなるIntrinsicsモジュールをSwiftモジュールGrapheneより先にビルドするためです。
@_silgen_nameはとりあえず外す
なんかCのシンボルがリンク時に見つからないとか言うし@silgen_nameアトリビュートがあるファイルにimport Intrinsicsと書くと今度はシンボルが曖昧とか言われるしわけわかんないのでとりあえず@silgen_nameは消してimport Intrinsicsを残すことにしました。つまり
#if os(Linux) import Intrinsics #else @_silgen_name("_bitScanForward") func _bitScanForward(_: UInt64) -> UInt @_silgen_name("_bitPop") func _bitPop(_: UInt64) -> UInt #endif
こんな感じです。当然macOSでXcode上でビルドするためには_silgen_nameが必要なので、こんなふうに分岐させてみました。Linux向け特殊処理が必要なときは今のところこうやってさっさと回避するのが吉のようです。
こうすることでLinux上のコンパイラはIntrinsics内のシンボルをリンクしようとするし、Xcode上では@_silgen_nameアトリビュートを使ってビルドしてくれます。ここらへんは多分ちゃんとしたやり方があると思うのですが、今はこれが精一杯(ルパン)。
ちゃんとstdヘッダをインクルードする
uint64_tのためにCのコードに#include <stdint.h>
が必要です。Xcodeでビルドするときはどっかでインクルードされてるから必要なかったりして、意外にハマりました。他にもimplicitに依存しているstdライブラリはちゃんと明示する必要があります。明示しておくほうがお行儀も良いですしね。
これらがうまくいくと
~/projects/Graphite$ swift build warning: minimum recommended clang is version 3.6, otherwise you may encounter linker errors. Compile Intrinsics Intrinsics.c Linking Intrinsics Compile Swift Module 'Graphene' (27 sources) <module-includes>:1:1: warning: umbrella header for module 'Intrinsics' does not include header 'Graphene-Bridging-Header.h' #include "/home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Intrinsics/include/Intrinsics.h" ^ <module-includes>:1:1: warning: umbrella header for module 'Intrinsics' does not include header 'Swift_Bridging_Header.h' #include "/home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Intrinsics/include/Intrinsics.h" ^ /home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Graphene/EdaxProtocol.swift:28:13: warning: variable 'bb' was never mutated; consider changing to 'let' constant var bb = SimpleBitBoard() ~~~ ^ let /home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Graphene/NegaAlphaSearch.swift:81:26: warning: result of call to 'put(_:x:y:guides:)' is unused newBoard.put(turn, x: px, y: py, guides: false) ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Graphene/BoardMediator.swift:22:9: warning: result of call to 'updateGuides' is unused updateGuides(.black) ^ ~~~~~~~~ /home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Graphene/SimpleProofSolver.swift:281:33: warning: result of call to 'put(_:x:y:guides:returnChanges:)' is unused newBR.boardMediator.put(node.whosTurn, x: px, y: py) ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Compile Swift Module 'Graphite' (1 sources) Linking ./.build/debug/Graphite
と、GraphiteというGrapheneパッケージを利用した実行ファイルがビルドできました(警告出てますね。恥ずかしい)。CモジュールIntrinsicsが先にコンパイルされているのがわかります。
そのほかSwift on Linuxのメモ
Ubuntu 14.04でやる
RHELとかCentOSでもビルドできたという報告がググルと出ますが、どうも更に茨の道のようです。Ubuntu 14.04が一番鉄板です。16も私のところではライブラリ周りでうまくいかなくて諦めました。ヘタレです。
arc4randomはない
知らなかったのですが、arc4randomはFonudationの中ではなくてBSDのライブラリファンクションなんですね。
というわけで当然Linuxのglibcにはないので、代替品を用意する必要があります。私はこんな感じにしました。
#if os(Linux) import Glibc import SwiftShims #else import Darwin #endif func cs_arc4random_uniform(upperBound : UInt32 = UINT32_MAX) -> UInt32 { #if os(Linux) return _swift_stdlib_cxx11_mt19937_uniform(upperBound) #else return arc4random_uniform(upperBound) #endif }
こういうときもさっさとLinuxだけ別にしちゃいましょう。_swift_stdlib_cxx11_mt19937_uniform
はSwiftShimsにあります。CSPRNGの必要あるのかって気はしますが。他のパターンもSwiftShimsで結構カバーできるような気がします。
まとめ
とりあえずLinux上でビルドができるようになりました。というわけでLinux上で開発ができるようにEmacsの環境を整えようと思います。
Xcodeで書きたいのはやまやまなのですが、SwiftのコンパイラのバージョンがmacOS用のツールチェーンだと微妙に古いのでうまくいかず、かといってオフィシャルのベータをインストールしたりしてぐちゃぐちゃにするとiOS用のビルド環境がなくなっちゃうのでLinux上で頑張ることにします。
2017.02.05追記
開発はXcodeだけでLinuxと両方サポートできています。なのでXcodeで快適開発しちゃいましょう。Emacsが快適じゃないとは言ってないですよ。Emacsも大好きです。