Créer un JAR exécutable avec Gradle

148

Jusqu'à présent, j'ai créé des fichiers JAR exécutables via la fonctionnalité "Exporter ..." d'Eclipse, mais maintenant je suis passé à IntelliJ IDEA et Gradle pour l'automatisation de la construction.

Certains articles suggèrent ici le plugin "application", mais cela ne conduit pas entièrement au résultat attendu (juste un JAR, pas de scripts de démarrage ou quelque chose comme ça).

Comment puis-je obtenir le même résultat qu'Eclipse avec la boîte de dialogue "Exporter ..."?

Hannes
la source

Réponses:

164

Un fichier jar exécutable est simplement un fichier jar contenant une entrée Main-Class dans son manifeste. Il vous suffit donc de configurer la tâche jar pour ajouter cette entrée dans son manifeste:

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

Vous devrez peut-être également ajouter des entrées de chemin de classe dans le manifeste, mais cela se ferait de la même manière.

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

JB Nizet
la source
6
Cela semble être ce que je cherchais; mais: j'ai déjà déclaré des dépendances dans build.gradle, dois-je vraiment ajouter manuellement le chemin de classe ou puis-je réutiliser ma déclaration de dépendance?
Hannes le
Vous devriez être capable de parcourir les bibliothèques dans la configuration 'runtime' et de les concaténer pour créer la valeur d'attribut Class-Path.
JB Nizet
3
Que voulez-vous dire par 'inseid the "runtime" configuration'? Désolé pour les questions stupides, je suis assez nouveau à Gradle ...
Hannes
Le plugin gradle java définit 4 "configurations" correspondant à 4 chemins de classe différents: compile (utilisé pour compiler les fichiers Java), testCompile (qui sert à compiler les fichiers source Java de test), runtime (qui est utilisé pour exécuter l'application) et testRuntime (qui est utilisé pour exécuter les tests). Voir gradle.org/docs/current/userguide
JB Nizet
Ah, merci - que je l'ai bien compris et que j'ai réussi à construire le JAR, maintenant je m'efforce de créer le classpath ;-) Merci beaucoup pour votre aide!
Hannes
98

Les réponses de JB Nizet et Jorge_B sont correctes.

Dans sa forme la plus simple, la création d'un fichier JAR exécutable avec Gradle consiste simplement à ajouter les entrées appropriées au manifeste . Cependant, il est beaucoup plus courant d'avoir des dépendances qui doivent être incluses dans le chemin de classe, ce qui rend cette approche délicate en pratique.

Le plugin d'application fournit une approche alternative; au lieu de créer un JAR exécutable, il fournit:

  • une runtâche pour faciliter l'exécution facile de l'application directement à partir de la construction
  • une installDisttâche qui génère une structure de répertoires comprenant le JAR intégré, tous les JAR dont elle dépend et un script de démarrage qui rassemble le tout dans un programme que vous pouvez exécuter
  • distZipet les distTartâches qui créent des archives contenant une distribution complète de l'application (scripts de démarrage et JAR)

Une troisième approche consiste à créer un soi-disant "fat JAR" qui est un JAR exécutable qui inclut non seulement le code de votre composant, mais également toutes ses dépendances. Il existe quelques plugins différents qui utilisent cette approche. J'ai inclus des liens vers quelques-uns que je connais; Je suis sûr qu'il y en a d'autres.

davidmc24
la source
J'ai juste essayé l'ombre et un pot. Je m'en tiendrai à un pot: il est plus simple et plus facile à utiliser. Cool! merci
Jako
Malheureusement, one-jar ne fonctionne pas avec les versions récentes de Gradle. Voir, par exemple, github.com/rholder/gradle-one-jar/issues/34
pharsicle
Voici une solution en une seule ligne pour créer un bocal en modifiant la tâche du bocal. J'ai trouvé que c'était le plus pratique. Notez que vous devez ajouter configurations.runtimeaux dépendances d'exécution du bundle dans le fichier JAR unique.
Quazi Irfan
J'ai trouvé que le "plugin d'application" était suffisamment adapté et flexible pour mes besoins. Il permet également de tout compresser et d'inclure / d'exclure des fichiers supplémentaires dans ce zip. En outre, il est possible d'ajouter un autre script de lancement avec un point d'entrée supplémentaire à l'aide d'une tâche personnalisée.
kinORnirvana
Comment exécuter les .tar/ .zipfiles générés?
Tobiq le
35

Comme d'autres l'ont noté, pour qu'un fichier jar soit exécutable, le point d'entrée de l'application doit être défini dans l' Main-Classattribut du fichier manifeste. Si les fichiers de classe de dépendance ne sont pas colocalisés, ils doivent être définis dans l' Class-Pathentrée du fichier manifeste.

J'ai essayé toutes sortes de combinaisons de plugins et ce qui ne l'est pas pour la simple tâche de créer un fichier exécutable et, d'une manière ou d'une autre, d'inclure les dépendances. Tous les plugins semblent manquer d'une manière ou d'une autre, mais finalement je l'ai eu comme je le voulais. Pas de scripts mystérieux, pas un million de mini-fichiers différents polluant le répertoire de construction, un fichier de script de construction assez propre, et surtout: pas un million de fichiers de classe tiers étrangers fusionnés dans mon archive jar.

Ce qui suit est un copier-coller à partir d' ici pour votre commodité.

[Comment] créer un fichier zip de distribution avec des jars de dépendances dans le sous /lib- répertoire et ajouter toutes les dépendances à l' Class-Pathentrée dans le fichier manifeste:

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
}

Hébergé comme un essentiel ici .

Le résultat peut être trouvé dans build/distributionset le contenu décompressé ressemble à ceci:

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

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

