使用 Gradle 构建具有依赖关系的 JAR

我有一个多项目构建,我把一个任务放在一个子项目中构建一个胖 JAR。我创建了一个类似于 这本烹饪书中描述的的任务。

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

运行它会导致以下错误:

原因: 你不能改变一个 配置不在 悬而未决的状态!

我不确定这个错误是什么意思。

218704 次浏览

我在吉拉对格拉德尔发表了 一个解决方案:

// 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 {之前。

如果您希望 jar任务运行正常,并且还有一个额外的 fatJar任务,请使用以下方法:

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

重要的部分是 with jar。没有它,这个项目的类不包括在内。

这对我来说没问题。

我的主要课程:

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

简单的清洗

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

如果你习惯了蚂蚁,那么你也可以尝试同样的方法:

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

@ Felix 的回答几乎把我带到了那里,我有两个问题:

  1. 使用 Gradle 1.5,在 fatJar 任务中无法识别清单标签,因此不能直接设置 Main-Class 属性
  2. Jar 有冲突的外部 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

为了生成带有主可执行类的胖 JAR,避免带有签名 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'
}

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

对于那些需要从项目中构建多个 jar 的用户。

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

在五年级工作。

罐子将放置在 ./build/libs

我通过 plugin < code > 使用任务 shadowJar。 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()
}


在运行“ 梯度结构”任务时,来自“ jar 任务”的代码将依赖关系添加到“ build/libs/xyz.jar”中。

plugins {
id 'java-library'
}


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

排除不需要的 Manifest 条目修复了 MainClass 文件中未找到的 Gradle 构建 jar 文件错误。

jar{
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
from {
-----
}
}

基于@blotsvoets 提出的解决方案,我这样编辑了 jar 目标:

jar {
manifest {
attributes('Main-Class': 'eu.tib.sre.Main')
}
// Include the classpath from the dependencies
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
// This help solve the issue with jar lunch
{
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
}

因为使用 编译来列出依赖项现在已经不被推荐,所有的依赖项都应该切换到 实施,所以构建一个包含所有依赖项的 Jar 的解决方案应该使用本网站的示例。

Https://docs.gradle.org/current/userguide/working_with_files.html#sec:creating_uber_jar_example

特别是这个命令:

configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it)

以下是全年级部分: 译自: 美国《科学》杂志网站(http://docs.gradle.org/current/userguide/working _ with _ files.html)原文链接: http://docs.gradle.org/current/userguide/working _ with _ files.html # sec: create _ uber _ jar _ example”rel = “ noReferrer”> https://docs.gradle.org/current/userguide/working_with_files.html#sec:creating_uber_jar_example

task uberJar(type: Jar) {
archiveClassifier = 'uber'


from sourceSets.main.output


dependsOn configurations.runtimeClasspath
from {
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}}

关于这种解决方案,有一点需要记住:

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

只要你使用“编译”依赖项,它就可以工作。如果你使用“实现”依赖项,它就不能工作。

有无缝设置的梯度插件阴影罐。

plugins {
id "com.github.johnrengelman.shadow" version "5.0.0"
}


shadowJar {
mergeServiceFiles()
}


请在这里查看与你的分级版本的兼容性: Https://github.com/johnrengelman/shadow#latest-test-compatibility

如果“编译”和“实现”不工作,请尝试“ runtimeClasspath”。

jar {
manifest {
attributes "Main-Class": "com.example.app"
}


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

我使用下一个脚本的等级7.3。它解决了我在试图实现这个问题的解决方案时遇到的错误和异常。

jar {
manifest {
attributes(
"Main-Class": "path.to.main.Application",
)
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

这是用于 Kotlin DSL(build.gradle.kts)的。

方法1(不需要 application或其他插件)

tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
// OR another notation
// manifest {
//     attributes["Main-Class"] = "com.example.MyMainClass"
// }
}

如果使用任何外部库,请使用以下代码。复制结果 JAR 所在位置的 Libs子目录中的库 JAR。确保库 JAR 文件的文件名中不包含空格。

tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
manifest.attributes["Class-Path"] = configurations
.runtimeClasspath
.get()
.joinToString(separator = " ") { file ->
"libs/${file.name}"
}
}

注意,Java 要求我们对 Class-Path属性使用相对 URL。因此,我们不能使用 Gradle 依赖关系的绝对路径(它也很容易被更改,而且在其他系统上不可用)。如果您想使用绝对路径,也许 这个变通方案可以工作。

使用以下命令创建 JAR:

./gradlew jar

默认情况下,结果 JAR 将在 Build/libs/目录中创建。

方法2: 在结果 JAR (fat 或 uber JAR)中嵌入库(如果有的话)

tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
val dependencies = configurations
.runtimeClasspath
.get()
.map(::zipTree) // OR .map { zipTree(it) }
from(dependencies)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

创建 JAR 与前面的方法完全相同。

方法3: 使用 阴影插件(创建一个胖的或超级的 JAR)

plugins {
id("com.github.johnrengelman.shadow") version "6.0.0"
}
// Shadow task depends on Jar task, so these will be reflected for Shadow as well
tasks.jar {
manifest.attributes["Main-Class"] = "org.example.MainKt"
}

使用以下命令创建 JAR:

./gradlew shadowJar

有关配置插件的更多信息,请参见 影子文件

运行创建的 JAR

java -jar my-artifact.jar

上述解决方案经过以下测试:

  • 爪哇17
  • Gradle 7.1(对于 。 kts构建脚本使用 Kotlin 1.4.31)

见官方 用于创建 uber (fat) JAR 的 Gradle 文档

有关清单的详细信息,请参阅 Oracle Java 文档: 使用 Manifest 文件

请注意,您的资源文件将自动包含在 JAR 文件中(假设它们位于构建文件中的 /src/main/resources/目录或任何自定义目录集中,作为资源根目录)。要访问应用程序中的资源文件,请使用以下代码(请注意名称开头的 /) :

  • 科特林
    val vegetables = MyClass::class.java.getResource("/vegetables.txt").readText()
    // Alternative ways:
    // val vegetables = object{}.javaClass.getResource("/vegetables.txt").readText()
    // val vegetables = MyClass::class.java.getResourceAsStream("/vegetables.txt").reader().readText()
    // val vegetables = object{}.javaClass.getResourceAsStream("/vegetables.txt").reader().readText()
    
  • var stream = MyClass.class.getResource("/vegetables.txt").openStream();
    // OR var stream = MyClass.class.getResourceAsStream("/vegetables.txt");
    var reader = new BufferedReader(new InputStreamReader(stream));
    var vegetables = reader.lines().collect(Collectors.joining("\n"));