序文の代わりに
次の土曜日、 EvgenyBorisovと私はJUG.ru でサンクトペテルブルクで公演します。 楽しいスラッシュと興味深い情報がたくさんあり(境界線がどこに行くのかわからないこともあります)、私のスピーチの1つは、WTFスタイルのモジュール式プログラム開発に専念します。 いくつかのホラー映画を紹介します。そのうちの1つは、プロジェクト内の依存関係を誰もが素早く、柔軟かつ正しく説明しようとする方法と、通常は何が起こるかについてです。 面白い? それから地獄へようこそ!

むしろ、「良い、快適で、WTF番目」です。
ちょっとした理論...
依存関係マネージャーとは何ですか?なぜ必要なのですか?
最新のビルドツール(特にJVMの世界)には、別名依存関係マネージャーが含まれています(または、簡単に接続できます)。 もちろん最も有名なのは、 Apache Maven 、 Apache Ivy (Apache Antの依存関係マネージャー)、 Gradleです。
JVMの世界におけるビルドツールの主な機能の1つは、クラスパスを作成することです。 これらは、コードをコンパイルし、テストを実行し、アーカイブ(war、ears、または配布物とインストーラー)をビルドし、さらにIDEでプロジェクトをセットアップするために、アセンブリプロセスで多く使用されます。
ネットワークの検索、依存関係のダウンロード、保存、構成のプロセスを容易にするために、依存関係マネージャーがあります。 たとえば
commons-lang
やvoilaなどが必要だと宣言します。
推移的依存関係とは何ですか?
推移的依存関係は、プロジェクトの直接依存関係が依存する成果物です。 次の状況を想像してください。

プロジェクト
A
は、
E
と
B
2つのアーティファクトに依存しています
B
これで、直接依存関係が終了し、 推移的 (
C, D
)が開始します。 その結果、アーティファクトを繰り返すことができる依存関係のチェーンを取得します(この例では
D
)
なぜ推移的な依存関係が必要なのですか?
明らかにコンパイル用ではありません。 しかし、他のすべてのために、おそらく、それらが必要です-これらのアーティファクトは、アーカイブアセンブリ内にある必要があり、テストを実行するためのクラスパスにある必要があり、IDEへの統合のためにAPIを介してそれらのパスを指定する必要があります
対立はどのように形成されますか?
上の図を見ると、まさに矛盾が見られます。 プロジェクト
A
のクラスパスには、アーティファクト
D
バージョン1(Eに依存)とアーティファクト
D
バージョン2(
C
依存)の両方が含まれている必要があります!
なぜこれが悪いのですか?
JVM(およびjavac)は、名前(およびクラスローダー、ただしこの単純な例では、すべてのクラスが1つのクラスローダーでロードされる)によってクラスの一意性を決定します。 クラスパスに同じ名前の2つのクラスが見つかった場合、最初のクラスのみがロードされます。
D1
と
D2
同じ名前のクラス
D2
と仮定すると(おそらく同意する必要があります)、生成されたクラスパスに登録されるjarのクラスは、2番目のクラスではロードされません。 どちらが最初ですか? それは依存関係マネージャーのロジックに依存し、一般的には不明です。
ご存じのとおり、これは競合です。

