同じワークスペース内で作ったSwiftモジュールを使う

こういう感じの構成で、libの中をPokemonKitとしてモジュールにして、sourcesをアプリにしたいと思った。

app $ tree
.
├── Makefile
├── README.md
├── build.ninja
├── lib
│   └── pokemon.swift
└── sources
    └── main.swift

モジュールにしなければ、swiftc lib/pokemon.swift sources/main.swift -o build/main で済む話だし、どこかのGitリポジトリのライブラリを持ってくるのであればSwiftPMを使えばいい話である。

どうしてもSwiftPMを使いたくないというケースも考慮して、一応このまま突き進もう。

module = swiftc -emit-module -module-name PokemonKit -o build/PokemonKit
module-obj = swiftc -emit-library -emit-object -module-name PokemonKit
app-obj = swiftc -I build/ -emit-object
executable = swiftc
lib = lib/pokemon.swift
app = sources/main.swift

rule kit
    command = $module -o build/PokemonKit $in && mv build/PokemonKit build/PokemonKit.swiftmodule && $module-obj $in -o $out

rule app-obj
    command = $app-obj $in -o $out

rule app
    command = $executable $in -o $out

build build/pokemon.o: kit $lib
build build/main.o: app-obj $app
build build/main: app build/pokemon.o build/main.o

default build/main

ninja-buildを使って見た。これで差分ビルドにも対応している。

-emit-module の時にswiftdocも一緒に生成するのだが、出力場所を指定する方法がわからなかった。仕方ないので -o をえいやで指定すると .swiftmoduleのサフィクスがつかなかったので、自分でリネームする処理を入れた。惜しい。

ビルド方法は以下のようになっている。

$ cat Makefile
emitted = build/*

.PHONY: clean build

clean:
    rm $(emitted)

build:
    ninja -j 1

ninja -j 1 で並列にビルドしないようにしている点がポイントである。 こうしないと、pokemon.oとmain.o を同時に作ろうとして失敗する。

ビルドした状態。

app $ tree
.
├── Makefile
├── README.md
├── build
│   ├── PokemonKit.swiftdoc
│   ├── PokemonKit.swiftmodule
│   ├── main
│   ├── main.o
│   └── pokemon.o
├── build.ninja
├── lib
│   └── pokemon.swift
└── sources
    └── main.swift

動かしてみよう。

app $ ./build/main
[ATTACK] Pikachu:Lv.1:Scratch!
[ATTACK] Bulbasaur:Lv.5:Scratch!
[ATTACK] Pikachu:Lv.11:ThunderShock!
[ATTACK] Pikachu:Lv.4:Scratch!
[ATTACK] Pikachu:Lv.20:ThunderShock!

ちなみにコードはこんな感じである。

import PokemonKit

let a = PokeDeck(pokemons: [
    Pikachu(level: 1),
    Bulbasaur(level: 5),
    Pikachu(level: 11),
    Pikachu(level: 4),
    Pikachu(level: 20)
])

for pokemon in a.pokemons {
    do {
        try pokemon.attack(move: ElectricMove.ThunderShock)
    } catch {
        try! pokemon.attack(move: NormalMove.Scratch)
    }
}

無駄に凝ってしまった。

記事にしてしまったし一応コードをアップしておいた。ご参考まで。

GitHub - toshi0383/PokemonAttackDemo: swiftc usage demo using ninja-build

参考