Usare Gradle per costruire un vaso con dipendenze


122

Ho una build multiprogetto e ho inserito un compito per costruire un fat jar in uno dei sottoprogetti. Ho creato l'attività simile a quella descritta nel ricettario .

jar {
  from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  manifest { attributes 'Main-Class': 'com.benmccann.gradle.test.WebServer' }
}

Eseguendolo si ottiene il seguente errore:

Causa: non è possibile modificare una configurazione che non è in uno stato irrisolto!

Non sono sicuro di cosa significhi questo errore. Ho anche segnalato questo su Gradle JIRA nel caso fosse un bug .

Risposte:


195

Aggiornamento: nelle versioni Gradle più recenti (4+) il compilequalificatore è deprecato a favore del nuovo apie delle implementationconfigurazioni. Se li usi, dovrebbe funzionare quanto segue:

// Include dependent libraries in archive.
mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  

  from {
    configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
  }
}

Per le versioni gradle precedenti, o se usi ancora il qualificatore "compile" per le tue dipendenze, dovrebbe funzionare:

// Include dependent libraries in archive.
mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  

  from {
    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  }
}

Nota che mainClassNamedeve apparire PRIMA jar {.


4
Ho dovuto modificarlo in configurations.runtime.collect per il mio progetto poiché ho anche dipendenze di runtime.
vextorspace

2
Ho dovuto aggiungere def mainClassNameper far funzionare il codice ... Stavo ricevendo Impossibile impostare la proprietà sconosciuta 'mainClassName' per il progetto root
hanskoff

1
Come gestisci le collisioni di nomi di file? I file sullo stesso percorso in diversi JAR verranno sovrascritti.
wst

3
Purtroppo questo non funziona più. Uso gradle 4.10 e la nuova implementationconfigurazione invece dell'ormai deprecato compile. Il codice sopra mi crea un piccolo vaso senza le dipendenze. Quando lo cambio ( from { configurations.implementation.collect {...} }), si verifica un errore dicendo che la risoluzione diretta dell '"implementazione" della configurazione non è consentita
Bastian Voigt

1
@BastianVoigt configurations.compileClasspathrisolverà tutti i messaggi di posta elettronica implementation, ma apitralascerà le dipendenze afik. Trovata qui in un'altra risposta la soluzione runtimeClasspath. Ciò include anche le apidipendenze.
rekire

64

La risposta di @felix mi ha quasi portato lì. Ho avuto due problemi:

  1. Con Gradle 1.5, il tag manifest non è stato riconosciuto all'interno dell'attività fatJar, quindi l'attributo Main-Class non può essere impostato direttamente
  2. il jar aveva file META-INF esterni in conflitto.

La seguente configurazione risolve questo problema

jar {
  manifest {
    attributes(
      'Main-Class': 'my.project.main',
    )
  }
}

task fatJar(type: Jar) {
  manifest.from jar.manifest
  classifier = 'all'
  from {
    configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
  } {
    exclude "META-INF/*.SF"
    exclude "META-INF/*.DSA"
    exclude "META-INF/*.RSA"
  }
  with jar
}

Per aggiungerlo all'attività di assemblaggio o creazione standard, aggiungi:

artifacts {
    archives fatJar
}

Modifica: grazie a @mjaggard: nelle versioni recenti di Gradle, configurations.runtimepassa aconfigurations.runtimeClasspath


3
Questo ha anche risolto un problema che avevo in cui era firmato uno dei miei vasi di dipendenza. I file della firma sono stati inseriti nel META-INF del mio vaso, ma la firma non corrisponde più al contenuto.
Flavin

2
Un ringraziamento speciale per artifacts: esattamente quello che stavo cercando.
AlexR

Quando esegui gradle fatJarle dipendenze di runtime non sembrano essere compilate, quindi non possono essere copiate.
mjaggard

64

Se desideri che l' jarattività si comporti normalmente e abbia anche un'attività aggiuntiva fatJar, utilizza quanto segue:

task fatJar(type: Jar) {
    classifier = 'all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

La parte importante è with jar. Senza di esso, le classi di questo progetto non sono incluse.


1
Vedi anche il seguente problema se stai usando vasi firmati da includere e riscontri un problema con le firme: stackoverflow.com/questions/999489/…
Peter N. Steinmetz

6
Questo non funziona. Il file manifest è vuoto con questa soluzione.
Jonas

4
I miei 2 centesimi: è meglio impostare un classificatore che cambiare il nome. Inserisci classifier = 'all' invece di baseName = project.name + '-all'. In questo modo mantieni il nome del manufatto in conformità con le norme Maven / Nexus.
taciosd

1
Aggiungi group "build"e questa attività sarà in buildgruppo (con altre attività, ad esempio jarattività.
MAGx2

1
Non riesco a trovare alcun tipo di documentazione sulla with jarparola chiave, cosa fa esattamente?
Philipp Hemmelmayr

9

Questo funziona bene per me.

La mia classe principale:

package com.curso.online.gradle;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

public class Main {

    public static void main(String[] args) {
        Logger logger = Logger.getLogger(Main.class);
        logger.debug("Starting demo");

        String s = "Some Value";

        if (!StringUtils.isEmpty(s)) {
            System.out.println("Welcome ");
        }

        logger.debug("End of demo");
    }

}

Ed è il contenuto del mio file build.gradle:

apply plugin: 'java'

apply plugin: 'eclipse'

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
    compile  'org.apache.commons:commons-lang3:3.0'
    compile  'log4j:log4j:1.2.16'
}

task fatJar(type: Jar) {
    manifest {
        attributes 'Main-Class': 'com.curso.online.gradle.Main'
    }
    baseName = project.name + '-all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

E scrivo quanto segue nella mia console:

java -jar ProyectoEclipseTest-all.jar

E il risultato è ottimo:

log4j:WARN No appenders could be found for logger (com.curso.online.gradle.Main)
.
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more in
fo.
Welcome

6

Per generare un JAR grasso con una classe eseguibile principale, evitando problemi con i JAR firmati, suggerisco il plugin gradle-one-jar . Un semplice plugin che utilizza il progetto One-JAR .

Facile da usare:

apply plugin: 'gradle-one-jar'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.github.rholder:gradle-one-jar:1.0.4'
    }
}

task myjar(type: OneJar) {
    mainClass = 'com.benmccann.gradle.test.WebServer'
}

5

Semplice soluzione

jar {
    manifest {
        attributes 'Main-Class': 'cova2.Main'
    } 
    doFirst {
        from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
    }
}

5

La risposta di @ben funziona quasi per me tranne per il fatto che le mie dipendenze sono troppo grandi e ho ricevuto il seguente errore

Execution failed for task ':jar'.
> archive contains more than 65535 entries.

  To build this archive, please enable the zip64 extension.

Per risolvere questo problema, devo utilizzare il seguente codice

mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  
  zip64 = true
  from {
    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  }
}

