如何使用Gradle将可执行的Jar文件创建为可执行文件?

huangapple go评论72阅读模式
英文:

How To Create An Executable Out Of A Executable Jar File Using Gradle?

问题

我正在使用Gradle来构建我的应用程序(一个Python编译器)。我之前已经用C++写过一个类似的,但考虑用Java做一个更好的版本,因为我在Java方面有更多的知识。我正在使用Gradle进行构建。到目前为止,这是我的项目结构:

.
├── build.gradle
├── gradle
│    └── wrapper
│        ├── gradle-wrapper.jar
│        └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │    ├── java
    │    │    └── compiler
    │    │        ├── Lexer.java
    │    │        ├── Main.java
    │    │        └── TableEntry.java
    │    └── resources
    └── test
        ├── java
        │    └── compiler
        │        └── MainTest.java
        └── resources

其中,LexerTableEntry 文件并不重要。下面是我的 build.gradle 文件的内容:

repositories {
    mavenCentral()
}

apply plugin: 'java'

version '0.1.0'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'

jar {
    manifest {
        attributes 'Implementation-Title': 'python',
        'Implementation-Version': version, 
        'Main-Class': 'compiler.Main'
    }
}

dependencies {
    implementation 'com.google.guava:guava:29.0-jre'

    testImplementation 'junit:junit:4.13'
}

task deleteBin(type: Delete) {
    doLast {
        new File('bin').deleteDir()
    }
}

tasks.clean.dependsOn(tasks.deleteBin)

tasks.jar.doLast {
    copy {
        from 'build/libs'
        into 'bin'
    }
}

这个脚本的目标是将 Main.java 文件构建为一个可执行的 JAR 文件,并将其复制到名为 bin 的目录中。通过运行 ./gradlew build 后,项目结构会变成这样:

.
├── bin
│    └── python-0.1.0.jar
├── build
│    ├── classes
│    │    └── ...
├── build.gradle
├── gradle
│    └── wrapper
│        ├── gradle-wrapper.jar
│        └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │    ├── java
    │    │    └── ...
    └── test
        ├── java
        │    └── ...

但是我在考虑使用我的编译器的用户需要安装Java才能实际运行它。在C++中,它已经将其编译为可执行文件,这很方便。通过一些研究,我找到了一个叫做 launch4j 的插件,但似乎无法使其工作。除了 launch4j,我还有哪些选项可以将我的JAR文件转换为可执行文件?

对不起,我没有解释为什么 launch4j 不起作用。我去了 GitHub 仓库,上面说要添加以下插件:

'edu.sc.seis.launch4j'

我添加了这行代码,但Gradle 无法识别它。我添加的那行代码:

apply plugin: 'edu.sc.seis.launch4j'

输出结果:

Plugin with id 'edu.sc.seis.launch4j' not found.

然后,我去 Maven 中央仓库搜索,发现它可以作为依赖项添加到我的 dependencies 部分:

implementation 'edu.sc.seis.gradle:launch4j:1.0.6'

然而,当我尝试像这样编辑 launch4j 任务时:

launch4j {
}

它说找不到该任务。

Could not find method launch4j() for arguments [build_c92xwmoykapsewu0cy351y5ww$_run_closure4@5635a004] on root project 'python' of type org.gradle.api.Project.
英文:

I'm using gradle for my application (a python compiler). I already wrote one before in C++, but I'm thinking of doing another better on in java as I have more knowledge in Java. I'm using gradle to build. So far, here is what my project looks like:

.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── compiler
    │   │       ├── Lexer.java
    │   │       ├── Main.java
    │   │       └── TableEntry.java
    │   └── resources
    └── test
        ├── java
        │   └── compiler
        │       └── MainTest.java
        └── resources

The Lexer and TableEntry files are not important. Here is what my build.gradle file looks like:

repositories {
    mavenCentral()
}

apply plugin: 'java'

version '0.1.0'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'

jar {
    manifest {
        attributes 'Implementation-Title': 'python',
        'Implementation-Version': version, 
        'Main-Class': 'compiler.Main'
    }
}

dependencies {
    implementation 'com.google.guava:guava:29.0-jre'

    testImplementation 'junit:junit:4.13'
}

task deleteBin(type: Delete) {
    doLast {
        new File('bin').deleteDir()
    }
}

tasks.clean.dependsOn(tasks.deleteBin)

tasks.jar.doLast {
    copy {
        from 'build/libs'
        into 'bin'
    }
}

What this is supposed to do is build the Main.java file into an executable jar and copy it into a directory called bin. Which it does do. After I call ./gradlew build, this is what the tree looks like:

