编写一次,随时随地构建

2020-06-30 00:51:00

Java承诺的“一次编写,随时随地运行”仍然让我产生共鸣,也许是因为我对JDK 1.0.2b了如指掌。编写跨平台的网络应用程序时不用担心POSIX实现的变化,不用担心C编译器之间的细微差别,甚至不需要摸索就能写出令人惊叹的东西。其他人可以与#ifdefs搏斗,通过高级别的标准抽象来保护其他人。尽管Smalltalk在1983年就炮制了独立于平台的冲床,但直到1996年,Java的虚拟机(VM)和随后的OEM发行版才轻松实现了跨平台开发。

在战略上-而且是自私的-微软用一把大锤打击了未来:它开始拥抱和扩展Java,以实现供应商锁定,使平台四分五裂。

开发人员不能再依赖具有可用的Java VM的系统。这意味着最终用户必须下载(并安装)两个独立的程序:合适的VM和Java归档。这也意味着开发人员现在面临着曾经令人担忧的选择。给最终用户和开发人员带来负担的选择。要求人们下载适用于其操作系统和CPU架构的虚拟机是产品采用的障碍。更糟糕的是,一些VM现在支持“完整的”Java,而另一些则根本不支持JavaFX模块。

2019年末,甲骨文Java团队的凯文·拉什福斯提出了打包问题的解决方案:jpackage和jlink。在演示期间,他列出了该工具的一些缺点,包括:

让我们直截了当地说:Java开发人员曾经可以在他们喜欢的操作系统上构建应用程序,并在任何地方部署;现在,他们必须为他们打算在这些系统上面向的每个单独的平台构建安装程序。

要求开发人员至少拥有两个商业操作系统(Windows和MacOS X),即使是在虚拟化容器内进行仿真-而不是使用物理硬件-来使用独立于平台的编程语言,这是一个具有讽刺意味的进入障碍。尤其是对那些失去资金的人来说。

是的,这些工具-与模块一起-有助于生成最少的可执行文件。谁在乎啊?它们没有做的是使从一台构建机器创建一组本地的、独立的启动器二进制文件成为可能。让我们通过在一台构建机器上为Java交叉编译应用程序启动器的大问题来解决这个问题。我们将使用Linux,因为它是免费的。

翘曲封隔器的安装说明有点被掩埋了。在Linux系统上安装软件,如下所示(根据需要更改版本号):

如果需要,通过将以下行添加到$HOME/.bashrc,将$HOME/bin包括在PATH环境变量中:

通过直接打开新的终端或源文件.bashrc来应用新的环境设置。

在我们开始构建Java应用程序之前,我们需要了解更多的历史。最初,抽象窗口工具包(AWT)与Java捆绑在一起,原因与拥有捆绑的网络API非常有用的原因相同。AWT在外观和作为图书馆方面都缺乏美感。AWT的继任者Swing大踏步地解决了这两个问题,但没有做到这一点。JavaFX最初也与Java(版本8到10)捆绑在一起,它是用于开发桌面应用程序的现代图形用户界面(GUI)库,也是Swing的绝佳替代品。

有人认为JavaFX不应该与Java捆绑在一起。尽管如此,从Java 11开始解绑JavaFX的决定已经导致Java项目在未来数年内将停留在旧的Java版本上-可能导致它们被放弃-因为要迁移构建过程来支持这些新的、非捆绑的Java开发工具包(JDK)需要付出大量的努力,这是因为要迁移构建过程来支持这些新的、非捆绑的Java开发工具包(JDK)需要付出很大的努力。

这项工作的一部分需要重新捆绑JavaFX。有几种方法可以实现这一点:

这几乎是Hobson的选择,因为无论如何我们都必须为每个目标平台创建启动器二进制文件。这意味着将所有目标平台库捆绑到每个本机启动程序中是浪费的;但是,对于那些已经安装了Java VM的用户来说,将它们捆绑到单个JAR文件中可能会有些帮助。

Java引入了模块的概念,希望简化构建过程。但是,有时由于过时的依赖关系,不能包含模块。对旧依赖项运行jlink时可能会出现问题。使问题变得更加复杂的是,当项目已经日落或发布时间表很长的时候。不过,出于我们的目的,我们希望下载一个支持JavaFX的OpenJDK版本。

我们将为Scrivenvar创建本地应用程序启动器,Scrivenvar是我一直在开发的一个基于JavaFX的文本编辑器。

