使用Gradle构建带有依赖的JAR文件

165

我有一个多项目构建,我在其中一个子项目中放置了一个构建fat JAR的任务。我创建的任务类似于这个cookbook中描述的任务。

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

运行时会出现以下错误:

原因:您无法更改未解决状态的配置!

我不确定这个错误是什么意思。我还在Gradle JIRA上报告了这个问题,以防它是一个错误。


1
有关 Kotlin DSL(build.gradle.kts)的完整答案,请参见此帖子 - Mahozad
1
这个回答解决了你的问题吗?使用Gradle创建可运行的JAR - Mahozad
https://www.baeldung.com/gradle-fat-jar - k4dima
19个回答

254

我在 Gradle 的 JIRA 上发布了 一个解决方法

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

请注意,在jar {之前必须出现mainClassName


5
我必须将其修改为"configurations.runtime.collect"以适应我的项目,因为我也有运行时依赖项。 - vextorspace
9
为了让代码正常工作,我不得不添加 def mainClassName。 我一直收到“无法为根项目设置未知属性'mainClassName'”的错误提示。 - hanskoff
3
你如何处理文件名冲突?在不同的JAR包中,同一路径下的文件将会被覆盖。 - wst
11
很遗憾,这个方法已经行不通了。我使用的是gradle 4.10版本以及新的implementation配置,而不是现在已经弃用的compile配置。上面的代码可以让我构建一个没有依赖关系的小jar包。但是如果我更改它(from { configurations.implementation.collect {...} }),就会出现错误,提示直接解决implementation配置是不允许的。 - Bastian Voigt
6
configurations.compileClasspath会修复所有的implementation依赖,但据我所知不包括api依赖。在另一个答案中发现了解决方案runtimeClasspath,它包括api依赖。 - rekire
显示剩余8条评论

84

@felix的答案几乎把我带到了目的地,但我遇到了两个问题:

  1. 在Gradle 1.5中,manifest标签在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


3
这也解决了我遇到的一个问题,其中我的某个依赖JAR已经签名。签名文件被放置在我的JAR的META-INF目录中,但签名不再匹配内容。 - Flavin
2
特别感谢“artifacts”:正是我所需要的。 - AlexR
当你运行 gradle fatJar 时,似乎没有编译运行时依赖项,因此无法复制。 - mjaggard
最佳答案! - Giri

66

如果你想让jar任务正常工作并且再添加一个额外的fatJar任务,可以使用以下命令:

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

重要的部分是 with jar。如果没有它,该项目的类将不会被包含。


1
如果您正在使用签名的JAR包并遇到签名问题,请参阅以下问题:https://dev59.com/qHNA5IYBdhLWcg3wWsUA - Peter N. Steinmetz
6
这个方法不起作用。使用这个解决方案,清单文件是空的。 - Jonas
4
我的建议是:与其改变名称,不如设置一个分类器。将分类器设置为“all”,而不是使用“project.name + '-all'”作为基本名称,这样可以遵守Maven/Nexus政策,同时保持构件名称的一致性。 - taciosd
1
添加group "build",这个任务将会在build组中(和其他任务一起,例如jar任务)。 - MAGx2
4
我找不到关于 with jar 关键字的任何文档,它具体是做什么的? - Philipp Hemmelmayr
显示剩余3条评论

24

由于现在使用compile列出依赖项的方法已经被弃用,所有人都应该切换到implementation,因此构建包含所有依赖项的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)

这里是完整的Gradle章节: [1]: 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) }
}}

1
赞成官方文档参考。 - wonsuc

10

这对我来说很好用。

我的主要类:

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

8

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

7
为了生成一个带有可执行主类的fat 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'
}

6

简单解决方案

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

6

基于@blootsvoets提出的解决方案,我这样编辑了我的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"
  }
}

4
我使用下面的脚本适用于Gradle 7.3.3。它解决了我在尝试实现这个问题的解决方案时遇到的错误和异常。
jar {
    manifest {
        attributes(
                "Main-Class": "path.to.main.Application",
        )
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

解决了我遇到的重复问题。谢谢! - Austin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接