個人開発のPythonプロジェクトをGitHubで公開しようとしたとき、
「依存ライブラリのライセンスって何か見ておいた方がいいよな」
と思い、pip-licensesを触ってみました。
正直、ライセンスに詳しいわけでもなく、法的に完璧な判断をしたいわけでもありません。
この記事は、
- よく分からないなりに手を動かしてみた
- 完璧は諦めた
- やらかさないためのガードレールだけ作った
という、その結果の共有です。
この記事の内容は法的判断を代替するものではありません。
商用利用や大規模公開の場合は、専門家への確認を前提としてください。
やりたかったこと
- 明らかにNGそうなライセンスは自動で止めたい
- OKと言えそうなものは分かるようにしたい
- それ以外は「あとで自分が見る」で済ませたい
つまり、
完全に自動で判定するのではなく、立ち止まるきっかけが欲しかった
という感じです。
判定ルール(暫定版)
| 区分 | 判定 | 扱い |
|---|---|---|
| 🚫 ブラック | 含まれていたらNG | エラー停止 |
| ✅ ホワイト | 正規化して完全一致 | OK |
| ⚠️ グレー | それ以外 | 人が確認 |
厳密さよりも
自分が説明できるルールかどうか を優先しています。
設定(ここだけ自分で決める)
REQUIREMENTS_FILE = "requirements.txt"
OUTPUT_FILE = "THIRD-PARTY-NOTICES.md"
# 含まれていたらアウトにするもの
# → コピーレフト色が強く、個人開発では判断が難しいため
BLACKLIST = [
"GPL",
"AGPL",
"LGPL",
"MPL"
]
# 正規化後に完全一致したら OK とするもの
# → 利用条件が比較的シンプルで実績が多いもの
WHITELIST = [
"MIT",
"BSD",
"APACHE-2.0"
]
参考にしたサイト:
requirements.txt に書いた依存だけを見る
result = !pip-licenses
↑だとローカル環境の全部が対象になるのですが、
使っていないライブラリまで大量に出てしまいました。
(Google Colabで実行する時などは特に)
そのため requirements.txt に書いたものだけ を見ることにしました。
from pathlib import Path
def load_requirements(path):
return [
line.split("==")[0].strip()
for line in Path(path).read_text().splitlines()
if line and not line.startswith("#") #コメントアウトされたものは無視
]
packages = load_requirements(REQUIREMENTS_FILE)
print(f"ライセンスチェック対象: {packages}")
pip-licenses をそのまま使う
深く考えず、まずはそのまま実行します。
result = !pip-licenses --packages {" ".join(packages)}
result_text = "\n".join(result)
JSON にしたり、細かくパースしたりはしていません。
読める形で出てくれれば十分 という判断です。
表記揺れは最低限だけ吸収する
出力を見ていると、
MITMIT LicenseBSDBSD License
のような表記揺れがありました。
全部きれいに揃えるのは大変そうだったので、
今回は 接尾語を落とすだけ にしました。
def normalize_license_name(text: str) -> str:
text = text.upper().strip() # 大文字に変換
text = text.replace("SOFTWARE LICENSE", "")
text = text.replace("LICENSE", "")
return text.strip() # 最後にもう一度 strip() して空白除去
ブラック / ホワイト / グレー判定
normalized_text = normalize_license_name(result_text)
# ブラック:含まれていたら NG
black_hits = [
k for k in BLACKLIST
if k in normalized_text
]
# ホワイト:正規化後に完全一致
white_hits = []
for line in normalized_text.splitlines():
license_name = line.strip()
if license_name in WHITELIST:
white_hits.append(license_name)
- ブラックにも当たらない
- ホワイトにも完全一致しない
ものは、すべて グレー(WARN) 扱いです。
LGPL が LGPL-2.1 にもヒットしますが、
今回は「含まれていたら止める」ため、あえて粗い文字列判定にしています。
判定結果の扱い
if black_hits:
print(f"🚫 LICENSE CHECK FAILED: {sorted(set(black_hits))}")
print(result_text)
assert False, "禁止ライセンスが見つかりました"
if not white_hits:
print("⚠️ LICENSE CHECK WARNING: gray licenses detected")
print("目視でレビューしてください")
- ブラックだけは止める
- グレーはログに出して、人が見る
「全部は自動化しない」前提です。
THIRD-PARTY-NOTICES を生成する
!pip-licenses \
--format=markdown \
--packages {" ".join(packages)} \
--output-file={OUTPUT_FILE}
print("✅ ライセンスチェック完了。NOTICEファイルを作成しました")
OSS 公開用のファイルが一応できるので、 ここはそのまま使っています。
- ブラック / ホワイト / グレー判定のロジック
-
requirements.txtから依存パッケージを読み込む処理 - NOTICE ファイル (
THIRD-PARTY-NOTICES.md) の生成
これら一式のコードは GitHub にまとめてあります。
JSON出力に対応し、Notebook・CLI・CI のいずれでも実行可能です:
まとめ
- ライセンスをちゃんと判定するのは難しい
- でも「何も見ない」よりはマシ
- ブラック / ホワイト / グレーくらいが自分には扱いやすかった
自分と同じように
「よく分からないけど、最低限のガードレールは欲しい」
と思った人の参考になれば嬉しいです。