从技术上讲,没有必要将安装程序脚本的职责与build.gradle分开。在运行第三方可执行文件(例如,WARP-PACKER)时,我的第一个倾向是使用shell脚本,而不是Gradle的exec。

第一个代码片段确定要在JAR文件中包括哪些原生JavaFX库(即目标平台代码):

string[]os=[";win";,";Mac";,";linux";]if(project.hasProperty(';targetOS';)){if(";windows";.equals(TargetOs)){os=[";win";]}Else{os=[targetOS]}}。

除非另有说明,否则构建脚本将创建一个JAR文件,其中包含嵌入式Windows、Linux和MacOS的JavaFX二进制文件。我们可以在构建时使用-P命令行选项针对特定平台,例如:

在这场胜利中有一个陷阱,窗口是不同的名字。安装程序脚本需要使用windows,因为这是正在下载的JDK文件名的一部分。可以说,我们可以将该逻辑移到安装程序脚本中,但重点是JDK文件名和JavaFX使用不同的术语引用Windows。这种差异必须在某个地方被捕捉到。

第二个代码片段告诉构建系统为给定的一组操作系统包含JavaFX,如下所示:

def fx=[';Controls';,';Graphics';,';fxml';,';Swing';]fx.each{fxitem->;os.each{ositem-&>;runtimeOnly";org.openjfx:javafx-${fxitem}:${javafx.version}:${ositem}";}}。

该应用程序使用JavaFX控件、图形、fxml,请注意,它还列出了JavaFX-Swing集成。使用.each是一种整洁的方式,无需为每个操作系统维护特定于JavaFX的导入列表,只要其中一个或两个都需要更改。

如果不从某些与JavaFX相关的依赖项中排除JavaFX,构建将会失败。不幸的是,构建失败的方式如此严重,以至于开发人员必须调查是什么包导致了冲突。理想情况下,构建失败将为开发人员指出任何冲突。

现在我们了解了此构建脚本的工作方式,接下来让我们转到安装程序shell脚本。

纵观全局,安装程序脚本执行以下步骤来创建本机启动程序二进制文件:

构建模板脚本在我的排版标记系列的前三部分中进行了深入描述。我们在这里重用该脚本,因为它简化了用户友好的shell脚本的编写。

下载并提取必要的JDK至关重要。JDK可以从几个地方下载,但大多数都有不可行的缺点。以下是我的调查结果:

Azul-还没有提供OpenJDK14,这很奇怪,因为提供它的同一个页面并没有提供下载选项。

BellSoft和其他OpenJDK供应商可以通过为所有平台提供zip存档来改进。我们稍后再来看看原因。

utilExtract_jre函数根据多个参数为特定目标平台生成下载URL:

如果所有的归档都是以zip格式提供的,我们就不需要在任何地方捕获${ARCHIVE_EXT}的差异。计算机憎恶例外。系统越通用,实现自动化的工作就越少,维护起来也就不那么困难。有一种流行的假设,即Windows系统使用zip文件,而Unix系统使用tar.*z文件。这很奇怪,因为Unzip从1989年起就可以在Unix系统上使用(更新到2009年)。是的,tar。*z文件压缩得更小。那又怎么样?大小上的差异可以忽略不计。小声咆哮完毕。森林和树木,森林和树木。

该脚本将JDK下载到/tmp中,并根据Java版本、目标平台和体系结构使用简化名称。在后续构建中,如果该存档存在,则会重新使用它,而不是重新下载。

要使本机二进制包工作,它需要知道如何启动应用程序。使用Java时,这通常类似于:

对于本机包装器,在概念上没有什么不同,但在实践中我们需要考虑到:

在UTILE_CONFIGURE_TARGET()中设置的ARG_JRE_OS变量值确定创建哪种类型的启动脚本。

在分发目录中创建JAR文件、启动脚本(例如,run.sh或run.bat)和特定于平台的JDK版本之后,我们可以编译本机包装器。

在这样做之前,必须捕捉到另一个细微的差异。Warp Packer使用x64表示64位二进制,而下载文件名使用AMD64。解决了最后一个问题后,封隔器的名称如下:

WARP-PACKER\--ARCH";${ARG_JRE_OS}-${ARG_JRE_ARCH}";\--INPUT_DIR";${ARG_DIR_DIST}";\--EXEC";${FILE_DIST_EXEC}";\--OUTPUT";${APP_NAME}。${APP_EXTENSION}";>;/dev/

