Ohhnews

分类导航

$ cd ..
foojay原文

将 JAR 转换为完整的 MacOS 应用程序

#java#macos#jpackage#应用打包#maven

目录

几年前,我开发了一个小型的 Kotlin GUI 来帮助我批量重命名文件。实际上,我用 不同的 JVM 框架 创建了它,以比较它们的相对优缺点。无论如何,直到上周我都没怎么用过它。然后,我惊讶地发现它无法重命名网络卷,尽管过去是可以的。在这篇简短的文章中,我旨在描述这个问题及其解决方案。

问题

启动 UberJAR 时,我看不到网络卷。如果我在字段中输入路径,也看不到任何子节点。我在网上搜索了解决方案,提到需要授予应用程序“本地网络”权限:设置 > 隐私与安全性 > 本地网络。我尝试将 Java SDK 或 UberJAR 添加到列表中,但没有找到方法。

[LOADING...]

解决方案

解决方案很简单:从 UberJAR 创建一个标准的 MacOS 应用程序。最简单来说,MacOS 应用程序只是一个具有特定结构的文件夹。你可以手动复制它,但我很懒,而且有现成的工具。jpackage 就是这样一个工具。jpackage 是我了解过、为之兴奋、用过一次然后直到下次需要才会想起的工具之一。计数加一。

起初,我天真地像这样使用 jpackage

$ bash
jpackage \
  --type app-image \                                  #1
  --input target \                                    #2
  --name RenamerSwing \                               #3
  --main-jar renamer-swing-1.0-SNAPSHOT.jar           #4
  1. 设置值
  2. 要包含的文件夹
  3. 应用名称
  4. UberJAR 的路径

它有效并生成了一个 MacOS 应用。我们可以启动生成的应用,操作系统会询问是否允许本地网络访问。完成。

然而,jpackage 将整个 target 文件夹的内容复制到了应用中。它不仅包含原始 JAR、类,还包含其余的内容。

改进构建

我决定改进 Maven 构建,而不是手动调用 jpackage

第一步是将 UberJAR 移动到一个空文件夹,以便我们可以设置 jpackageinput

$ xml
<plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.3.1</version>
    <executions>
        <execution>
            <id>copy-jar-for-jpackage</id>
            <phase>package</phase>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/jpackage-input</outputDirectory>
                <resources>
                    <resource>
                        <directory>${project.build.directory}</directory>
                        <includes>
                            <include>${project.build.finalName}.jar</include>
                        </includes>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>

下一步实际上是找到一个封装了 jpackage 的 Maven 插件。我找到了 org.panteleyev:jpackage-maven-plugin。以下是使用方法:

$ xml
<plugin>
    <groupId>org.panteleyev</groupId>
    <artifactId>jpackage-maven-plugin</artifactId>
    <version>1.6.5</version>
    <configuration>
        <name>RenamerSwing</name>                     <!--1-->
        <appVersion>${project.version}</appVersion>
        <vendor>Nicolas Fränkel</vendor>
        <destination>target</destination>
        <input>target/jpackage-input</input>          <!--2-->
        <mainJar>${project.build.finalName}.jar</mainJar> <!--3-->
        <mainClass>${main.class}</mainClass>
        <type>APP_IMAGE</type>                        <!--4-->
    </configuration>
    <executions>
        <execution>
            <id>jpackage</id>
            <phase>package</phase>
            <goals>
                <goal>jpackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>
  1. 应用名称
  2. 指向上一步创建的文件夹
  3. 主 JAR 的路径
  4. 设置值

请注意,这些参数模仿了命令行选项。

最后润色

有两个最后润色点:版本和应用的大小。

让我们从版本开始。上述配置使用了 ${project.version}。如果它带有 -SNAPSHOT 后缀,构建就会失败,因为 Mac 应用版本必须遵循 major.minor.bugfix 模式。为了解决这个问题,我们可以利用 build-helper-maven-plugin。它提供了几个目标(goal),包括 parse-version,它可以将 Maven 版本解构为几个属性。

$ xml
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>3.6.0</version>
    <executions>
        <execution>
            <id>parse-version</id>
            <phase>initialize</phase>
            <goals>
                <goal>parse-version</goal>
            </goals>
        </execution>
    </executions>
</plugin>

我们可以用以下内容替换版本行:

$ xml
<appVersion>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}</appVersion>

第二个改进是最终应用的大小。在我的机器上,上述配置生成的应用大小为 160MB。对于基于 JVM 的应用来说,这并不令人担忧,但也算不上好。好在 jpackage 底层利用了 jlinkjlink 创建一个自定义的 JRE。该应用是自包含的。

jpackage 使用所有配置的模块,或者默认使用所有模块。不好的是,我们没有配置任何模块。通过仅添加必要的模块,我们可以将应用大小减少一半,降至 82MB。

$ xml
<plugin>
    <groupId>org.panteleyev</groupId>
    <artifactId>jpackage-maven-plugin</artifactId>
    <version>1.6.5</version>
    <configuration>
        <name>RenamerSwing</name>
        <appVersion>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}</appVersion>
        <vendor>ch.frankel.blog</vendor>
        <destination>target</destination>
        <input>target/jpackage-input</input>
        <mainJar>${project.build.finalName}.jar</mainJar>
        <mainClass>${main.class}</mainClass>
        <type>APP_IMAGE</type>
        <addModules>
            <addModule>java.base</addModule>
            <addModule>java.desktop</addModule>
            <addModule>java.logging</addModule>
        </addModules>
    </configuration>
</plugin>

此时,你可以将应用移动到 Mac 的 Applications 文件夹中。你还可以使用图标等进一步自定义它。对我来说,这已经足够满足我的需求了。

结论

在这篇文章中,我们从一个由于当前 MacOS 安全模型而无法重命名远程卷上文件的 UberJAR 开始。为了使其工作,我们将 UberJAR 封装在一个原生的 MacOS 应用中。然后,我们通过使用显式模块将大小减少了一半,从而改进了这种情况。

这篇文章的完整源代码可以在 GitHub 上找到。

延伸阅读:


最初于 2025 年 1 月 25 日发布在 A Java Geek

本文 从 JAR 到完整的 MacOS 应用 首次出现在 foojay 上。