pre-commitでコミット時にライセンスのチェックを行う

## きっかけ

先日投稿したオープンソースライセンスの記事にちょっと反響があり、Qiitaを始めたばかりの私としてはとても嬉しい体験でした。ありがとうございます。

https://qiita.com/The_Past_of_Dice/items/ef438420542667bed92e

私自身が開発する時や受託開発で協力会社や外部の方に依頼した際に、オープンソースライセンスについてあまり意識したことがありませんでした。
実際に毎回確認するのは大変ですし、外注などを考えるとライセンス確認の自動化が望ましいと思います。
GitHub Actionsでの確認方法もありますが、プルリク前のcommit時点で分かった方がいいでしょう。
そこで、先日Ruffとmypyをコミット時に実行したpre-commitで一緒にライセンスの確認もしてしまおうと思います。

## 環境と前提情報
MacBookPro
macOS Sequoia15.0.1
uvで仮想環境を作っています。
python 3.13

pre-commitについては下記記事を参照してください。

https://qiita.com/The_Past_of_Dice/items/9c1f46a0ee7b5f04aa4c

## pip-licensesとpre-commitの設定

まずは、ライセンスの確認に必要なpip-licensesのインストールをします。
```bash
uv add pip-licenses

uv sync # 毎回同期を忘れない!
```
現在のライセンスはこんな感じ。
```bash
$ uv run pip-licenses
 Name          Version  License                              
 PyYAML        6.0.2    MIT License                          
 cfgv          3.4.0    MIT License                          
 distlib       0.3.9    Python Software Foundation License   
 filelock      3.16.1   The Unlicense (Unlicense)            
 identify      2.6.1    MIT License                          
 iniconfig     2.0.0    MIT License                          
 nodeenv       1.9.1    BSD License                          
 packaging     24.1     Apache Software License; BSD License 
 platformdirs  4.3.6    MIT License                          
 pluggy        1.5.0    MIT License                          
 pre_commit    4.0.1    MIT License                          
 pytest        8.3.3    MIT License                          
 virtualenv    20.27.0  MIT License
```

pip-licensesをインストールし、動作確認もできたのでyamlファイルを書き換え、pre-commit時にライセンス確認をするようにします。
以前、commit時にRuffとmypyをチェックするpre-commitを記載したものがありますが気にしないでください。


```yaml:.pre-commit-config.yaml
repos:
  - repo: https://github.com/charliermarsh/ruff-pre-commit
    rev: v0.0.280
    hooks:
      - id: ruff

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.4.1
    hooks:
      - id: mypy
        args: ["--strict"]

  - repo: local
    hooks:
      - id: check-licenses
        name: Check licenses
        entry: bash -c 'uv run pip-licenses --from=mixed --format=json | jq ".[] | select(.License | contains(\"GPL\"))" | tee /tmp/gpl_check_output && if [ -s /tmp/gpl_check_output ]; then echo "GPL license found!" && exit 1; else echo "No GPL licenses found" && exit 0; fi'
        language: system
        types_or: [file] # 何かしらのファイルに変更があった場合は毎回確認
```

これで基本的に作業は完了です。

### GPLライセンスでテストしてみる。

適当にいくつかのGPLライセンスのものを追加しました。

```bash
$ uv run pip-licenses
 Name          Version  License                                                 
 PyQt6         6.7.1    GPL v3                                                  
 PyQt6-Qt6     6.7.3    LGPL v3                                                 
 PyQt6_sip     13.8.0   SIP                                                     
 PyYAML        6.0.2    MIT License                                             
 cfgv          3.4.0    MIT License                                             
 distlib       0.3.9    Python Software Foundation License                      
 filelock      3.16.1   The Unlicense (Unlicense)                               
 gsl           0.0.3    GNU Affero General Public License v3 or later (AGPLv3+) 
 identify      2.6.1    MIT License                                             
 iniconfig     2.0.0    MIT License                                             
 nodeenv       1.9.1    BSD License                                             
 packaging     24.1     Apache Software License; BSD License                    
 platformdirs  4.3.6    MIT License                                             
 pluggy        1.5.0    MIT License                                             
 pre_commit    4.0.1    MIT License                                             
 pytest        8.3.3    MIT License                                             
 virtualenv    20.27.0  MIT License
 ```

実際にcommitしてみます。
 ```bash
$ git commit
ruff.................................................(no files to check)Skipped
mypy.................................................(no files to check)Skipped
Check licenses...........................................................Failed
- hook id: check-licenses
- exit code: 1

{
  "License": "GPL v3",
  "Name": "PyQt6",
  "Version": "6.7.1"
}
{
  "License": "LGPL v3",
  "Name": "PyQt6-Qt6",
  "Version": "6.7.3"
}
{
  "License": "GNU Affero General Public License v3 or later (AGPLv3+)",
  "Name": "gsl",
  "Version": "0.0.3"
}
 ```
無事、GPL系のライセンスに対してエラーを出してくれました。

対象するライセンスを増やしたい場合は、`|`で繋げて書いてください。
ちなみに部分一致なので、GPLを指定すればLGPLとかも引っかかってきます。
 ```yaml
entry: bash -c 'uv run pip-licenses --from=mixed --format=json | jq ".[] | select(.License | contains(\"GPL|AGPL|LGPL\"))" | tee /tmp/gpl_check_output && if [ -s /tmp/gpl_check_output ]; then echo "GPL license found!" && exit 1; else echo "No GPL licenses found" && exit 0; fi'
 ```

 ということで、ライセンス違反のライブラリを削除します。
 ```bash
 $ uv remove PyQt6
Resolved 20 packages in 136ms
Uninstalled 3 packages in 378ms
 - pyqt6==6.7.1
 - pyqt6-qt6==6.7.3
 - pyqt6-sip==13.8.0
MacBook-Pro-3:LearningPythonLab cocolo$ uv remove PyQt6-Qt6
error: The dependency `pyqt6-qt6` could not be found in `dependencies`
MacBook-Pro-3:LearningPythonLab cocolo$ uv remove gsl
Resolved 19 packages in 7ms
Uninstalled 1 package in 3ms
 - gsl==0.0.3
MacBook-Pro-3:LearningPythonLab cocolo$ uv sync
Resolved 19 packages in 0.66ms
Audited 17 packages in 0.02ms
MacBook-Pro-3:LearningPythonLab cocolo$ uv run pip-licenses
 Name          Version  License                              
 PyYAML        6.0.2    MIT License                          
 cfgv          3.4.0    MIT License                          
 distlib       0.3.9    Python Software Foundation License   
 filelock      3.16.1   The Unlicense (Unlicense)            
 identify      2.6.1    MIT License                          
 iniconfig     2.0.0    MIT License                          
 nodeenv       1.9.1    BSD License                          
 packaging     24.1     Apache Software License; BSD License 
 platformdirs  4.3.6    MIT License                          
 pluggy        1.5.0    MIT License                          
 pre_commit    4.0.1    MIT License                          
 pytest        8.3.3    MIT License                          
 virtualenv    20.27.0  MIT License
 ```

 もう一度コミットしてみます。
 ```bash
$ git commit
ruff.....................................................................Passed
mypy.....................................................................Passed
Check licenses...........................................................Passed
 ```

 無事全てのテストをクリアしました。

 ## まとめ

 とりあえず、pre-commitでコミット時にライセンスチェックを行えるようにしました。
 実際の運用では、これと合わせてGitHub Actionsも使ってダブルチェック体制を作るのだと思います。

 自分が発注者になったりするときは、この辺も気をつけたいですね。