如果要查看错误消息,您可能希望删除>;/dev/null。对于喜欢冒险的人,您可以重定向到日志文件,并根据从Warp Packer返回的退出值显示其内容。

${APP_NAME}派生自Java属性文件,因此可以从代码库中的单点更改应用程序名称。将安装脚本迁移到build.gradle脚本将简化应用程序名称的确定。

这两个调用都创建独立的二进制可执行文件,应用程序用户无需安装:只需下载并运行即可。第一个为Windows生成一个可执行文件;第二个为Linux平台生成一个bin文件(只在一个发行版上测试,买家要小心)。MacOS仍然是读者的练习。

为JavaFX构建免安装的本机二进制应用程序启动器的最大浪潮已经消退。在我看来,这种方法牢记Java最初的口号,同时着眼于桌面应用程序未来的持续构建过程:一次编写、随处运行(是的,或者到处测试)。

回到历史的主题,这篇文章的其余部分将讨论这篇博客文章是如何产生的。

我喜欢写作。必须记住角色名称、绰号、物理属性、故事位置、事件时间线、日期中的日期名称等等,这是一件苦差事。角色表有多种形式:电子表格、笔记本、网站,甚至是软件。当我意识到我是一名软件开发人员时,我开始为一本硬科幻小说制作电子表格;我决定改用YAML文档。

似乎没有一个跨平台的应用程序允许用户在编写文本的同时打开分层结构的数据定义文档。此外,一旦建立了层次结构,就能够在嵌入到文档中之前插入这些定义。

鉴于Java是我的第一语言(紧随其后的是英语),这让我找到了一个基于Java的基本Markdown编辑器,我可以用一个面板来扩展它来编辑定义。也就是说,提供了一种轻松编辑和注入文档变量方法。有一位编辑脱颖而出:降价写手FX。

事实证明,许多这样的文本编辑器(Markdown、AsciiDoc等)。重新散列相同的公式,该公式在下图中描述为最上面的框(标记为“Today”):

更多的前期努力,一个更多功能的编辑器已经触手可及:红色的,最下面的方框。在面向对象的术语中,最下面的框代表责任链设计模式。一个处理器接收文档,对其执行一些转换,然后沿行传递新版本,直到生成最终表单:HTML文档。

Markdown Writer FX有以下主要组件:RichTextFX、WebView和Flowless。Markdown编辑器由RichTextFX提供,HTML预览由WebView呈现,滚动条由Flowless提供。具体地说,VirtualizedScrollPane处理WebView的滚动。这种方法有一些缺点。

首先,WebView几乎是一个完整的Web浏览器,带有CSS3解析和JavaScript执行功能。标记文档通常会转换为基本的HTML文档,这些文档既不需要CSS3的全部功能,也不需要任何JavaScript。

其次,相当残酷的是,VirtualizedScrollPane没有提供简单的API来获取其滚动条的句柄。在同步文本编辑器和预览面板滚动条时,这变成了一个棘手的问题。向该类添加这样的功能可能不会实现,因为维护人员已经离开了。

如果没有引用VirtualizedScrollPane的滚动条,同步编辑器和预览窗格似乎难以实现。如果没有Flowless,WebView组件将不再可用。不过,损失不大,因为WebView就像从水族馆里喝水,而用杯子喝就行了。

用HTML查看器替换Web浏览器绝非易事。最终,WebView被以下组件取代:

FlyingSaucer-转换可以在Swing画布上绘制的XHTML文档,并在嵌入到JScrollPane中时进行识别;

对Apache Batik的依赖使构建过程与jlink不兼容。删除依赖关系将意味着矢量图形不再显示。由于硬科幻小说使用的是矢量图形,所以我不想引入外部步骤来首先对图像进行光栅化。

除了jlink不在选项之外,我没有Mac,也不想要,我宁愿不使用MacOSX--更不用说购买了。对于AIX,这是双倍的。不过,如果可以为MacOSX、AIX和其他平台交叉编译独立的二进制文件,那就更好了。多亏了翘曲包装机,才能做到这一点。

一些早期的Beta测试者提到,如果有可以尝试的独立二进制文件,采用率可能会更高。这篇博客帖子源于我想在写作时使用插值字符串的愿望,最终形成了以下应用程序二进制文件:

我的职业生涯涵盖了电信和无线电通信、企业级电子商务解决方案、金融、交通、医疗和教育方面的现代化项目,等等。