Version du manifeste: 1.0
Classe principale: com.somepackage.MainClass
Chemin de classe: lib / commons-lang3-3.3.2.jar

Martin Andersson
la source
Le fichier jar actuel de l'application sera également dans le répertoire 'lib' de la distribution. Vous devez soit le déplacer par had vers le répertoire supérieur, soit changer cette ligne: 'Class-Path': configurations.runtime.files.collect {"lib / $ it.name"} .join ('')} en ceci: 'Class-Path': configurations.runtime.files.collect {"$ it.name"} .join ('')}
Marc Nuri
1
@MarcNuri Êtes-vous sûr? J'ai essayé d'utiliser cette approche pour mon application, et le fichier jar actuel de l'application n'était pas dans le librépertoire du fichier zip / tar produit, mais plutôt dans lible répertoire parent de, comme le suggère cette réponse. Cette solution semblait fonctionner parfaitement pour moi.
thejonwithnoh
1
@thejonwithnoh Je suis désolé, vous avez raison. Je n'ai pas vu la solution proposée d'utiliser le plugin "java-library-distribution". Dans mon cas, j'utilise simplement le plugin "application" qui fait le même travail avec la principale différence que tous les fichiers jar ( y compris le fichier jar de l'application) sont situés dans le répertoire "lib". Ainsi changer "lib/$it.name"en "$it.name"fera le travail.
Marc Nuri
28

La solution la moins efficace pour moi était d'utiliser le plugin gradle-shadow-plugin

Outre l'application du plugin, tout ce qui doit être fait est:

Configurez la tâche jar pour mettre votre classe principale dans le manifeste

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

Exécutez la tâche gradle

./gradlew shadowJar

Prenez le fichier app-version-all.jar de build / libs /

Et enfin exécutez-le via:

java -jar app-version-all.jar
Ostkontentitan
la source
2
Voici un exemple completbuild.gradle .
Brad Turek
Quand je fais cette construction, j'obtiens BUILD SUCCESSFUL mais quand j'essaye d'exécuter le fichier jar avec java -jar build / libs / core-all-1.0.jar j'obtiens l'erreur suivante: Erreur: Impossible de trouver ou de charger scanners.exchange de classe principale. Principal Causé par: java.lang.ClassNotFoundException: scanners.exchange.Main Savez-vous comment je peux résoudre ce problème?
Luka Lopusina
@LukaLopusina La classe que vous avez spécifiée n'est pas dans votre fichier JAR. Si vous utilisez kotlin, vous devez dire 'com.my.app.MainKt'. Sans plus d'informations, je ne peux pas vous aider davantage.
byxor
Le plugin actuel Shadow v5. + Est uniquement compatible avec Gradle 5.0+ et Java 7+.
Zon
5

Avez-vous essayé la tâche 'installApp'? Ne crée-t-il pas un répertoire complet avec un ensemble de scripts de démarrage?

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

Jorge_B
la source
D'après ce que je comprends, installAppne crée pas de META-INF/MANIFEST.MFfichier. Est-ce que je fais quelque chose de mal?
Avancez
1
Je ne vois pas de installApptâche dans la liste des tâches d' Application Plugin . Voulez-vous dire à la installDistplace?
Quazi Irfan
2
Oui, a installAppété renommé installDisten Gradle 3.0. Voici la note de publication .
Quazi Irfan
4

Merci Konstantin, cela a fonctionné comme un charme avec quelques nuances. Pour une raison quelconque, la spécification de la classe principale dans le cadre du manifeste jar ne fonctionnait pas tout à fait et il souhaitait plutôt l'attribut mainClassName. Voici un extrait de build.gradle qui comprend tout pour le faire fonctionner:

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'
}

Après avoir exécuté gradle shadowJar, vous obtenez myapp- {version} -all.jar dans votre dossier de construction qui peut être exécuté en tant que java -jar myapp- {version} -all.jar.

Alex Yavorskiy
la source
3

Vous pouvez définir un artefact jar dans les paramètres du module (ou dans la structure du projet).

  • Cliquez avec le bouton droit sur le module> Ouvrir les paramètres du module> Artefacts> +> JAR> à partir des modules avec dépendances.
  • Définissez la classe principale.

Créer un pot est alors aussi simple que de cliquer sur "Construire un artefact ..." dans le menu Construire. En prime, vous pouvez regrouper toutes les dépendances dans un seul bocal.

Testé sur IntelliJ IDEA 14 Ultimate.

Mondain
la source
2

J'ai vérifié plusieurs liens pour la solution, puis j'ai finalement suivi les étapes mentionnées ci-dessous pour la faire fonctionner. J'utilise Gradle 2.9.

Apportez les modifications suivantes à votre build, fichier gradle:

  1. Mentionnez le plugin:

    apply plugin: 'eu.appsatori.fatjar'
  2. Fournissez le Buildscript:

    buildscript {
    repositories {
        jcenter()
    }
    
    dependencies {
        classpath "eu.appsatori:gradle-fatjar-plugin:0.3"
    }
    }
  3. Fournissez la classe principale:

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

    ./gradlew clean fatjar
  5. Exécutez le fatjar depuis / build / libs /:

    java -jar MyFatJar.jar
Sandeep Sarkar
la source
1
À partir de 2019: cette suggestion ne fonctionne pas ici. Aucune dépendance n'est incluse dans le fatjar
carl
1

Vous pouvez utiliser le plugin SpringBoot:

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

Créer le pot

gradle assemble

Et puis lancez-le

java -jar build/libs/*.jar

Remarque: votre projet n'a PAS besoin d'être un projet SpringBoot pour utiliser ce plugin.

Topera
la source