どうする
誰が責任を負うのかは明らかです(Java、あなたが考えたものではありません)が、何ができるでしょうか?
推移的な依存関係の競合を解決するための戦略はいくつかあります(それらの一部は論理的であり、他は不合理です)が、もちろん、特効薬はありません。 それらのいくつかを見てみましょう:
- 最新 。 最新の戦略は、下位互換性を意味します。 D2がD1と完全に互換性がある場合、クラスパスに新しいアーティファクト(D2)のみを残すと、Cの正しい操作(結局、D2の下に記述されます)が得られますが、Eの正しい操作も得られます(D2に下位互換性がある場合、そのように動作します) E)の下に記述されているD1と同じです。 この戦略は、最新バージョンと最新バージョンの2つの亜種です。 ほとんどの場合、同じように機能します(そうでない場合を除く)。
この例の場合、クラスパスで最新のものを使用するとD2になります。 - 失敗 (別名、厳密)。 この戦略では、依存関係マネージャーが競合を検出すると、アセンブリがクラッシュします。 当然のことながら、最も安全ですが、最も時間のかかる戦略です。
この例の場合、failを使用すると、アセンブリは失敗します。 - すべて (別名競合なし)。 「それと、別の、そしてパンがなくても可能です」とは、この例のD1とD2の両方が(任意の順序で)クラスパスに表示されることを意味します。 地獄? 地獄! ただし、クラスパス分離テクノロジーを使用する場合(異なるクラスローダーで異なるモジュールをロードすることにより)、それは有用であるだけでなく、必要になる可能性があります。
この例の場合、クラスパスでallを使用すると、D1とD2の両方になります。 - 最も近い 。 「最も近い」戦略は、完全かつ完全に素晴らしいWTFです。これについては、以下で説明します。 お楽しみに。
- カスタム この場合、依存関係マネージャーは、マスターのデザインを尋ねます。 もちろん、これは「手動制御」ですが、時には非常に役立つこともあります。 擬似溝の擬似コードの例を次に示します。
coflictManager = {artifact, versionA, versionB -> //, Apache, if(artifact.org.startsWith ('org.apache')){ [versionA, versionB].max() } else { fail() } }
この例の場合、このカスタム実装を使用するときに、D1とD2のorgが「org.apache」で始まると仮定すると、D2がクラスパスに表示されます。そうでない場合、アセンブリは失敗します。
誰が何ですか?
では、前述のどのDer Grosse Troikaができるか見てみましょう。
アパッチアイビー
紛争管理者の面では、アイビーは素晴らしいです。 それらはプラグインであり、両方のオプションが最新であり、同様に失敗し、すべてが箱に入っています。 カスタムでは、すべても美しいです。 ロジックを完全に実装することも、半製品を使用して適切な正規表現を作成することもできます。 デフォルトでは、最新版が実行されています(バージョン管理されています)。
グラドル
Gradleの最初のバージョン(0.6より前、私の記憶が正しければ)Ivyは依存関係マネージャーとして使用されました。 したがって、上記のすべてはGradleにも当てはまりますが、Gradlewareのメンバーは依存関係マネージャーを作成しました(主にGradleの主な利点の1つである並列アセンブリ中のIvyローカルストレージの問題による)。 マネージャーをリリースする過程で、競合マネージャーを置き換えるなどの「マイナーな」機能がロードマップに深く入り込んでおり、かなり長い間、Gradleは最新のもののみで存在していました。 私は最新のものが好きではありません-推移的な依存関係をオフにして、先に進み、すべてを手動でリストします。 しかし、今日はすべて順調です。 1.0からはfailがあり、1.4ではcustomもあります。
Apache Maven
どのバージョンのDがクラスパスに分類されると思いますか? D1? D2? 両方? じゃない? アセンブリは落ちますか?

おそらく既に推測したように、 D1はクラスパスに分類されます (D1に存在しない新しい機能のために記述されたCのコードはすべて単純に分類されるため、問題が発生する可能性が非常に高くなります)。 これは私があなたに約束した素晴らしいWTFです。 Mavenは、プロジェクトツリー内のプロジェクトのルート(A)に近いクラスパス内のアーティファクトを選択する一意の最も近い戦略を使用します。

どうして? なんてナンセンス?
問題の根本は、Mavenの依存関係を排除することの難しさにあります。 たとえば、D1ではなくD2を使用する場合は、Mavenに次のように伝える必要があります。DearMaven、決してD1は使用しないでください。 ちょうど例として、Gradleでは次のように記述します。
configurations {all*.exclude group: 'mygroup', module: 'D', version: '1'}
問題は、これをMavenで表現することが不可能であることです。 (Mavenの経験豊富な、したがって気配りのある思慮深いユーザーは、ここで「エンフォーサープラグインはどうですか?!」と宣言しますが、これは間違っています )。 モジュールEに対して具体的に言うことができます。「Dに依存していると思いましたか? だから、彼女はそこにいません。」 これは良い方法です。競合はもうありません。クラスパスのD2が勝ちます。 しかし、このソリューションは完全にスケーラブルです。 多数のアーティファクトがD1に依存している場合はどうなりますか? 推移性のさまざまなレベルで?
さて、どこが最も近いですか?
グローバル除外が存在しないという問題は、Mavenで非常に「興味深い」方法で解決されました。 プロジェクトAで特定のバージョンの依存関係を宣言した場合、このバージョンのみがクラスパスに分類されることが決定されました。 つまり、実際には、これは究極の最も近いものです-A(これはルート)よりも近いことはできないため、競合は解決されます。Dを除外する必要があるすべての場所を探す必要はありません。 AがDを直接宣言していない場合(この例を参照)、つまりそうです。
興味深いことに、「ユーザーが自分自身を法律と宣言した」という考え方はGradleでも使用されています。これは、最新のような正気の戦略を使用することを妨げません。
更新:コメントの数人は、 forcer-pluginに失敗した機能があることを思い出しました。これにより、問題が部分的に解決されます。 残り:1.デフォルトで最も近いワイルド。 2.問題の解決策-プロジェクト内のすべての競合する依存関係を(許容範囲内で)登録するか、無限に除外します(地獄)。
そして同じ深さなら?
この美しい質問(例BがD2に依存していた場合の対処方法)は、2年半(2005年10月のリリース2.0から2008年4月のバージョン2.0.9まで)Mavenのメンバーには発生しませんでした。クラスパスにどのようなアーティファクトがあるのかはあいまいでした。 Maven 2.0.9で強い意思決定が行われました-最初のものがあります!

これはどのように役立ちますか? そうです、何もありません。 一般的な場合、どちらが最初になるかわからないからです。なぜなら、競合が発生するまで(またはこの謎を調査し始めるまで)推移的な依存関係は現れないからです。 みんなありがとう!
エピローグの代わりに
もちろん、MavenのWTFの洞察は、代替マインドの奇跡的な生成、つまり最も近い戦略に限定されません。 しかし、今日はそれで十分だと思います。 コメントのホリバーは大歓迎です(もしあれば、Gradleに沈むでしょう)。そして、すべてのPetersburgersは、土曜日、ペトロコングレスの31日、宴会を続けるためにJUGに来ます。