|
| 1 | +--- |
| 2 | +title: "cargoプロジェクトで依存関係を継続的に管理する" |
| 3 | +tags: ["cargo", "rust", "OSS"] |
| 4 | + |
| 5 | +cover: "https://blog.kyu08.com/cover.png" |
| 6 | +description: "" |
| 7 | +date: 2025-05-20T14:22:40+09:00 |
| 8 | +author: "kyu08" |
| 9 | +authorTwitter: "kyu08_" |
| 10 | +draft: false |
| 11 | +showFullContent: false |
| 12 | +readingTime: true |
| 13 | +hideComments: false |
| 14 | +color: "" |
| 15 | +--- |
| 16 | + |
| 17 | +OSSに依存するプロジェクトでは依存しているライブラリのライセンス表記が必要になることがある。(体感的にはほとんどのライブラリはライセンス表記を求めているように思う) |
| 18 | + |
| 19 | +そのため、依存ライブラリが増えた場合にはライセンス表記を更新する必要があるが、依存ライブラリの依存ライブラリに対しても再帰的にライセンス表記を見に行って自リポジトリの依存ライセンス表記ファイルを更新して...という作業を行うのは現実的ではない。 |
| 20 | + |
| 21 | +他にもうっかりコピーレフトなライブラリに依存してしまうとプロジェクトのライセンスを変更しなければならなかったりと案外注意すべきポイントが多い。 |
| 22 | + |
| 23 | +cargoプロジェクトにおいてこれらの労力を削減してくれるツールがいくつか存在している。 |
| 24 | + |
| 25 | +筆者は趣味プロジェクトとして`fzf-make`というmake targetやpnpm script, yarn script, just recipeをfuzzy finder形式で選択、実行できるCLIツール[^1]をRustで開発しており、このプロジェクトで最近それらのツールを導入したので使い方とともに紹介する。 |
| 26 | + |
| 27 | +## tl;dr |
| 28 | +以下のチェックをCIで実行することでcargoプロジェクトの依存関係を継続的に省力で管理できる。 |
| 29 | + |
| 30 | +- [`cargo-machete`](https://github.com/bnjbvr/cargo-machete): 不要な依存関係がないか |
| 31 | +- [`cargo-deny`](https://github.com/EmbarkStudios/cargo-deny): 依存ライブラリに許容しないライセンスが含まれていないか |
| 32 | +- [`cargo-about`](https://github.com/EmbarkStudios/cargo-about): リポジトリのライセンス表記ファイルに更新漏れがないか |
| 33 | + |
| 34 | +それぞれ紹介する。なお、コード例はそれぞれ以下のランナーを想定している。 |
| 35 | + |
| 36 | +| 環境 | ランナー | |
| 37 | +|-- | -- | |
| 38 | +| ローカル | make | |
| 39 | +| CI | GitHub Actions | |
| 40 | + |
| 41 | +## `cargo-machete`で不要な依存関係を検出する |
| 42 | +### why |
| 43 | +ある程度の期間開発を続けていると以前追加した依存関係が不要になったことに気付かずに`cargo.toml`の`dependencies`や`dev-dependencies`に残ったままになることがある。 |
| 44 | +不要な依存関係が残ったままになっているとrenovateやdependabotによるバージョン更新の手間が不必要に増えてしまうのでPRの時点で気付けるようにしたい。 |
| 45 | + |
| 46 | +### what |
| 47 | +cargo-macheteを使って`cargo.toml`に記述されている依存関係のうち、プロジェクトが実際には依存していない依存関係を検出する。 |
| 48 | + |
| 49 | +### how(ローカル) |
| 50 | +以下のようなmake targetを定義しておくと`make detect-unused-dependencies`で不要な依存関係を検出できる。 |
| 51 | +```makefile |
| 52 | +.PHONY: tool-detect-unused-dependencies |
| 53 | +tool-detect-unused-dependencies: |
| 54 | + @if ! which cargo-machete > /dev/null; then \ |
| 55 | + cargo install --locked cargo-machete; \ |
| 56 | + fi |
| 57 | + |
| 58 | +.PHONY: detect-unused-dependencies |
| 59 | +detect-unused-dependencies: tool-detect-unused-dependencies |
| 60 | + cargo machete |
| 61 | +``` |
| 62 | + |
| 63 | +以下は不必要な依存関係として`tempfile`が検出されている様子。 |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +### how(CI) |
| 68 | +以下のようなyamlを定義しておくと不要な依存関係がある場合にCIが落ちてくれる。 |
| 69 | +```yaml |
| 70 | +name: Detect unused dependencies |
| 71 | +on: |
| 72 | + push: |
| 73 | + branches: [ "main" ] |
| 74 | + pull_request: |
| 75 | + |
| 76 | +jobs: |
| 77 | + detect-unused-dependencies: |
| 78 | + runs-on: ubuntu-latest |
| 79 | + steps: |
| 80 | + - name: Checkout |
| 81 | + uses: actions/checkout@v4 |
| 82 | + - name: Machete |
| 83 | + uses: bnjbvr/cargo-machete@v0.8.0 |
| 84 | +``` |
| 85 | + |
| 86 | +## `cargo-deny`で依存ライブラリに許容しないライセンスが含まれていないか検証する |
| 87 | +### why |
| 88 | +筆者は`fzf-make`をより自由に利用してほしいと考えているためMITライセンスでプロジェクトを公開したい。 |
| 89 | +コピーレフトのライセンスに依存してしまうとライセンス変更を余儀なくされるため、コピーレフトのライブラリに依存することを避けたい。 |
| 90 | + |
| 91 | +### what |
| 92 | +`cargo-deny`は依存関係のlint機能を提供する。`fzf-make`では、`cargo deny check licenses`を用いてホワイトリストに登録したライセンス以外のライセンスが依存ライブラリに含まれていないか検証している。 |
| 93 | + |
| 94 | +```toml |
| 95 | +# deny.toml |
| 96 | +[licenses] |
| 97 | +allow = [ |
| 98 | + "MIT", |
| 99 | + "ISC", |
| 100 | + "Apache-2.0", |
| 101 | + "MPL-2.0", |
| 102 | + "BSD-2-Clause", |
| 103 | + "BSD-3-Clause", |
| 104 | + "Unicode-DFS-2016", |
| 105 | + "OpenSSL", |
| 106 | +] |
| 107 | +confidence-threshold = 0.8 |
| 108 | +exceptions = [] |
| 109 | + |
| 110 | +[[licenses.clarify]] |
| 111 | +name = "ring" |
| 112 | +expression = "MIT AND ISC AND OpenSSL" |
| 113 | +license-files = [ |
| 114 | + { path = "LICENSE", hash = 0xbd0eed23 } |
| 115 | +] |
| 116 | +``` |
| 117 | + |
| 118 | +### how(ローカル) |
| 119 | +以下のようなmake targetを定義しておくと`make check-licenses`で依存ライブラリに許容しないライセンスが含まれていないかを検証できる。 |
| 120 | +```makefile |
| 121 | +.PHONY: tool-check-licenses |
| 122 | +tool-check-licenses: |
| 123 | + @if ! which cargo-deny > /dev/null; then \ |
| 124 | + cargo install --locked cargo-deny; \ |
| 125 | + fi |
| 126 | + |
| 127 | +.PHONY: check-licenses |
| 128 | +check-licenses: tool-check-licenses |
| 129 | + cargo deny check licenses |
| 130 | +``` |
| 131 | + |
| 132 | +許可していないライセンスのライブラリに依存していることがわかると以下のようなエラーが出力される。 |
| 133 | + |
| 134 | + |
| 135 | + |
| 136 | +### how(CI) |
| 137 | +以下のようなyamlを定義しておくと不要な依存関係があるとCIが落ちてくれる。 |
| 138 | +```yaml |
| 139 | +name: Check licenses |
| 140 | +on: |
| 141 | + push: |
| 142 | + branches: [ "main" ] |
| 143 | + pull_request: |
| 144 | + |
| 145 | +jobs: |
| 146 | + check-licenses: |
| 147 | + runs-on: ubuntu-latest |
| 148 | + steps: |
| 149 | + - uses: actions/checkout@v4 |
| 150 | + - uses: EmbarkStudios/cargo-deny-action@v2 |
| 151 | + with: |
| 152 | + command: check licenses |
| 153 | +``` |
| 154 | + |
| 155 | +## `cargo-about`でリポジトリのライセンス表記が最新か検証する |
| 156 | +### why |
| 157 | +冒頭でも触れた通り、多くのOSSでは依存ライブラリのライセンス表記を求めている。 |
| 158 | + |
| 159 | +`fzf-make`では`CREDITS.html`というファイルを用意しておりそこに依存ライブラリのライセンス表記を記載している。 |
| 160 | + |
| 161 | +これまでは依存ライブラリを追加/削除した際に手作業でライセンス表記ファイルを更新していたが、漏れがちになっていた。 |
| 162 | + |
| 163 | +また、本来は依存ライブラリの依存ライブラリを再帰的にチェックしてすべてのライセンス表記をすべきなのだと思うが、手作業でそれをする気にはなれなかったので直接依存しているライブラリのライセンスしか表記できていなかった。 |
| 164 | + |
| 165 | +これらの課題を`cargo-about`を用いることで解決できそうだったので導入してみた。 |
| 166 | + |
| 167 | +### what |
| 168 | +`cargo-about`は依存ライブラリのライセンス表記ファイルを自動生成する機能を提供する。 |
| 169 | + |
| 170 | +以下のようなテンプレートを用意し、`cargo about generate about.hbs > CREDITS.html`を実行すると`CREDITS.html`にライセンス表記を出力してくれる。 |
| 171 | + |
| 172 | +```html |
| 173 | +<html> |
| 174 | + <head> |
| 175 | + <style> |
| 176 | + @media (prefers-color-scheme: dark) { |
| 177 | + body { |
| 178 | + background: #333; |
| 179 | + color: white; |
| 180 | + } |
| 181 | + a { |
| 182 | + color: skyblue; |
| 183 | + } |
| 184 | + } |
| 185 | + .container { |
| 186 | + font-family: sans-serif; |
| 187 | + max-width: 800px; |
| 188 | + margin: 0 auto; |
| 189 | + } |
| 190 | + .intro { |
| 191 | + text-align: center; |
| 192 | + } |
| 193 | + .licenses-list { |
| 194 | + list-style-type: none; |
| 195 | + margin: 0; |
| 196 | + padding: 0; |
| 197 | + } |
| 198 | + .license-used-by { |
| 199 | + margin-top: -10px; |
| 200 | + } |
| 201 | + .license-text { |
| 202 | + max-height: 200px; |
| 203 | + overflow-y: scroll; |
| 204 | + white-space: pre-wrap; |
| 205 | + } |
| 206 | + </style> |
| 207 | + </head> |
| 208 | + <body> |
| 209 | + <main class="container"> |
| 210 | + <div class="intro"> |
| 211 | + <h1>Third Party Licenses</h1> |
| 212 | + <p>This page lists the licenses of the projects used in fzf-make.</p> |
| 213 | + </div> |
| 214 | + |
| 215 | + <h2>Overview of licenses:</h2> |
| 216 | + <ul class="licenses-overview"> |
| 217 | + {{#each overview}} |
| 218 | + <li><a href="#{{id}}">{{name}}</a> ({{count}})</li> |
| 219 | + {{/each}} |
| 220 | + </ul> |
| 221 | + |
| 222 | + <h2>All license text:</h2> |
| 223 | + <ul class="licenses-list"> |
| 224 | + {{#each licenses}} |
| 225 | + <li class="license"> |
| 226 | + <h3 id="{{id}}">{{name}}</h3> |
| 227 | + <h4>Used by:</h4> |
| 228 | + <ul class="license-used-by"> |
| 229 | + {{#each used_by}} |
| 230 | + <li><a href="{{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}">{{crate.name}} {{crate.version}}</a></li> |
| 231 | + {{/each}} |
| 232 | + </ul> |
| 233 | + <pre class="license-text">{{text}}</pre> |
| 234 | + </li> |
| 235 | + {{/each}} |
| 236 | + </ul> |
| 237 | + </main> |
| 238 | + </body> |
| 239 | +</html> |
| 240 | +``` |
| 241 | + |
| 242 | + |
| 243 | +### how(ローカル) |
| 244 | +以下のようなmake targetを定義しておくと`make update-license-file`で依存ライブラリのライセンス表記が最新か検証できる。 |
| 245 | +```makefile |
| 246 | +.PHONY: tool-update-license-file |
| 247 | +tool-update-license-file: |
| 248 | + @if ! which cargo-about > /dev/null; then \ |
| 249 | + cargo install --locked cargo-about; \ |
| 250 | + fi |
| 251 | + |
| 252 | +.PHONY: update-license-file |
| 253 | +update-license-file: tool-update-license-file |
| 254 | + cargo about generate about.hbs > CREDITS.html |
| 255 | +``` |
| 256 | + |
| 257 | +### how(CI) |
| 258 | +以下のようなyamlを定義しておくと依存ライブラリのライセンス表記が最新でないとCIが落ちてくれる。 |
| 259 | +```yaml |
| 260 | +name: Check license file |
| 261 | +on: |
| 262 | + push: |
| 263 | + branches: [ "main" ] |
| 264 | + pull_request: |
| 265 | + |
| 266 | +jobs: |
| 267 | + check-license-file: |
| 268 | + runs-on: ubuntu-latest |
| 269 | + steps: |
| 270 | + - name: Checkout |
| 271 | + uses: actions/checkout@v4 |
| 272 | + - name: Check |
| 273 | + run: make update-license-file |
| 274 | + - name: Check diff |
| 275 | + run: | |
| 276 | + git add . |
| 277 | + git diff --cached --exit-code |
| 278 | +``` |
| 279 | + |
| 280 | +なお、デフォルトだと以下の部分の`This page...fzf-make`の部分が`This page lists the licenses of the projects used in cargo-about.`として出力される。そのため、そのまま利用すると「`cargo-about`プロジェクトでは以下のライセンスに依存しています」という意味の表示になってしまうためユーザーが忘れずに`cargo-about`の部分をプロジェクト名に変更する必要があるという問題がある。[^2] |
| 281 | + |
| 282 | +```html |
| 283 | +<div class="intro"> |
| 284 | + <h1>Third Party Licenses</h1> |
| 285 | + <p>This page lists the licenses of the projects used in fzf-make.</p> |
| 286 | +</div> |
| 287 | +``` |
| 288 | + |
| 289 | +これに関しては以下のissueを立ててみたので`cargo-about`側と意見が一致したら修正するPRを送ってみようと思う。 |
| 290 | + |
| 291 | +[[Feature request] change `cargo about init` to generate the statement including user's project name.](https://github.com/EmbarkStudios/cargo-about/issues/281) |
| 292 | + |
| 293 | +## おわりに |
| 294 | +繰り返しの定型タスクをCIに任せることができたので開発により集中できるようになった。(趣味開発だと数ヶ月ぶりにコミットするとかもザラにあり、開発に必要なタスクを忘れがちなのでそういった意味でも自動化の範囲を増やすことができてよかった) |
| 295 | + |
| 296 | +便利ツールをOSSとして公開してくれている人/企業に感謝しつつ利用するだけでなく自分にできる貢献はやっていきたい。 |
| 297 | + |
| 298 | +[^1]: `fzf-make`の紹介記事: [[make,pnpm,yarn,justに対応]コマンドをfuzzy finder形式で選択できるCLIツール fzf-makeの紹介](https://zenn.dev/kyu08/articles/974fd8bc25c303) |
| 299 | +[^2]: そして実際に[90件近くのプロジェクトで`cargo-about`のままになっている。](https://github.com/search?q=%22This+page+lists+the+licenses+of+the+projects+used+in+cargo-about.%22&type=code) |
0 commit comments