5

Java 8 qui.Gradle può aiutare a risolvere l'inferno in qualche modo?

dire che c'è una versione del widget libray, con coordinate Maven widgetmakers:widget:1.0.4, che ha una classe definita in questo modo:

public class Widget { 
    private String meow; 

    // constructor, getters, setters, etc. 
} 

anni passano. I manutentori di questa libreria widget decidono che uno Widget non dovrebbe mai meow, piuttosto che dovrebbe in effetti bark. E così una nuova release è fatto, con le coordinate Maven widgetmakers:widget:2.0.0 e con Widget cercando come:

public class Widget { 
    private Bark bark; 

    // constructor, getters, setters, etc. 
} 

Così ora vado a costruire la mia app, myapp. E, volendo utilizzare le ultime versioni stabili di tutte le mie dipendenze, dichiaro le mie dipendenze in questo modo (all'interno di build.gradle):

dependencies { 
    compile (
     ,'org.slf4j:slf4j-api:1.7.20' 
     ,'org.slf4j:slf4j-simple:1.7.20' 
     ,'bupo:fizzbuzz:3.7.14' 
     ,'commons-cli:commons-cli:1.2' 
     ,'widgetmakers:widget:2.0.0' 
    ) 
} 

Ora diciamo che questo (fittizia) fizzbuzz biblioteca ha sempre dipendeva da un Versione 1.x della libreria widget, dove Widgetmeow.

Così ora, mi sto specificando 2 versioni di widget sul mio compilazione classpath:

  1. widgetmakers:widget:1.0.4 che è attratto dalla libreria fizzbuzz, come una dipendenza di essa; e
  2. widgetmakers:widget:2.0.0 che sto riferimento diretto

Così, ovviamente, a seconda di quale versione di Widget viene classloaded prima, vi applicheremo una Widget#meow o un Widget#bark.

Gradle fornisce delle strutture per aiutarmi qui? Esiste un modo per inserire più versioni della stessa classe e configurare le classi fizzbuzz per utilizzare la versione precedente di Widget e le mie classi per utilizzare la nuova versione? In caso contrario, le uniche soluzioni mi viene in mente sono:

  1. potrei essere in grado di realizzare una sorta di shading- e/o soltuion fatjar-based, dove forse mi tira in tutte le mie dipendenze come pacchetti sotto myapp/bin e quindi dai loro differenti prefissi di versione. Devo ammettere che non vedo una soluzione chiara qui, ma sono sicuro che qualcosa sia fattibile (eppure totalmente hacky/brutto). Oppure ...
  2. Ispeziona attentamente l'intero grafico delle dipendenze e assicurati che tutte le mie dipendenze transitive non siano in conflitto tra loro. In questo caso per me, questo significa inviare una richiesta di pull ai manutentori fizzbuzz per aggiornarlo alla versione più recente widget o, purtroppo, eseguire il downgrade di myapp per utilizzare la versione precedente widget.

Ma Gradle (finora) è stato magico per me. Quindi chiedo: c'è qualche magia di Gradle che può servirmi qui?

+0

L'unico modo in cui vedo che è possibile utilizzare correttamente due versioni diverse della stessa libreria è il riferimento a ciascuna versione, laddove richiesto dall'utente o dalla libreria. In caso contrario, si dovrà dipendere dalla versione che Gradle fornirebbe. Inoltre otterrete un'eccezione classe/metodo non trovata se è la versione sbagliata. Gradle può trovare dipendenze che mancano e trascinarle, ma sabotare manualmente il meccanismo di risoluzione delle dipendenze di Gradle confondendolo con 2 librerie non andrà bene a lungo termine. –

+0

Grazie a @WeareBorg (+1). ** (1) ** Quando dici "* riferendosi a ogni versione, ovunque tu sia richiesto da te o dalla biblioteca *", puoi approfondire un po '? Sono stato uno sviluppatore JVM per oltre 10 anni e non ho familiarità con questa strategia/approccio! Puoi dare un esempio di cosa intendi? E ** (2) ** qual è il comportamento predefinito di Gradle in questa situazione? Lasciato intatto, quale versione della libreria 'widget' sarà in Gradle recuperare/risolvere? Grazie ancora! – smeeb

+1

1) Intendevo qualcosa di semplice, ma conosco la sintassi per Maven, dove puoi escludere la libreria che non ti servirà con una versione specifica, quindi l'altra versione viene automaticamente richiamata.2) Per il gradle a causa delle aggiunte del classpath, sarà il primo in lista, per Maven, sarà l'ultimo. –

