Konflikt tranzitivních závislostí
Pochopitelně není možné znát do hloubky všechny nástroje a frameworky, se kterými denně přicházíme do styku. Na druhou stranu, pragmatický programátor se snaží pochopit alespoň principy, obzvláště u klíčových technologií. Tentokrát bych chtěl vysvětlit konflikt tranzitivních závislostí v Mavenu (ukážu i alternativu v Gradlu).
Zadání
Máme projekt s hromadou závislostí a ty s sebou nesou další tranzitivní závislosti. Ale co se stane, zavlečeme-li si do projektu stejný artefakt jiné verze?
Maven
Co na to Maven? Projekt možná nakrásně zkompilujete, ale za běhu se můžou dít věci… najednou na vás vyskočí ClassNotFoundException či NoSuchMethodError, ačkoliv tam patřičnou třídu přeci máte, ne? Nejspíš máte, ale třeba binárně nekompatibilní. Problém je v tom, že při konfliktu tranzitivních závislostí Maven nekřičí, ani nevezme nejvyšší verzi (jak byste nejspíš čekali), ale zvolí tu nejbližší ve stromě závislostí. Více v dokumentaci Introduction to the Dependency Mechanism.
Pojďme si to ukázat na následujícím příkladu. Jistě, v takto jednoduchém případě byste měli vybrat stejné verze Springu, ale v komplikovanějším projektu se vám závislosti zavlečou, ani nevíte odkud.
Nechme si vypsat seznam závislostí pomocí příkazu mvn dependency:list
Hm, vidíme spring-aop 3.0.7, zkontrolujme "proč" a to pomocí příkazu mvn dependency:tree -Dverbose
Teď, když už víme proč, můžeme danou závislost exludovat, případně explicitně definovat, čímž verzi přebijeme.
Všimněte si, že spring-web se vybral ve vyšší verzi, ovšem nikoliv proto, že je novější, ani proto, že je v tranzitivní závislosti blíž. V tranzitivní závislosti je ve stejné vzdálenosti, ale je v pomu definovaný dříve.
Gradle
Koluje takový vtip:
Jaký je rozdíl mezi autory Mavenu a Antu? Autoři Antu už se omluvili.
A co na to Gradle? Mějme ekvivalentní konfiguraci předchozímu maven příkladu.
Nechme si vypsat závislosti pomocí příkazu gradle dependencies
Všimněte si, že jsou všude použity nejvyšší verze, což je výchozí chování Gradlu. Můžete být ovšem přísnější a v případě konfliktu nechat build selhat, čehož docílíte použitím failOnVersionConflict() (odkomentujte TODO ve skriptu výše).
V tom případě musíte verzi vynutit pomocí force. Více v dokumentaci Resolve version conflicts a v javadocu ResolutionStrategy.