Crear JAR ejecutable con Gradle

148

Hasta ahora, creé archivos JAR ejecutables a través de la funcionalidad "Exportar ..." de Eclipse, pero ahora cambié a IntelliJ IDEA y Gradle para la automatización de compilación.

Algunos artículos aquí sugieren el complemento de "aplicación", pero esto no conduce completamente al resultado que esperaba (solo un JAR, sin scripts de inicio ni nada parecido).

¿Cómo puedo lograr el mismo resultado que Eclipse con el cuadro de diálogo "Exportar ..."?

Hannes
fuente

Respuestas:

164

Un archivo jar ejecutable es solo un archivo jar que contiene una entrada de clase principal en su manifiesto. Entonces solo necesita configurar la tarea jar para agregar esta entrada en su manifiesto:

jar {
    manifest {
        attributes 'Main-Class': 'com.foo.bar.MainClass'
    }
}

Es posible que también deba agregar entradas de classpath en el manifiesto, pero eso se haría de la misma manera.

Ver http://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html

JB Nizet
fuente
66
Esto parece ser lo que estaba buscando; pero: ya he declarado dependencias en build.gradle, ¿realmente tengo que agregar manualmente la ruta de clase o puedo reutilizar mi declaración de dependencia?
Hannes
Debería poder recorrer las bibliotecas dentro de la configuración de 'tiempo de ejecución' y concatenarlas para crear el valor del atributo Class-Path.
JB Nizet
3
¿Qué quieres decir con 'inseid the "runtime" configuration'? Perdón por las estúpidas preguntas, soy bastante nuevo en Gradle ...
Hannes
El complemento gradle java define 4 "configuraciones" correspondientes a 4 rutas de clase diferentes: compilar (se usa para compilar los archivos Java), testCompile (que se usa para compilar los archivos fuente de prueba Java), tiempo de ejecución (que se usa para ejecutar la aplicación) y testRuntime (que se usa para ejecutar las pruebas). Ver gradle.org/docs/current/userguide/…
JB Nizet
Ah, gracias, de lo que entendí correcto y logré construir el JAR, ahora estoy jugando con crear el classpath ;-) ¡Muchas gracias por su ayuda!
Hannes
98

Las respuestas de JB Nizet y Jorge_B son correctas.

En su forma más simple, crear un JAR ejecutable con Gradle es solo cuestión de agregar las entradas apropiadas al manifiesto . Sin embargo, es mucho más común tener dependencias que deben incluirse en el classpath, lo que hace que este enfoque sea complicado en la práctica.

El complemento de aplicación proporciona un enfoque alternativo; en lugar de crear un JAR ejecutable, proporciona:

  • una runtarea para facilitar la ejecución fácil de la aplicación directamente desde la compilación
  • una installDisttarea que genera una estructura de directorio que incluye el JAR integrado, todos los JAR de los que depende y un script de inicio que reúne todo en un programa que puede ejecutar
  • distZipy distTartareas que crean archivos que contienen una distribución completa de la aplicación (scripts de inicio y JAR)

Un tercer enfoque es crear el llamado "JAR gordo", que es un JAR ejecutable que incluye no solo el código de su componente, sino también todas sus dependencias. Hay algunos complementos diferentes que utilizan este enfoque. He incluido enlaces a algunos que conozco; Estoy seguro de que hay más.

davidmc24
fuente
Acabo de probar shadow y one-jar. Me adheriré a one-jar: es más simple y fácil de usar. ¡Frio! gracias
Jako
Lamentablemente, one-jar no funciona con versiones recientes de Gradle. Ver, por ejemplo, github.com/rholder/gradle-one-jar/issues/34
pharsicle
Aquí hay una solución de una línea para construir un jar modificando la tarea jar. He encontrado que esto es lo más conveniente. Tenga en cuenta que debe agregar configurations.runtimea las dependencias de tiempo de ejecución del paquete en el jar individual.
Quazi Irfan
Encontré que el "complemento de aplicación" es lo suficientemente adecuado y flexible para mis necesidades. También permite empaquetar todo para comprimir e incluir / excluir archivos adicionales en ese zip. Además, es posible agregar otro script de inicio con un punto de entrada adicional mediante una tarea personalizada.
kinORnirvana
¿Cómo ejecutaría los archivos .tar/ .zipgenerados?
Tobiq
35

