Использование Gradle для создания jar-файла с зависимостями

122

У меня мультипроектная сборка и в один из подпроектов я поставил задачу собрать толстую банку. Я создал задачу, аналогичную описанной в кулинарной книге .

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

Его запуск приводит к следующей ошибке:

Причина: вы не можете изменить конфигурацию, которая не находится в неразрешенном состоянии!

Я не уверен, что означает эта ошибка. Я также сообщил об этом в Gradle JIRA, если это ошибка .

Бен Макканн
источник

Ответы:

196

Обновление: в более новых версиях Gradle (4+) compileквалификатор устарел в пользу новых apiи implementationконфигураций. Если вы их используете, у вас должно работать следующее:

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

Для более старых версий Gradle или если вы все еще используете квалификатор compile для своих зависимостей, это должно работать:

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

Обратите внимание, что mainClassNameдолжно появиться ДО jar {.

Бен Макканн
источник
4
Мне пришлось изменить это на configurations.runtime.collect для моего проекта, поскольку у меня также есть зависимости времени выполнения.
vextorspace
2
Мне пришлось добавить, def mainClassNameчтобы код заработал ... Я получал Не удалось установить неизвестное свойство mainClassName для корневого проекта
hanskoff
1
Как вы справляетесь с конфликтами имен файлов? Файлы по одному и тому же пути в разных JAR-файлах будут перезаписаны.
WST
3
К сожалению, это больше не работает. Я использую gradle 4.10 и новую implementationконфигурацию вместо устаревшей compile. Приведенный выше код создает мне небольшую банку без зависимостей. Когда я меняю его ( from { configurations.implementation.collect {...} }), возникает ошибка, в которой говорится, что разрешение конфигурации «реализация» напрямую не разрешено
Бастиан Войт
1
@BastianVoigt configurations.compileClasspathисправит все ошибки implementation, но оставит apiзависимости afik. Нашел здесь в другом ответе решение runtimeClasspath. Это также включает в себя apiзависимости.
rekire
64

Ответ @felix почти привел меня туда. У меня было две проблемы:

  1. В Gradle 1.5 тег manifest не распознавался внутри задачи fatJar, поэтому атрибут Main-Class нельзя было установить напрямую
  2. в банке были конфликтующие внешние файлы META-INF.

Следующая установка решает эту проблему

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
}

Чтобы добавить это в стандартную задачу сборки или сборки, добавьте:

artifacts {
    archives fatJar
}

Изменить: благодаря @mjaggard: в последних версиях Gradle измените configurations.runtimeнаconfigurations.runtimeClasspath

blootsvoets
источник
3
Это также устранило проблему, с которой у меня была подписана одна из моих jar-файлов зависимостей. Файлы подписи были помещены в мой файл META-INF, но подпись больше не соответствовала содержимому.
Flavin
2
Особая благодарность за artifactsто, что я искал.
AlexR
Когда вы запускаете, gradle fatJarкажется, что зависимости среды выполнения не компилируются, поэтому их нельзя скопировать.
mjaggard 04
64

Если вы хотите, чтобы jarзадача выполнялась нормально, а также имела дополнительную fatJarзадачу, используйте следующее:

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

Важная часть with jar. Без него классы этого проекта не включены.

Феликс
источник
1
Также см. Следующую проблему, если вы используете подписанные jar-файлы для включения и столкновения с проблемой с подписями: stackoverflow.com/questions/999489/…
Питер Н. Стейнметц
6
Это не работает. Файл манифеста с этим решением пуст.
Йонас,
4
Мои 2 цента: Лучше установить классификатор, чем менять название. Поместите classifier = 'all' вместо baseName = project.name + '-all'. Таким образом вы сохраните имя артефакта в соответствии с политиками Maven / Nexus.
taciosd
1
Добавьте, group "build"и эта задача будет в buildгруппе (с другими задачами, то есть jarзадачей.
MAGx2
1
Я не могу найти никакой документации по этому with jarключевому слову, что именно оно делает?
Филипп Хеммельмайр
9

У меня это отлично работает.

Мой основной класс:

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

}

И это содержимое моего файла 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
}

И я пишу в консоли следующее:

java -jar ProyectoEclipseTest-all.jar

И вывод отличный:

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
источник
6

Чтобы сгенерировать толстый JAR с основным исполняемым классом, избегая проблем с подписанными JAR, я предлагаю плагин gradle-one-jar . Простой плагин, использующий проект One-JAR .

Легко использовать:

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

Простое решение

jar {
    manifest {
        attributes 'Main-Class': 'cova2.Main'
    } 
    doFirst {
        from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
    }
}
Йонас Майер
источник
5

Ответ от @ben почти работает для меня, за исключением того, что мои зависимости слишком велики, и я получил следующую ошибку

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

  To build this archive, please enable the zip64 extension.

Чтобы решить эту проблему, мне нужно использовать следующий код

mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  
  zip64 = true
  from {
    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  }
}
Алгоритм
источник
1

Для тех, кому нужно собрать больше одной фляги из проекта.

Создайте функцию в 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'
    }
}

затем позвоните:

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

Работает на Gradle 5.

Баночка будет размещена по адресу ./build/libs.

MiguelSlv
источник
0

Я использую задачу shadowJar по плагину. com.github.jengelman.gradle.plugins:shadow:5.2.0

Использование просто запускается ./gradlew app::shadowJar Файл результатов будет по адресуMyProject/app/build/libs/shadow.jar

build.gradleфайл верхнего уровня :

 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.gradleфайл уровня модуля приложения

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
источник
0

Gradle 6.3, библиотека Java. Код из «jar task» добавляет зависимости к «build / libs / xyz.jar» при запуске задачи « gradle build ».

plugins {
    id 'java-library'
}

jar {
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}
Алекс Урече
источник
-1

Если вы привыкли к ant, вы можете попробовать то же самое и с Gradle:

task bundlemyjava{
    ant.jar(destfile: "build/cookmyjar.jar"){
        fileset(dir:"path to your source", includes:'**/*.class,*.class', excludes:'if any')
        } 
}
МИГ
источник