Usando Gradle para construir un jar con dependencias

122

Tengo una compilación multiproyecto y puse una tarea para construir un frasco gordo en uno de los subproyectos. Creé la tarea similar a la descrita en el libro de cocina .

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

Ejecutarlo da como resultado el siguiente error:

Causa: ¡No puede cambiar una configuración que no esté en estado sin resolver!

No estoy seguro de lo que significa este error. También informé esto en Gradle JIRA en caso de que sea un error .

Ben McCann
fuente

Respuestas:

195

Actualización: en las versiones más recientes de Gradle (4+), el compilecalificador está obsoleto en favor de las nuevas configuraciones apiy implementation. Si los usa, lo siguiente debería funcionar para usted:

// 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) }
  }
}

Para versiones anteriores de Gradle, o si todavía usa el calificador "compilar" para sus dependencias, esto debería funcionar:

// 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) }
  }
}

Tenga en cuenta que mainClassNamedebe aparecer ANTES jar {.

Ben McCann
fuente
4
Tuve que modificar esto a configurations.runtime.collect para mi proyecto ya que también tengo dependencias de tiempo de ejecución.
vextorspace
2
Tuve que agregar def mainClassNamepara que el código funcionara ... Estaba recibiendo No se pudo establecer la propiedad desconocida 'mainClassName' para el proyecto raíz
hanskoff
1
¿Cómo maneja las colisiones de nombres de archivos? Se sobrescribirán los archivos de la misma ruta en diferentes JAR.
wst
3
Desafortunadamente, esto ya no funciona. Utilizo gradle 4.10 y la nueva implementationconfiguración en lugar de la ahora obsoleta compile. El código anterior me crea un pequeño frasco sin las dependencias. Cuando lo cambio ( from { configurations.implementation.collect {...} }), se produce un error que dice que no se permite resolver la 'implementación' de la configuración directamente
Bastian Voigt
1
@BastianVoigt configurations.compileClasspatharreglará todos los implementations, pero dejará fuera las apidependencias afik. Encontré aquí en otra respuesta la solución runtimeClasspath. Eso también incluye las apidependencias.
rekire
64

La respuesta de @felix casi me lleva allí. Tuve dos problemas:

  1. Con Gradle 1.5, la etiqueta de manifiesto no se reconoció dentro de la tarea fatJar, por lo que el atributo Main-Class no se pudo establecer directamente
  2. el jar tenía archivos META-INF externos en conflicto.

La siguiente configuración resuelve este

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
}

Para agregar esto a la tarea estándar de ensamblar o compilar, agregue:

artifacts {
    archives fatJar
}

Editar: gracias a @mjaggard: en versiones recientes de Gradle, cambie configurations.runtimeaconfigurations.runtimeClasspath

blootsvoets
fuente
3
Esto también solucionó un problema que tenía donde se firmó uno de mis frascos de dependencia. Los archivos de firma se colocaron en el META-INF de mi jar, pero la firma ya no coincidía con el contenido.
Flavin
2
Un agradecimiento especial por artifacts: exactamente lo que estaba buscando.
AlexR
Cuando ejecuta, gradle fatJarlas dependencias en tiempo de ejecución no parecen estar compiladas, por lo que no se pueden copiar.
mjaggard
64

Si desea que la jartarea se comporte normalmente y también tiene una fatJartarea adicional , use lo siguiente:

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

La parte importante es with jar. Sin él, las clases de este proyecto no están incluidas.

Felix
fuente
1
También vea el siguiente problema si está usando frascos firmados para incluir y se encuentra con un problema con las firmas: stackoverflow.com/questions/999489/…
Peter N. Steinmetz
6
Esto no funciona. El archivo de manifiesto está vacío con esta solución.
Jonas
4
Mis 2 centavos: es mejor establecer un clasificador que cambiar el nombre. Ponga classifier = 'all' en lugar de baseName = project.name + '-all'. De esa manera, mantendrá el nombre del artefacto de acuerdo con las políticas de Maven / Nexus.
taciosd
1
Agregue group "build"y esta tarea estará en buildgrupo (con otras tareas, es decir, jartarea.
MAGx2
1
No puedo encontrar ningún tipo de documentación sobre la with jarpalabra clave, ¿qué hace exactamente?
Philipp Hemmelmayr
9

Esto funciona bien para mi.

Mi clase principal:

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");
    }

}

Y es el contenido de mi archivo 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
}

Y escribo lo siguiente en mi consola:

java -jar ProyectoEclipseTest-all.jar

Y la salida es genial:

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
Aron
fuente
6

Para generar un JAR gordo con una clase ejecutable principal, evitando problemas con los JAR firmados, sugiero el complemento gradle-one-jar . Un complemento simple que usa el proyecto One-JAR .

Fácil de usar:

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'
}
Italo Borssatto
fuente
5

Sulución simple

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

La respuesta de @ben casi funciona para mí, excepto que mis dependencias son demasiado grandes y recibí el siguiente error

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

  To build this archive, please enable the zip64 extension.

Para solucionar este problema, tengo que usar el siguiente código

mainClassName = "com.company.application.Main"

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

Para aquellos que necesitan construir más de un frasco del proyecto.

Crea una función en 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'
    }
}

luego llame:

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

Funciona en gradle 5.

Jar se colocará en ./build/libs.

MiguelSlv
fuente
0

Yo uso tarea shadowJarpor complemento. com.github.jengelman.gradle.plugins:shadow:5.2.0

El uso del ./gradlew app::shadowJar archivo de resultados de ejecución será enMyProject/app/build/libs/shadow.jar

build.gradlearchivo de nivel superior :

 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.gradlearchivo de nivel de módulo de aplicación

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()
}

NickUnuchek
fuente
0

Gradle 6.3, biblioteca de Java. El código de "jar task" agrega las dependencias a "build / libs / xyz.jar" cuando se ejecuta la tarea " gradle build ".

plugins {
    id 'java-library'
}

jar {
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}
Alex Ureche
fuente
-1

Si estás acostumbrado a las hormigas, también puedes intentar lo mismo con Gradle:

task bundlemyjava{
    ant.jar(destfile: "build/cookmyjar.jar"){
        fileset(dir:"path to your source", includes:'**/*.class,*.class', excludes:'if any')
        } 
}
mig
fuente