Sõltuvuste haldamine
Sissejuhatus
Ühe ainsa teegi projekti lisamine on lihtne ja otsekohene. Keerukus tekib siis, kui sellel teegil on omad sõltuvused ja neil sõltuvustel omakorda järgmised sõltuvused. Sellist ahelat nimetatakse transitiivseteks sõltuvusteks ja just siit tulevad enamik reaalseid ehitusprobleeme.
Nii Maven kui ka Gradle käsitlevad transitiivseid sõltuvusi automaatselt, kuid lahendavad nendest tulenevaid versioonikonflikte erinevalt. Nende tööpõhimõtete mõistmine on tähtis olukorras, kus projekti ehitusprotsess läheb katki kahe erineva sõltuvuse versiooni erinevuse pärast.
Transitiivsed sõltuvused
Oletame, et projekt sõltub teegist A ja teek A sõltub teegist B. Konfiguratsioonis on deklareeritud ainult teek A, kuid B tõmmatakse automaatselt kaasa:
Your Project
└── A (You declared this dependency)
└── B (Automatically downloaded along side A)
See on mugav kuna ei pea käsitsi jälgima, millest sõltuvused sõltuvad. Kuid see tähendab ka seda, et sinu classpath sisaldab teeke, mida sa otseselt ei deklareerinud.
Kõiki alla laetud teeke saab vaadata järgnevalt:
# Maven
mvn dependency:tree
# Gradle
./gradlew dependencies
Need käsud näitavad igat teeki, milline sõltuvus selle kaasa tõi ja valitud versiooni. See on esimene tööriist, mille poole pöörduda, kui midagi läheb valesti.
Versioonikonfiktid
Konflikt tekib siis, kui kaks sõltuvust vajavad sama teegi erinevaid versioone. Näiteks:
Your Project
├── gson:2.10.1
└── jackson-databind:2.9.0
└── gson:2.8.6 (transitive)
Projektis on deklareeritud Gson versioon 2.10.1, kuid Jackson (oma sõltuvuste kaudu) kasutab versiooni 2.8.6.
Mõlemad versioonid ei saa classpath'is koos eksisteerida ehk ehitusinstrument peab valima ühe nendest.
Kuidas Maven konflikte lahendab
Maven kasutab neartest-fist lahendust ehk valitakse see versioon, mis on sõltuvuspuu juurele kõige lähemal deklareeritud.
Ülaltoodud näites on otsene Gsoni 2.10.1 deklaratsioon sügavusel 1.
Transitiivne 2.8.6 on sügavusel 2.
Maven valib 2.10.1, sest see on juurele lähemal.
Kui kaks transitiivset sõltuvust samal sügavusel vajavad erinevaid versioone, valib Maven selle, mis on POM-is esimesena deklareeritud.
Kuidas Gradle konflikte lahendab
Gradle kasutab vaikimisi newest-wins lahendust ehk Gradle valib antud teegi kõrgeima versiooninumbri.
Samas näites valiks Gradle 2.10.1, kuna see on uuem kui 2.8.6.
Kumbki strateegia pole 100% ohutu. Uuemal versioonil võivad olla fundamentaalsed muudatused, mis rikuvad varasemat kasutuskogemust. Vanemal versioonil võivad puududa funktsioonid, mida sellest sõltuv teek vajab. Kontrolli alati oma sõltuvuspuud pärast uute teekide lisamist.
Versiooni sundimine
Nii Maven kui ka Gradle pakuvad viisi, kuidas sundida mõni teek kindlale versioonile. See tagab stabiilsust üle terve projekti.
Maven: dependencyManagement
<dependencyManagement> plokk seab versioonireeglid, mis kehtivad kõikidele sõltuvustele, nii otsestele kui ka transitiivsetele:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</dependencyManagement>
See ei lisa Gsoni sõltuvusena, vaid määrab tervet projekti kasutama versiooni 2.10.1.
Sõltuvusena lisamiseks vajad ikkagi eraldi <dependency> kirjet <dependencies> plokis.
<!-- These two blocks are not the same-->
<!--- Adds Gson as a dependency --->
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
<!--- Locks down Gson version to 2.10.1 --->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</dependencyManagement>
Gradle: resolutionStrategy
Gradle'i vaste sellele on resolutionStrategy configurations.all ploki sees:
configurations.all {
resolutionStrategy {
force 'com.google.code.gson:gson:2.10.1'
}
}
See tühistab iga Gsoni versiooni, mis sõltuvuspuus esineb, asendades selle versiooniga 2.10.1.
Transitiivse sõltuvuse välistamine
Mõnikord ei ole probleem mitte versioonis, vaid sõltuvuses endas. Teek võib kaasa tõmmata midagi, mida sa ei soovi. On olukordi, kus antud teek võib olla vastuolus teise teegiga või on lihtsalt ebavajalik.
Maven: exclusion
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
Gradle: exclude
implementation('com.fasterxml.jackson.core:jackson-databind:2.9.0') {
exclude group: 'com.google.code.gson', module: 'gson'
}
Mõlemad takistavad Gsoni kaasatõmbamist Jacksoni kaudu, olenemata versioonist.
Millal sundida vs millal välistada
| Olukord | Lähenemine |
|---|---|
| Kaks sõltuvust vajavad sama teegi erinevaid versioone | Määra projekti jaoks versioon, mis töötab mõlema jaoks |
| Transitiivne sõltuvus on vastuolus sinu deklareeritud otsese sõltuvusega | Määra enda poolt deklareeritud versioon |
| Transitiivne sõltuvus on täiesti ebavajalik või põhjustab classpath-i probleeme | Välista see |
| Soovid standardiseerida teegi versiooni suures projektis | Kasuta dependencyManagement (Maven) või resolutionStrategy (Gradle) |
Sõltuvuspuu käsk on sinu diagnostikatööriist kõigil juhtudel. Käivita see enne ja pärast muudatuste tegemist, et tulemust kontrollida.