.
├── bin
│   └── python-0.1.0.jar
├── build
│   ├── classes
│   │   └── java
│   │       ├── main
│   │       │   └── compiler
│   │       │       ├── Lexer.class
│   │       │       ├── Main.class
│   │       │       └── TableEntry.class
│   │       └── test
│   │           └── compiler
│   │               └── MainTest.class
│   ├── generated
│   │   └── sources
│   │       ├── annotationProcessor
│   │       │   └── java
│   │       │       ├── main
│   │       │       └── test
│   │       └── headers
│   │           └── java
│   │               ├── main
│   │               └── test
│   ├── libs
│   │   └── python-0.1.0.jar
│   ├── reports
│   │   └── tests
│   │       └── test
│   │           ├── css
│   │           │   ├── base-style.css
│   │           │   └── style.css
│   │           ├── index.html
│   │           └── js
│   │               └── report.js
│   ├── test-results
│   │   └── test
│   │       └── binary
│   │           ├── output.bin
│   │           ├── output.bin.idx
│   │           └── results.bin
│   └── tmp
│       ├── compileJava
│       │   └── source-classes-mapping.txt
│       ├── compileTestJava
│       │   └── source-classes-mapping.txt
│       └── jar
│           └── MANIFEST.MF
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── compiler
    │   │       ├── Lexer.java
    │   │       ├── Main.java
    │   │       └── TableEntry.java
    │   └── resources
    └── test
        ├── java
        │   └── compiler
        │       └── MainTest.java
        └── resources

But I was thinking that people who use my compiler will need java installed to actually use it. In C++, it already compiles it into an executable, which is convenient. With a little research, I found a plugin called launch4j, but I couldn't seem to get it to work. What other options do I have to covert my jar file into an executable?

Sorry for not explaining why launch4j doesn't work. So I went to the github repository where it said to add this plugin:

'edu.sc.seis.launch4j'

I added that, and gradle didn't recognize it. The line I put:

apply plugin: 'edu.sc.seis.launch4j'

The output:

Plugin with id 'edu.sc.seis.launch4j' not found.

So then I went to the maven central repository search, and found it again as a dependency over here. It got added fine when I added this line to my dependencies section:

implementation 'edu.sc.seis.gradle:launch4j:1.0.6'

However, when I tried editing the launch4j task like this:

launch4j {
}

It said that it could not find that task.

Could not find method launch4j() for arguments [build_c92xwmoykapsewu0cy351y5ww$_run_closure4@5635a004] on root project 'python' of type org.gradle.api.Project.

答案1

得分: 1

launch4j还不错。有点奇怪的是,你找到了一个完全符合你需求的工具,但你只说了一句:“...它不起作用”。

以下是一些关于launch4j的注释,可能会对你有所帮助:

  • 当然,这只适用于Windows操作系统。
  • 你仍然需要打包整个虚拟机(VM)。它永远不会成为单个可执行文件;Java不是那种类型的工具。
  • 你需要一个安装程序,将由launch4j创建的可执行文件(exe文件)+虚拟机(VM)+所有依赖项放在一个位置。

最终你会得到类似这样的东西:

C:\Program Files\MyApp\myapp.exe
C:\Program Files\MyApp\jvm\(这里有许多文件;整个JRE/JDK安装)
C:\Program Files\MyApp\lib\dep1.jar
C:\Program Files\MyApp\lib\dep2.jar

当然,依赖项是可选的,也可以被整合进来。

然后,你可以告诉launch4j使用那个虚拟机,而不是已经安装的虚拟机(如果有的话)。这也有一个方便的附带效果,与当前的桌面Java应用程序运行模式兼容(应用程序供应商负责VM分发,而不是Oracle+终端用户,这是直到Java 8之前的工作方式),并且你会准确地知道你正在运行的Java版本。

从Java 11开始,你可以使用jlink来创建“剪枝”定制的虚拟机:这些虚拟机仅包含你实际使用的VM部分。你需要切换到模块化构建(这涉及创建一个module-info.java文件 - 如果你真的想采用这种方法,可以找到许多教程)。

我不知道你是否可以使这些“剪枝”虚拟机与launch4j一起工作,但你应该是可以的。

我不知道是否有适用于Linux和Mac的类似解决方案。

英文:

launch4j is fine. It's a bit odd that you found a tool that describes precisely what you want, and all you have to say about it is: "... it doesn't work".

A few notes to help you out with launch4j:

  • This is, of course, solely for windows.
  • You still need to ship an entire VM. It's never going to be a single executable; java is not that kind of tool.
  • You'd need an installer that places the exe (made by launch4j) + a VM + all your deps in a single location.

You'd end up with something like:

C:\Program Files\MyApp\myapp.exe
C:\Program Files\MyApp\jvm\(a bunch of files here; an entire JRE/JDK installation)
C:\Program Files\MyApp\lib\dep1.jar
C:\Program Files\MyApp\lib\dep2.jar

The dependencies are of course optional and can be striped in too.

You can then tell launch4j to use that VM and not whatever's already installed (if any). This also has the convenient side-effect of being compatible with the current 'model' of running java apps on desktops (which is that you, the app vendor, are responsible for VM distribution and not oracle+the end user, which was how it more or less worked until java8, which is outdated), and also that you know precisely which version you're running on.

Starting with java 11 you can use jlink to make 'treeshaken' custom VMs: These contain juuust the bits of the VM you actually end up using. You do need to switch to a modular build (which involves making a module-info.java file - plenty of tutorials to be found if you really want to go this route).

I have no idea if you can make such treeshaken VMs work together with launch4j, but you should be able to.

I am not aware of any solutions for linux and mac.

huangapple
  • 本文由 发表于 2020年7月24日 04:54:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/63062979.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定