Como otros han señalado, para que un archivo jar sea ejecutable, el punto de entrada de la aplicación debe establecerse en el Main-Classatributo del archivo de manifiesto. Si los archivos de clase de dependencia no están colocados, entonces deben establecerse en la Class-Pathentrada del archivo de manifiesto.

He intentado todo tipo de combinaciones de complementos y, además de la simple tarea de crear un jar ejecutable y de alguna manera, incluir las dependencias. Parece que faltan todos los complementos de una forma u otra, pero finalmente lo obtuve como quería. Sin scripts misteriosos, ni un millón de mini archivos diferentes que contaminan el directorio de compilación, un archivo de scripts de compilación bastante limpio y, sobre todo: ni un millón de archivos de clases de terceros extranjeros fusionados en mi archivo jar.

Lo siguiente es copiar y pegar desde aquí para su conveniencia.

[Cómo hacer] crear un archivo zip de distribución con tarros de dependencias en el subdirectorio /liby agregar todas las dependencias a la Class-Pathentrada en el archivo de manifiesto:

apply plugin: 'java'
apply plugin: 'java-library-distribution'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.apache.commons:commons-lang3:3.3.2'
}

// Task "distZip" added by plugin "java-library-distribution":
distZip.shouldRunAfter(build)

jar {
    // Keep jar clean:
    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'

    manifest {
        attributes 'Main-Class': 'com.somepackage.MainClass',
                   'Class-Path': configurations.runtime.files.collect { "lib/$it.name" }.join(' ')
    }
    // How-to add class path:
    //     /programming/22659463/add-classpath-in-manifest-using-gradle
    //     https://gist.github.com/simon04/6865179
}

Alojado como una esencia aquí .

El resultado se puede encontrar en build/distributionsy el contenido descomprimido se ve así:

lib / commons-lang3-3.3.2.jar
MyJarFile.jar

Contenido de MyJarFile.jar#META-INF/MANIFEST.mf:

Versión de manifiesto: 1.0
Main-Class: com.somepackage.MainClass
Class-Path: lib / commons-lang3-3.3.2.jar

Martin Andersson
fuente
El archivo jar de la aplicación actual también estará en el directorio 'lib' de la distribución. Debe moverlo al directorio superior o cambiar esta línea: 'Class-Path': configurations.runtime.files.collect {"lib / $ it.name"} .join ('')} a esto: 'Class-Path': configurations.runtime.files.collect {"$ it.name"} .join ('')}
Marc Nuri
1
@MarcNuri ¿Estás seguro? Intenté usar este enfoque para mi aplicación, y el archivo jar de la aplicación actual no estaba en el libdirectorio del archivo zip / tar producido, sino que estaba en libel directorio padre, como sugiere esta respuesta. Esta solución pareció funcionar perfectamente para mí.
thejonwithnoh
1
@thejonwithnoh Lo siento, tienes razón. No vi la solución propuesta para usar el complemento "java-library-distribution". En mi caso, simplemente estoy usando el complemento de "aplicación" que hace el mismo trabajo con la diferencia principal de que todos los archivos jar ( incluido el jar de la aplicación) se encuentran en el directorio "lib". Por lo tanto, cambiar "lib/$it.name"a "$it.name"hará el trabajo.
Marc Nuri
28

La solución de menor esfuerzo para mí fue hacer uso de complemento gradle-shadow-plugin

Además de aplicar el complemento, todo lo que hay que hacer es:

Configure la tarea jar para poner su clase Main en manifiesto

jar {
  manifest {
   attributes 'Main-Class': 'com.my.app.Main'
  }
}

Ejecute la tarea gradle

./gradlew shadowJar

Tome la versión de la aplicación all.jar de build / libs /

Y finalmente ejecutarlo a través de:

java -jar app-version-all.jar
Ostkontentitan
fuente
2
Aquí hay un ejemplo completobuild.gradle .
Brad Turek
Cuando hago esta compilación, CONSTRUYO EXITOSO, pero cuando intento ejecutar el archivo jar con java -jar build / libs / core-all-1.0.jar, aparece el siguiente error: Error: No se pudo encontrar o cargar scanners.exchange de clase principal. Principal Causado por: java.lang.ClassNotFoundException: scanners.exchange.Main ¿Sabes cómo puedo resolver esto?
Luka Lopusina
@LukaLopusina La clase que especificó no está en su archivo JAR. Si estás usando kotlin, debes decirlo 'com.my.app.MainKt'. Sin más información, no puedo ayudarte más.
byxor
El complemento actual Shadow v5. + Es compatible solo con Gradle 5.0+ y Java 7+.
Zon
5

¿Has probado la tarea 'installApp'? ¿No crea un directorio completo con un conjunto de scripts de inicio?

http://www.gradle.org/docs/current/userguide/application_plugin.html

Jorge_B
fuente
Por lo que entiendo, installAppno crea un META-INF/MANIFEST.MFarchivo. ¿Estoy haciendo algo mal?
Advait
1
No veo la installApptarea en la lista de tareas del complemento de aplicación . ¿Quiso decir en su installDistlugar?
Quazi Irfan
2
Sí, installAppse le cambió el nombre a installDisten Gradle 3.0. Aquí está la nota de lanzamiento .
Quazi Irfan
4

Gracias Konstantin, funcionó de maravilla con pocos matices. Por alguna razón, especificar la clase principal como parte del manifiesto jar no funcionó del todo y quería el atributo mainClassName. Aquí hay un fragmento de build.gradle que incluye todo para que funcione:

plugins {
  id 'java' 
  id 'com.github.johnrengelman.shadow' version '1.2.2'
}
...
...
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'
...
...
mainClassName = 'com.acme.myapp.MyClassMain'
...
...
...
shadowJar {
    baseName = 'myapp'
}

Después de ejecutar gradle shadowJar, obtienes myapp- {versión} -all.jar en tu carpeta de compilación que se puede ejecutar como java -jar myapp- {versión} -all.jar.

Alex Yavorskiy
fuente
3

Puede definir un artefacto jar en la configuración del módulo (o estructura del proyecto).

  • Haga clic con el botón derecho en el módulo> Abrir configuración del módulo> Artefactos> +> JAR> desde módulos con dependencias.
  • Establecer la clase principal.

Hacer un frasco es tan fácil como hacer clic en "Crear artefacto ..." en el menú Crear. Como beneficio adicional, puede empaquetar todas las dependencias en un solo jar.

Probado en IntelliJ IDEA 14 Ultimate.

Mondain
fuente
2

Revisé algunos enlaces para la solución, finalmente hice los pasos mencionados a continuación para que funcione. Estoy usando Gradle 2.9.

Realice los siguientes cambios en su compilación, archivo gradle:

  1. Mencione el complemento:

    apply plugin: 'eu.appsatori.fatjar'
  2. Proporcione el Buildscript:

    buildscript {
    repositories {
        jcenter()
    }
    
    dependencies {
        classpath "eu.appsatori:gradle-fatjar-plugin:0.3"
    }
    }
  3. Proporcionar la clase principal:

    fatJar {
      classifier 'fat'
      manifest {
        attributes 'Main-Class': 'my.project.core.MyMainClass'
      }
      exclude 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.SF'
    }
  4. Crea el fatjar:

    ./gradlew clean fatjar
  5. Ejecute el fatjar desde / build / libs /:

    java -jar MyFatJar.jar
Sandeep Sarkar
fuente
1
Desde 2019: esta sugerencia no funciona aquí. No se incluyen dependencias en el fatjar
carl
1

Puede usar el complemento SpringBoot:

plugins {
  id "org.springframework.boot" version "2.2.2.RELEASE"
}

Crea el frasco

gradle assemble

Y luego ejecutarlo

java -jar build/libs/*.jar

Nota: su proyecto NO necesita ser un proyecto SpringBoot para usar este complemento.

Topera
fuente