TECH PLAY

Dart

イベント

該当するコンテンツが見つかりませんでした

マガジン

該当するコンテンツが見つかりませんでした

技術ブログ

はじめに Dart で == を使ったとき、「同じ値なのに false になる」という経験はないでしょうか。 この記事を書こうと思ったきっかけは、LinkedHashMap を Equatable パッケージで等価比較しようとしたときの出来事でした。LinkedHashMap は挿入順序を保持するデータ構造なので、当然順序も含めて比較してくれるだろうと思っていたのですが、実際には順序が無視されて等しいと判定されてしまいました。なぜこうなるのかを調べていくうちに、Dart の等価性の仕組み全体に興味を持ち、さまざまなデータ構造について網羅的に検証してみることにしました。 本記事では、クラス・コレ…
Flutter SDK 3.29 → 3.38 へのアップグレード中に遭遇した retrofit / analyzer / custom_lint の依存衝突を解いた記録です。 「なぜ pub solver が答えを見つけられないのか」から順を追って説明します。 はじめに はじめまして、KINTOテクノロジーズ(KTC)でモバイルアプリ(Flutter)の開発を担当しているHand-Tomiです。 Flutter SDK のメジャーアップグレードを進めていたある日、 dart pub get が突然失敗するようになりました。エラーメッセージを読み解くと、 retrofit_generator と custom_lint がそれぞれ別々の analyzer バージョンを要求していて、両者が要求する analyzer のバージョン差はわずか 1 パッチ。けれど pub solver ではどうやっても解けない デッドロック でした。 本記事では、その原因と解決方法、そしてなぜ dependency_overrides が罠になるのかを順を追って解説します。同じ Flutter プロジェクトで似た衝突に遭遇した方の参考になれば幸いです。 :::message pub solver は dart pub get の内部で動く依存解決エンジンです。すべての制約を同時に満たすバージョンの組み合わせを探すのが役割で、本記事ではこの後も繰り返し登場します。 ::: :::message バージョン管理でよく耳にする SemVer (Semantic Versioning) は、バージョン番号を MAJOR.MINOR.PATCH の 3 桁で表す規約です。本記事では以降、それぞれ メジャー / マイナー / パッチ と表記します。 MAJOR (メジャー):互換性のない変更(壊れる) MINOR (マイナー):後方互換のある機能追加 PATCH (パッチ):後方互換のあるバグ修正 たとえば analyzer 8.4.0 → 8.4.1 は パッチ リリースなので、本来なら「コードを変えずに上げても安全」なはずです。本記事の 3 節で、この「はず」が崩れる仕組みを掘り下げます。 ::: TL;DR Flutter のメジャーアップグレード中に dart pub get が失敗。原因は retrofit_generator と custom_lint_visitor が同じ analyzer に対して別々のバージョンを要求していたこと。 最終的な解: retrofit: ^4.9.2 + retrofit_generator: ^10.2.1 。 pubspec のピン 2 行ですっきり解決します。 dependency_overrides には罠があり、推奨しません。 pub get は通っても dart_style が知らぬ間に昇格してビルドが壊れます。 この衝突は 構造的な問題 です。 analyzer のメジャーが上がるたびに再発します。 1. 始まり — 止まってしまったビルド Flutter SDK 3.29.2 から 3.38.10 へのメジャーアップグレードを進めていました。 flutter_riverpod 2 → 3、 freezed 2 → 3、 analyzer 6 → 8 といった大きな変更が立て続けに来ていて、いつもなら flutter upgrade のあと dart pub get で済む作業のはずでした。 ところがビルドが止まりました。要点だけ抜き出すと、こういうメッセージです。 And because retrofit_generator >=10.2.4 depends on analyzer >=8.4.1 <13.0.0 and custom_lint_core >=0.7.0 depends on custom_lint_visitor ^1.0.0, if retrofit_generator >=10.2.4 and custom_lint_core >=0.7.0 then analyzer 9.0.0. And because custom_lint >=0.8.1 depends on both analyzer ^8.0.0 and custom_lint_core 0.8.1, custom_lint >=0.8.1 is incompatible with retrofit_generator >=10.2.4. So, because app depends on both retrofit_generator ^10.2.5 and custom_lint ^0.8.1, version solving failed. pub solver が答えを見つけられなかったのです。片方を上げればもう片方が壊れ、下げればまた別のところが壊れる。普通のバージョン衝突ではなく、 デッドロック でした。 2. 誰と誰が戦っているのか 主な登場人物は次のとおりです。 analyzer — Dart コードの静的解析エンジン(共有資源) retrofit_generator — .g.dart を生成するコードジェネレータ custom_lint / custom_lint_core / custom_lint_builder — lint プラグインのランナーと、その builder custom_lint_visitor — analyzer の AST を訪問する visitor 実装 問題の核心は、 analyzer という共有資源 です。両陣営が同じ analyzer に対して別々のバージョンを要求しています。 retrofit_generator 10.2.3+ → 「 analyzer 8.4.1 以上が必要」 custom_lint_visitor 1.0.0+8.4.0 → 「 analyzer 8.4.0 ちょうど」 差は 8.4.0 と 8.4.1、 わずか 1 パッチ 。これだけでビルドが止まるのです。 なお、1 節のエラーメッセージ末尾には analyzer 9.0.0 も登場しますが、これは custom_lint_visitor に 1.0.0+8.4.0 のほかに 1.0.0+9.0.0 ビルドも存在し、 custom_lint_visitor: ^1.0.0 を介した solver が両方を順に試した結果です。どちらも analyzer をバージョン固定で要求する点は同じなので、本質的な対立点は変わりません。 3. なぜ 1 パッチ差で壊れるのか ここで 2 つの事実が噛み合います。 事実 1. analyzer の 内部 API はパッチリリースでも変わる SemVer の約束は「パッチリリースでは 公開 API は後方互換 を保つ」です。ところが custom_lint_visitor が使っているのは analyzer の公開 API ではなく、 内部 API (AST ノードの型など、パッケージの内部実装に属するもの)です。SemVer の保護範囲外なので、メジャー・パッチを問わず、型が消えたり、シグネチャが変わったりするのは珍しくありません。 後ほど引用するメンテナ自身の言葉を借りれば "some more unique APIs" — SemVer の通常のセーフティネットの外側で扱う必要のある API です。本記事の 5 節で扱う dart_style 3.1.9 の LabelReference / NamedArgument 欠落も、「内部 API は SemVer 保護外」という同じ構造から生じる事例の 1 つです(こちらは analyzer のメジャー間で起きたケースで、3 節でいうパッチ単位の例ではありません)。 事実 2. custom_lint_visitor はそれゆえ バージョンを完全に固定 する これを知っているからこそ、 custom_lint_visitor のメンテナは意図的に analyzer を完全に固定しています。パッケージ名そのものがその証拠です。 custom_lint_visitor 1.0.0+8.4.0 ^^^^^ analyzer のバージョン pubspec.yaml の中でも analyzer: 8.4.0 ( ^ (caret) なしのバージョン固定)になっています。 これがミスならば PR 一本で解決する話ですが、これは 関連する GitHub issue でメンテナ自身が明言した 意図的な方針 です。 "Custom_lint depends on some more unique APIs. I'll probably stick to requiring 8.0 for it." — invertase/dart_custom_lint#345 発言の直接の意図は「メジャー( 8.0 )単位で範囲を狭めて require する」ですが、その方針が実際のリリースにも反映されており、リリースされる custom_lint_visitor の各バージョンでは analyzer: 8.4.0 のように バージョンが完全に固定 されています( 1.0.0+8.4.0 → analyzer: 8.4.0 、 ^ (caret) なし)。つまり 意図された決定 の結果としてバージョン固定が生まれており、両者が同じ analyzer バージョンを要求するビルドが揃うまでは pub solver だけでは解けません。 4. 効果のなかった試みリスト 問題が難しく見えると、人は迂回路を探したくなります。しかし直感的に思いつく次の試みはどれも徒労でした。 試み なぜ失敗するのか retrofit_generator を最新(10.2.5)に上げる analyzer 8.4.1 を要求するため custom_lint_visitor と衝突 retrofit の上限を狭めてみる( <4.9.1 ) generator 10.2.1 を選びたい動機は 6 節で詳述しますが、retrofit を 4.9.0 系に下げると今度は generator 10.2.1 のソースが retrofit 4.9.2 の新 enum 値( Parser.DartMappable )を参照しているため、generator 自体の AOT コンパイルが Member not found で失敗します custom_lint_builder のダウングレード analyzer のメジャーが 7.x まで引きずり下ろされ、今度は retrofit_generator (analyzer 8.x 依存)と別の衝突を起こす — freezed / riverpod など analyzer 8 に依存するパッケージがある環境でも同様 analysis_options.yaml の lint を切る ( dependency_overrides で solver を通した後でも) lint を切って exclude: '**/*.g.dart' を両方適用しても、generator AOT 段階で発生する Member not found 系のコンパイルエラーはそのまま発生する dependency_overrides で強制固定 pub get は通るがビルド段階で dart_style が壊れる(5 節を参照) 特に最後の項目、 dependency_overrides は罠が深いので、別途取り上げる価値があります。 上の表の各行は、本記事と同じリポジトリの検証成果物( reports/01-reproduction.md 、 reports/03-overrides-fallback.md )で実際のコマンド出力として再現されています。 5. dependency_overrides という罠 最初の発想は単純です。「2 つのパッケージが争うなら、こちらで強制的に片方のバージョンを打ち込もう」。 dependency_overrides: retrofit: ^4.9.2 retrofit_generator: ^10.2.5 analyzer: ^8.4.1 驚くことに dart pub get は通ります。なぜなら dependency_overrides は オーバーライドした依存に対する他パッケージからの制約を黙らせ 、solver の選択肢を広げるからです。 ところが dart run build_runner build の段階で、突然ビルドが壊れます。 Failed to build build_runner:build_runner: .../dart_style-3.1.9/lib/src/front_end/ast_node_visitor.dart:1279:28: Error: Type 'LabelReference' not found. dart_style です。私たちが明示的に依存もしていないパッケージです。 理由を辿ってみると、次のようになっています。 dependency_overrides がオーバーライドした依存( retrofit 、 retrofit_generator 、 analyzer )に対する他パッケージからの制約を黙らせ、solver の選択肢が広がる その結果、solver は transitive で dart_style の最新版( 3.1.9 )を自動的に選ぶ dart_style 3.1.9 は analyzer の最新メジャーで導入された AST 型( LabelReference 、 NamedArgument 、 BlockEnumBody など)を参照している しかし私たちは override で analyzer ^8.4.1 (解決範囲は >=8.4.1 <9.0.0 )を強制している → その範囲には存在しない型を参照しようとしてコンパイル失敗 要するに dependency_overrides は制約を黙らせるだけで、互換性を保証しません。 一箇所を押さえるとまた別の場所から噴き出します。これを抑え込もうとすると dart_style もピン、 custom_lint_visitor も確認…… と際限なく増えていきます。 6. 結局解けた方法 — シンプルなピン調整 2 行 問題を逆から見ると答えが見えます。 私たちが変えられないもの: custom_lint_visitor 1.0.0+8.4.0 → analyzer 8.4.0 (正確には custom_lint_visitor 自体は 1.0.0+9.0.0 ビルドも存在しますが、それを選ぶと custom_lint 本体が要求する analyzer ^8.0.0 と衝突するため、 custom_lint を使う限り 8.4.0 ピン側に寄せるしかありません。1 節のエラーメッセージにも custom_lint >=0.8.1 depends on ... analyzer ^8.0.0 として現れています) 私たちが変えられるもの: retrofit_generator のバージョン であれば「 analyzer 8.4.0 でも動く最新の retrofit_generator 」を探せばよいわけです。 retrofit_generator のバージョン別要求を表にまとめると: retrofit_generator analyzer 要求 logError の呼び出し形式 10.2.0 >=7.7.1 <10.0.0 positional 4 個 10.2.1 >=8.0.0 <10.0.0 named ( response: _result ) 10.2.3 >=8.4.1 <11.0.0 named 10.2.4 / 10.2.5 >=8.4.1 <13.0.0 named 補足: retrofit.dart は monorepo で、 retrofit_generator (タグ v10.x.x )と retrofit (タグ retrofit-vX.Y.Z )を別系統で管理しています。本記事のリンクで prefix が混在するのはそのためです。なお 10.2.2 はリリースが存在しますが、本記事の議論には影響しないため上の表では省略しています。 答えが見えます。 10.2.1 です。 analyzer 8.4.0 と互換 ✓( >=8.0.0 なので) retrofit 4.9.2 の {Response? response} named optional シグネチャと互換 ✓ dependency_overrides 不要 ✓ dependencies: retrofit: ^4.9.2 dev_dependencies: retrofit_generator: ^10.2.1 これだけです。 ^10.2.1 というキャレット範囲を書いても、10.2.3+ は analyzer 8.4.1 を要求してくるので自動的に候補から外れ、実効的に 10.2.1 が選ばれます。 ちなみに retrofit_generator 10.2.1 と 10.2.5 の logError の呼び出しシグネチャは同一 です。generator のソース( lib/src/generator.dart )を直接比較しても、両バージョンとも '$_errorLoggerVar?.logError(e, s, $_optionsVar, response: $_resultVar);' という同一の出力テンプレートを使っています( v10.2.1#L3777 / v10.2.5#L3849 )。10.2.5 には Stream<Uint8List> / Stream<String> 処理の検証など別の機能が追加されていますが、本記事が扱う retrofit ↔ analyzer インターフェイスそのものは変更されていません。つまり 10.2.1 に留まることは、コア機能面で損ではありません。 7. それで私たちが学んだこと この件が片付いたとき、最初に浮かんだ考えは 「次のメジャーアップグレードでまた出くわすだろうな」 でした。 理由は 2 つです。 analyzer の内部 API は今後もパッチで変わり続ける。 それが AST を扱う解析器パッケージの本質です。 custom_lint_visitor は今後もバージョンを完全に固定し続ける。 メンテナが意図的に取っている方針だからです。 つまりこの衝突は 構造的 です。本記事を書いている 2026 年春の時点で、 analyzer はすでに 13.0.0 までリリースされており、 custom_lint_visitor のピンラインは 1.0.0+9.0.0 までしか追いついていません。 custom_lint_visitor がメジャーごとに 1 〜 2 個のビルドだけ追いつくこのまばらなパターンが続く限り、 analyzer がさらに一段上がるたびに同じ形で再発します。実際、 retrofit.dart の issue tracker を見ると analyzer 10.0 の段階でも同じシグネチャミスマッチが報告されています。 であれば、私たちにできることは: 自然な解決を先に試す。 ピン 1 つの調整で解けるかをまず確認する。シンプルな答えがあるのに dependency_overrides を最初に持ち出さない。 dependency_overrides は最後の手段。 黙らせるだけでは解決にならない。一箇所を押さえると別の場所から噴き出す。 プレイブックを残す。 次の人(あるいは 6 か月後の自分)が同じ罠にはまらないように。メカニズムと意思決定ツリーを一緒に書き残しておく(本記事自体がそのプレイブックの 1 つです)。 最後に ここまで読んでいただき、ありがとうございます。 analyzer のような共有依存をめぐる衝突は、一見すると「2 つのパッケージのバグ」に見えますが、実際にはエコシステム側の構造的な制約が背景にあります。同じ罠に出会ったときに「最初に何を疑い、何を試し、どこで止まるか」を整理できれば、次は数時間で解けるはずです。 皆さんの参考になれば幸いです。 参考 dart_custom_lint #345 — Support analyzer 8 retrofit.dart #911 — analyzer 10.0.0+ compatibility pub.dev — retrofit_generator バージョン別依存関係 retrofit 4.9.2 — Parser.DartMappable enum 追加箇所
はじめに 2026年3月に Genkit for Dart が Preview 版として公開され、Flutter / Dart だけで AI ワークフローを組み立てる選択肢が現実的になりました。 https://github.com/genkit-ai/genkit-dart 本記事では、Google の AI フレームワークである Genkit の Dart 版を主題として、実際のサンプルアプリを通じて、構造化出力、Tool、Flow といったコア機能を整理します。 そのうえで、ローカル実行だけではなく、実際に Google Cloud 上で動かす手順も見ていきます。 Google

動画

該当するコンテンツが見つかりませんでした

書籍