1

Per chi ha bisogno di realizzare più di un vaso dal progetto.

Crea una funzione in gradle:

void jarFactory(Jar jarTask, jarName, mainClass) {
    jarTask.doFirst {
        println 'Build jar ' + jarTask.name + + ' started'
    }

    jarTask.manifest {
        attributes(
                'Main-Class':  mainClass
        )
    }
    jarTask.classifier = 'all'
    jarTask.baseName = jarName
    jarTask.from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    {
        exclude "META-INF/*.SF"
        exclude "META-INF/*.DSA"
        exclude "META-INF/*.RSA"
    }
    jarTask.with jar 
    jarTask.doFirst {
        println 'Build jar ' + jarTask.name + ' ended'
    }
}

quindi chiama:

task makeMyJar(type: Jar) {
    jarFactory(it, 'MyJar', 'org.company.MainClass')
}

Funziona su gradle 5.

Jar sarà posizionato in ./build/libs.


0

Uso task shadowJarby plugin. com.github.jengelman.gradle.plugins:shadow:5.2.0

L'utilizzo del ./gradlew app::shadowJar file dei risultati appena eseguito sarà inMyProject/app/build/libs/shadow.jar

build.gradlefile di primo livello :

 apply plugin: 'kotlin'

buildscript {
    ext.kotlin_version = '1.3.61'

    repositories {
        mavenLocal()
        mavenCentral()
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
    }
}

build.gradlefile a livello di modulo dell'app

apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'

sourceCompatibility = 1.8

kapt {
    generateStubs = true
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation "org.seleniumhq.selenium:selenium-java:4.0.0-alpha-4"
    shadow "org.seleniumhq.selenium:selenium-java:4.0.0-alpha-4"

    implementation project(":module_remote")
    shadow project(":module_remote")
}

jar {
    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
    manifest {
        attributes(
                'Main-Class': 'com.github.kolyall.TheApplication',
                'Class-Path': configurations.compile.files.collect { "lib/$it.name" }.join(' ')
        )
    }
}

shadowJar {
    baseName = 'shadow'
    classifier = ''
    archiveVersion = ''
    mainClassName = 'com.github.kolyall.TheApplication'

    mergeServiceFiles()
}


0

Gradle 6.3, libreria Java. Il codice da "jar task" aggiunge le dipendenze a "build / libs / xyz.jar" durante l'esecuzione del task " gradle build ".

plugins {
    id 'java-library'
}

jar {
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

-1

Se sei abituato a formica, potresti provare lo stesso anche con Gradle:

task bundlemyjava{
    ant.jar(destfile: "build/cookmyjar.jar"){
        fileset(dir:"path to your source", includes:'**/*.class,*.class', excludes:'if any')
        } 
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.