GPL v2 only の派生物を、リポジトリごと隔離した
このサイトを作っていて、いちばん時間を使ったのは実装ではありませんでした。
ライセンスです。
コードを書いている時間より、「これは派生物なのか」を考えている時間のほうが長かった週があります。しかも日本語で書かれた実例がほとんど見つからなくて、かなり心細かった。
同じところで止まる人がいると思うので、判断の過程ごと残しておきます。
何をしたかったか
単語カードを自動で作りたかったんです。
教材に出てくる単語を拾って、原形(レンマ)を求めて、活用を展開して、訳語を付けて、間隔反復で復習できる形にする。この「原形を求める」ところに形態素解析が要ります。
アラビア語の形態素解析器で、実用になるものは多くありません。そのなかで使えそうだった辞書データが、GPL v2 でした。
「or later」が無い、という一行
ここが最初の分かれ道でした。
多くの GPL のプロジェクトは「version 2 or (at your option) any later version」と書いています。この場合、利用者は GPLv3 を選べます。
使いたかった辞書の LICENSE は、こうなっていました。
... as published by the Free Software Foundation version 2.
「version 2.」で止まっています。
つまり GPL v2 only。GPLv3 に上げる選択肢がありません。
最初これを読み飛ばしていました。「GPL v2 ね」と思って先に進んで、あとから「or later が無い」ことに気づいて全部やり直しています。
ライセンスファイルは、最後の一行まで読んでください。 一語で結論が変わります。
CC BY-SA 4.0 は助けにならなかった
「CC BY-SA 4.0 は GPL 互換」という話を見かけて、一瞬これで抜けられるかと思いました。
抜けられません。CC BY-SA 4.0 の GPL 互換は GPLv3 に対する一方向で、GPLv2 は対象にならないからです。
GPL v2 only という条件は、思っていたよりずっと狭いところに人を追い込みます。
どこからが派生物なのか
ここが本題です。3つ問いがありました。
- 解析結果から作った
cards.jsonは派生物か cards.jsonを読み込むサイト本体は派生物か- 生成器のソースは「対応するソース」として公開が要るか
1 は、実質的に辞書の見出し語一覧の複製になり得ます。派生物として扱いました。
3 も明らかに要ります。GPL は「配布するなら、それを作れるソースも渡せ」という要求なので、生成器を公開しないと cards.json を配れません。
問題は 2 でした。サイト本体まで GPL に引きずられると、教材ごと全部が GPL になります。 それは避けたい。
分割の方向は、1つしか成立しなかった
「生成器だけ公開して、あとは分ける」——最初はそう考えました。
ところが解析器は Go で書いてあって、コマンド側が internal/ 以下のパッケージに依存していました。
Go の仕様上、internal/ は他のモジュールから import できません。
つまり「生成器のコマンドだけを別モジュールに切り出す」ことが構造上できない。切り出せるのは、逆側(API サーバー)だけでした。
ライセンスの都合で分割の方向を決めようとしたのに、言語仕様のほうが先に方向を1つに固定していた、という話です。
こういうのは実際に手を動かすまで分かりませんでした。
最終的な構成
こうなりました。
| リポジトリ | 中身 | ライセンス |
|---|---|---|
| サイト本体 | 教材と Docusaurus のコード | — |
darasa-vocab-builder | カード生成器 | MIT |
darasa-vocab-cards | 生成されたカードと、生成の入力 | GPL v2 |
darasa-gloss-ja | 訳語 | GPL v2 |
darasa-morph | 解析器と台帳の生成 | MIT |
守っている規律は3つです。
- サイト本体に
cards.jsonをコミットしない npm run buildはcards.jsonを読まない- 実行時に、GPL のリポジトリから fetch する
サイトには出典表示を置いて、「対応するソース」へ辿れるようにしています。
分けた理由は「秘匿」ではない
これは自分への戒めとして書いておきます。
リポジトリを分けると、コミットメッセージに「private にするため」と書きたくなります。でも実際の理由は配布物として分けることです。
GPL のリポジトリは公開されていて、そこに「対応するソース」への道筋がある。隠すために分けたのではなく、混ぜないために分けた。 ここを書き間違えると、あとから読む人(と未来の自分)が判断を誤ります。
境界を Makefile に置いた
人間が「ここから先は混ぜない」と覚えておく方式は、いつか必ず破られます。
なので、境界をコマンドに落としました。
## vocab-source: 教材語彙を darasa-vocab-cards に書き出す
##
## これが教材と生成器の境界である。
## 決定的なので、2回実行すればバイト単位で一致する。
vocab-source:
$(NODE) scripts/vocab-source/build.mjs
## vocab: 書き出し → 生成 → 検証 → コミット
## 生成そのものは builder が行う。cards.json はここには来ない。
vocab: vocab-source
$(MAKE) -C $(BUILDER_REPO) build verify commit
サイト本体に残っているのは、教材の MDX から見出しを抜き出して TSV に書き出すところまでです。そこから先は別リポジトリの make に投げます。
さらに、規律が守られていることを検査するコマンドも置きました。
make vocab-source-verify # MDX を書き換えていない / build が cards.json を読まない
チェックできない規律は、規律として弱いと思っています。
諦めたもの
ライセンスの都合で、機能を1つ落としています。
生成の過程で単語の頻度データを使っています。「どの語を、どの順で教材に入れるか」を決めるためです。
このデータもライセンス上グレーだったので、cards.json に頻度を持ち込まないことにしました。 ビルド時に選定へ使うだけで、成果物には残さない。
その結果、「あなたはコア語彙の何%をカバーしています」という指標が出せなくなりました。 学習アプリとしてはかなり欲しい機能です。
でも、カバレッジを出すために頻度データを配布物に入れるのは、線を越えます。機能のために線を動かさないと決めました。
ここは今でも少し惜しいと思っています。
ただ、一度「このくらいならいいか」で線を動かすと、次からその位置が基準になります。判断を人に説明できなくなるほうが怖い。
実行時 fetch の代償
cards.json を別リポジトリから実行時に取ってくるので、代償もあります。
- 初回表示で外部リクエストが1回増える
- そのリポジトリが落ちるとカードが出ない
- オフラインでは使えない
正直なところ、ビルド時に取り込めたら楽でした。でもそれをすると、成果物にGPLのデータが同梱されます。取り込まないことが、そのまま境界の実装になっているので、ここは受け入れています。
まとめ
- ライセンスは最後の一行まで読む。「or later」の有無で結論が変わります
- どこからが派生物かを、先に紙の上で決める。 コードを書き始めてからだと戻れません
- 言語仕様が分割の方向を決めることがある。 Go の
internal/がそうでした - 境界は人の記憶ではなく、コマンドとテストに置く
- 機能のために線を動かさない
同じ場所で止まっている人の役に立てば嬉しいです。
このサイト自体はアラビア語の学習教材なので、よかったらそちらもどうぞ🙇