risposta

5

Non conosco le specifiche di Gradle, perché sono una persona Maven, ma in ogni caso è più generico. Fondamentalmente hai due opzioni (ed entrambe sono hacky):

  1. ClassLoader magic. In qualche modo, è necessario convincere il proprio sistema di compilazione a caricare due versioni della libreria (buona fortuna con quella), quindi in fase di esecuzione, caricare le classi che utilizzano la versione precedente con un ClassLoader con la versione precedente. L'ho fatto, ma è un dolore. (Strumenti come OSGI possono togliere alcuni di questi dolori)
  2. ombreggiatura del pacchetto. Ricreare la libreria A che usa la vecchia versione della libreria B, in modo che B sia effettivamente all'interno di A, ma con un prefisso di pacchetto specifico per B. Questa è una pratica comune, ad es. Navi a molla its own version of asm. Sul lato Maven, lo maven-shade-plugin fa questo, probabilmente c'è un equivalente Gradle. Oppure puoi usare ProGuard, il gorilla da 800 libbre di manipolazione Jar.
+0

Avendo già imboccato tutti questi percorsi, proverei per prima cosa l'approccio all'ombreggiatura, se questo non funziona, OSGI, e quindi probabilmente lascerò la magia manuale del classloader come ultima risorsa. – biziclop

+0

@biziclop yeah Sono d'accordo che è il modo meno doloroso –

+0

Sembra la migliore risposta a un problema appiccicoso. – cjstehno

2

Gradle imposterà solo il classpath con le dipendenze, non fornisce il proprio runtime per incapsulare le dipendenze e le sue dipendenze transitive.La versione attiva in fase di esecuzione sarà quella secondo le regole di caricamento classi, che credo sia il primo jar nell'ordine di classpath a contenere la classe. OSGI fornisce runtime che può gestire situazioni come questa, così come il prossimo sistema di moduli.

EDIT: Bjorn ha ragione nel tentare di risolvere i conflitti in diverse versioni; compila il classpath in base alle sue strategie, quindi l'ordine in cui metti le tue dipendenze nel file non ha importanza. Tuttavia, si ottiene comunque una sola classe per nome classe, non risolvendo il problema dell'OP

+1

Questo è sbagliato. Gradle risolverà il conflitto in base alla strategia di risoluzione del conflitto scelta. Per impostazione predefinita utilizzerà la versione più recente di tutte le versioni richieste a meno che non si configuri Gradle in un altro modo e. g. per errore o per utilizzare una specifica versione fissa o qualcosa del genere. Non aggiungerà entrambe le versioni al classpath fintanto che le coordinate sono uguali ad eccezione della versione. – Vampire

0

Se si dispone di versioni diverse di una libreria con coordinate altrimenti uguali, entra in gioco il meccanismo di risoluzione dei conflitti Gradles.

La strategia di risoluzione predefinita consiste nell'utilizzare la versione più recente richiesta della libreria. Non otterrai più versioni della stessa libreria nel grafico dipendente.

Se hai davvero bisogno di versioni diverse della stessa libreria in fase di esecuzione, dovresti eseguire qualche magia ClassLoader che è sicuramente possibile o eseguire un'ombreggiatura per una delle librerie o entrambe.

Per quanto riguarda la risoluzione dei conflitti, Gradle ha incorporato la più recente strategia predefinita e una strategia di fail fallito se diverse versioni sono nel grafico delle dipendenze e devi risolvere esplicitamente i conflitti di versione nei tuoi file di build.

0

Il caso peggiore è quando la stessa classe appare in più vasi. Questo è più insidioso: guarda i vasetti delle metriche di Codahale e Dropwizard con versioni incompatibili della stessa classe nei due vasi. Il plugin gradle classpath-hell può rilevare questo horror.