Skip to main content

使用Gradle的JavaPackager插件将Java应用打包成二进制文件

· 6 min read
orange
programmer on jvm platform

在之前的文章中, 我介绍过如何通过graalvmjava应用打包成二进制文件, 但是这种方式需要在graalvm中安装native-image 工具, 并且需要在graalvm中编译java应用, 这样的方式对于java应用的开发者来说, 有一定的门槛, 而且也不够灵活. 并且构建过程中由于代码没有满足graalvm的要求, 例如使用了java的反射机制, 会导致构建失败( graalvm需要在编译时就知道这些信息来生成) 下面将介绍另一种方式, 通过gradleJavaPackager插件来构建二进制文件.

添加插件

要启用此插件, 需要在build.gradle.kts中添加如下配置:

note

由于该插件没有发布到gradle的插件仓库中, 所以需要在buildscript中添加对应的依赖并通过apply来启用该插件

build.gradle.kts
buildscript {
repositories {
maven { setUrl("https://mirrors.huaweicloud.com/repository/maven/") }
maven { setUrl("https://mirrors.tencent.com/nexus/repository/maven-public/") }
maven { setUrl("https://maven.aliyun.com/nexus/content/groups/public/") }
maven { setUrl("https://oss.sonatype.org/content/repositories/snapshots") }
}
dependencies {
classpath("io.github.fvarrui:javapackager:1.7.0")
}
}

apply(plugin = "io.github.fvarrui.javapackager.plugin")

插件配置

linux为例, 介绍如何配置JavaPackager插:

build.gradle.kts
configure<PackagePluginExtension>() {
mainClass("<MAIN-CLASS>")
platform(Platform.linux)
packagingJdk(file(System.getProperty("java.home")))
// arch(Arch.x64) not work
// arch is not configured in DefaultPackageTask(导致自定义arch失效)
// packagingJdk没有设置默认值(导致jink定位到了/bin目录下而不是用户的jdk的home目录下的bin目录)
}

将上面的的模板替换为自己的mainClass即可, 其他的配置可以根据需要进行修改.

构建

执行如下命令即可构建:

./gradlew package -x test

已知问题

packagingJdk不会自动推断导致打包失败

执行过程中出现如下错误

 /bin/sh: 1: /bin/jdeps: not found

问题原因

该问题的原因是JavaPackager插件在执行jdeps命令时会根据packagingJdk配置的值拼接/bin/jdeps来执行, 但是由于没有配置packagingJdk的值, 所以默认使用了/bin/jdeps来执行, 但是/bin/jdeps并不存在, 所以导致了上面的错误.

相关代码在io.github.fvarrui.javapackager.packagers.BundleJre类的doApply方法中, 这个方法中会调用jdeps命令来获取依赖信息.

解决方案

解决方案就是在上面的配置中添加packagingJdk的配置packagingJdk(file(System.getProperty("java.home"))).

jdeps命令执行失败报错Module xxx not found, required by xxx

在我的case中报错为:

Module org.yaml.snakeyaml not found, required by com.fasterxml.jackson.dataformat.yaml

问题原因

这个问题的原因通常是因为被依赖的模块版本需要升级到支持java9模块化之后的版本

解决方案

在我的case中是org.yaml.snakeyaml需要升级, 在build.gradle.kts中添加如下配置后问题解决:

build.gradle.kts
implementation("org.yaml:snakeyaml:2.0")

jdeps命令执行失败报错java.util.concurrent.ExecutionException: com.sun.tools.jdeps.MultiReleaseException

这个问题的原因是因为jdk17中的jdeps命令存在bug(无法处理不同jar中具有相同名称的类)导致的, 在jdk18中已经修复了这个问题.

关于这个问题的更多信息可以参考

构建过程中出现

java.lang.Exception: https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImagenot found! ... Unsupported OS architecture x86_64?

这个问题的原因是在打包linuxAppImage时, 会去下载appimagetool来进行打包, 下载过程中可能会出现网络问题导致下载失败, 从而导致打包失败. 但是作者捕获相关异常后没有将原始信息打印出来, 导致我们无法知道具体的错误信息, 从而导致了上面的错误. (这个异常信息是伪信息)

相关代码在io.github.fvarrui.javapackager.packagers.GenerateAppImage中的getAppImageTool方法中.

解决方案

重试后问题解决.对于这个问题后续计划提PR给作者.

arch配置不生效导致生成出的deb文件无法安装

执行完package任务后, 会在build目录下生成相应的deb文件名称为xxx.deb. 对此文件执行sudo dpkg -i xxx.deb命令会出现如下错误:

dpkg: warning: parsing file '/var/lib/dpkg/tmp.ci/control' near line 5 package 'upshift:${info.arch.deb}':
'${info.arch.deb}' is not a valid architecture name in 'Architecture' field: must start with an alphanumeric
dpkg: error processing archive upshift_1.0-SNAPSHOT.deb (--install):
package architecture (${info.arch.deb}) does not match system (amd64)
Errors were encountered while processing:
upshift_1.0-SNAPSHOT.deb

后续在gradle中的task配置arch(Arch.x64)依然不生效.

问题原因

这个问题的原因是JavaPackager插件对应的gradle中的实现类是io.github.fvarrui.javapackager.gradle.DefaultPackageTask, 此插件并没有获取我们配置的PackagePluginExtension中的值. 后续io.github.fvarrui.javapackager.packagers.GenerateDeb会调用velocity来渲染control.vtl. 由于arch为空, 所以导致${info.arch.deb}没有被渲染, 模板内容如下

Package: ${info.name}
Version: ${info.version}
Section: misc
Priority: optional
Architecture: ${info.arch.deb}
Maintainer: ${info.organizationName} <$!{info.organizationEmail}>
Description: ${info.description}
Distribution: development
#if(${info.url})
Homepage: ${info.url}
#end

解决方案

目前没有一个好的办法解决此问题, 后续准备在JavaPackager插件中提一个PR来解决此问题.

备注

解压AppImage

./xxx.AppImage --appimage-extract

参考资料