跳到主要内容

JEP 138:基于 Autoconf 的构建系统

概括

引入 autoconf(./configure-style)构建设置,重构 Makefile 以删除递归,并利用JEP 139:增强 javac 以提高构建速度

目标

我们试图实现的最高目标是:

  1. 从根本上提高构建速度
  2. 简化构建系统源代码(Makefile_等_)
  3. 简化开发人员的工作
  4. 获得准确且可重复的构建输出
  5. 简化构建机器配置(JPRT_等_)

我们将通过四个子项目来实现这些目标,这些项目或多或少紧密地交织在一起。

  1. 更新Makefile结构
  2. 使用autoconf(配置脚本)
  3. 添加并行 Java 编译支持
  4. 使 Java 构建增量

我们需要正确理解现有的开发人员工作流程,以便能够最大限度地减少这一变化对每个人的影响。

该项目是改进 JDK 构建基础设施的更大努力的一部分。我们预计未来的步骤将密切关注该项目。这些步骤之间的区别有些随意,只是为了快速从改进 JDK 构建基础结构的第一个工作中受益。

非目标

由于我们将使用新的结构更新 Makefile,因此我们希望在未来解决的几个问题可能会因为更新而自行发挥作用。然而,我们在这个项目中并没有专门解决这些问题,我们不会测试它们,也不保证它们能够正常工作。 (但是,我们将尽力确保不会破坏任何有效的东西。)这些问题包括:

  • 轻松移植到新平台
  • 使得在没有网络连接的情况下进行JDK开发成为可能
  • 为交叉编译提供适当的支持,包括在 64 位主机上编译 32 位二进制文件
  • 改进警告的处理

我们也不会解决计划在未来步骤中解决的问题。 (但是,其中一些工作将为未来的改进奠定基础。)这些问题包括:

  • 加速热点编译
  • 升级编译器
  • 支持IDE项目
  • 重新考虑源丢弃机制

成功指标

构建简单性

鉴于所有先决条件均已具备,构建应通过以下方式完成:

  1. 从 Mercurial 存储库获取源代码
  2. ./configure
  3. make

构建速度

构建速度取决于硬件因素,并且改进会有所不同。我们的目标是在 8 路机器上的 Linux 上进行编译。这样的话,我们改进后构建JDK所花费的时间最多应该是当前时间的33%。 (通常这意味着从约 15 分钟缩短到约 5 分钟,或更短)。一个延伸目标是 JDK 的构建时间最多应为 20%(约 3 分钟)。

请注意,这仅适用于 JDK。它不包括构建 Hotspot,也不包括创建 Javadoc。

生成文件清理

JDK 中的所有小型 (<3 kB) 递归 Makefile(不包括 Hotspot)均应删除,并将功能收集到中央 Makefile 中。 (少量 Makefile 本身并不是目标,但是,将代码放在一个(或几个)位置有助于概览和理解。)

动机

构建完整的 JDK 不必要地缓慢。这给开发人员和构建系统带来了额外的负担。因此,开发人员只检查并构建部分源代码,因为整个产品的构建时间太长。

构建系统的当前实现有超过 350 个最小的递归 Makefile 分散在产品各处,因此很难对构建系统进行更改。当前的解决方案有时还需要更新 Makefiles 只是为了添加新的源文件或目录;不需要这样做。

今天,构建系统是通过使用多个环境变量来配置的。这与用于./configure设置构建系统的流行方法形成对比。除了熟悉之外,这比环境变量还有几个好处。检查配置的参数——拼写错误的参数会导致错误,而拼写错误的环境变量将被忽略。./configure --help显示可用参数的列表,而几乎不可能获得影响当前构建系统的所有环境变量的完整列表。

描述

这些更改不会导致构建的产品发生任何变化;它们只影响内部开发过程。

更新Makefile结构

背景

将旧的 Makefile 更新为新的、简化的架构将是此处描述的所有其他工作的基础。

执行

当前每个目录一个文件的递归 makefile 风格将被删除。相反,makefile 将通过递归查看源代码目录来发现要编译的文件。不应编译的文件将被列为显式排除项。这将需要能够使用新的并行 javac 编译器。

多个子系统通用的代码将存储在新的顶级目录“common/make”中。设计思想是这些通用文件将提供一个具有辅助函数的库,以便每个子系统的 Makefile 可以尽可能简单、干净地编写。如果这些库允许提高每个子系统 Makefile 的简单性,我们将接受更高的代码复杂性。

由于良好的编码实践不会由 Makefile 语法自动强制执行,因此我们将格外小心,以确保编写正确且可读的代码。

作为更新的一部分,我们将生成一份文档,描述我们发现有用并在重写过程中遵循的编码指南,以指导 Makefile 的未来更改。我们还将生成一个描述 Makefile 整体架构的文档。

除了构建生成的二进制文件或构建二进制文件的异常变体之外,Makefile 还执行其他操作。其中一些目标看起来很神秘并且不再使用。如果所有利益相关者都同意,那么我们不会将这些目标移植到新系统中。这是我们迄今为止正在考虑删除的功能列表:

  • (目前为空)

新旧混合

可能可以保留旧的 Makefile 系统,与新重写的 Makefile 系统并行,因此我们在一段时间内有两种构建产品的方法(新的和旧的)。这并不是真正可取的,因为它有导致代码重复和普遍混乱的风险,并且会让我们错过删除旧东西的好处。然而,保留旧系统,或者有一种简单的方法来恢复旧系统,将有助于我们管理所涉及的风险。

过渡

大多数开发人员不会与实际的 makefile 进行太多交互,因此工作流程不会有任何大的变化。

以前,有时每当添加或删除源文件或目录时都需要更新 Makefile。这将不再需要,并且需要将其传达给所有开发人员。

想要更改实际 Makefile 的开发人员需要了解所使用的整体设计和编码原则。这将被记录下来,但需要传达这些文件的存在。

使用autoconf(配置脚本)

背景

autoconf 背后的基本思想是,一个简单的界面将处理用户系统配置和 Makefile 要求之间的“粘合”问题。这个接口就是./configureshell脚本。

因此,使用 autoconf 有两个方面:创建和使用./configure脚本。配置脚本由 autoconf 工具根据configure.ac(以及随附的帮助程序文件)中的源代码生成,该脚本是使用 M4 宏编写的。从该源代码configure生成一个 shell 脚本。该脚本(即使已生成)已签入存储库。每当configure.ac源代码发生更改时,configure都需要重新生成脚本并在存储库中更新。要重新生成configure,需要在系统上安装 autoconf 工具。

然而,典型的用户不需要这样做。既然configure签到了,他/她只需要运行即可./configure。为此,不需要 autoconf 工具。这会产生一个config.spec采用 Makefile 语法的文件,该文件确定构建详细信息,并包含在 Makefile 中。

自动配置的实现

配置脚本有三个主要任务:

  1. 确定所有构建依赖项都存在。
  2. 分析平台之间的已知差异并确定哪些适用于当前情况。
  3. 应用用户给出的参数来专门化构建。

尽管 autoconf 框架有助于完成所有这些任务,但它们都必须使用有关 OpenJDK 细节的知识进行显式编码。这意味着我们需要清楚我们实际拥有哪些构建依赖项、需要确定哪些差异以及用户可以通过哪些方式影响构建结果。

构建依赖关系之前已在 README 文件中进行了描述。

已知的差异之前已编码在 Makefile 中,或者已在“常识”中。

历史上,用户影响是通过使用环境变量来实现的,并且对这些变量的检查是在 Makefile 中进行的。

配置脚本可以像旧 Makefile 的“包装器”一样工作,并在 config.spec 中设置与 Makefile 所使用的相同的变量。在这种情况下,对于 Makefile 来说,变量来自配置脚本而不是用户几乎是透明的。然而,在许多情况下,更好的解决方案可能是输出更“干净”的变量,并重写 Makefile 的相应部分。

作为使用 autoconf 的一部分,我们需要将 autoconf 中的三个文件包含在 JDK 8 源存储库中。这三个文件是pkg.m4config.guessconfig.sub。已请求将这些文件包含在 OpenJDK 中的法律许可。我们认为这应该不是问题,因为 autoconf 许可证是明确编写的以支持此用例(基本上允许我们以任何我们喜欢的方式分发它们,只要它们用作配置脚本的一部分)。

过渡

当前构建 OpenJDK 的工作流程基本上是:

  1. 从存储库中检索源代码
  2. 设置一系列环境变量
  3. 运行make
  4. 每次需要重建时重复 2 和 3

许多团队成员创建了个人 shell 脚本和类似的解决方案来帮助解决此问题。

使用配置脚本的新工作流程将导致:

  1. 从存储库中检索源代码
  2. 运行./configure,可能带有专门的参数
  3. 运行make
  4. 每次需要重建时重复 3

由于步骤 3 非常简单,因此不需要 shell 脚本来重建。但是,如果用户的设置非常专业,他们可能希望创建脚本来帮助他们使用正确的参数运行配置。

我们应该提供一个从旧环境变量到新参数的转换表configure

**讨论:**也许我们应该在运行configure/make时检查一些常用的旧式环境变量,并提醒用户?

使用支持并行编译的服务器模式加速javac

对于JEP 139:增强 javac 以提高构建速度,我们将为 javac 编写一个扩展,它将支持并行编译。要使用它,我们必须在 makefile 中添加对它的支持。

过渡

将 Java 编译切换为使用 javac 服务器不会对开发人员产生明显的影响(当然,除了主要的加速之外)。不需要过渡计划。

通过使用依赖输出增强 javac 使 Java 构建增量

Make 能够进行增量构建,即在发生更改时仅重新编译所有文件的子集。理想情况下,该子集应该是所需的最小子集。为此,make 需要以它可以使用的格式提供可用的依赖项信息。

对于JEP 139:增强 javac 以提高构建速度,我们将编写 javac 的扩展,该扩展将允许增量构建 Java 代码。要使用它,我们必须在 makefile 中添加对它的支持。

过渡

开发人员无需任何特定操作即可使用增量构建。理论上,开发人员唯一明显的区别应该是重新编译时速度的提高。但是,如果依赖项生成失败或变得混乱,则构建可能不正确,并且需要完全重建。这种情况不太可能发生,但是告知开发人员这个潜在问题并告知他们如何进行完全重建将很有用。

此外,编译速度现在将与源代码依赖项的复杂性相关。告知开发人员这一点可能会激励他们编写具有较少牵强依赖性的优质代码。

备择方案

我们可以并行启动多个不同且独立的 java 包的单线程编译,而不是让 javac 正确并行。这不需要对 javac 进行任何更改,但是使 Makefile 正确会更加困难,并且不会提供那么多的速度改进。

我们本可以跳过重写 Makefile,但在不首先正确清理 Makefile 的情况下引入此类更改将是一项艰巨且耗时的任务。

测试

由于我们不会更改生成的二进制文件,因此我们不需要添加或更改产品本身的任何测试。

但是,我们应该确保兑现不更改生成的二进制文件的承诺。作为该项目的一部分,我们应该创建一个构建比较工具,它可以在所有相关方面比较旧系统的构建结果与新系统的构建结果。这是一个比听起来更难的问题,因为由于瞬态和不相关的因素,即使使用相同的构建系统,两个后续构建也不会按位相同。为了有用,这样的工具需要忽略这些不相关的方面,并专注于不应该改变的内容。

该工具应该在各种平台和构建类型上运行,比较新旧系统。

该工具还可用于测试增量构建是否与完全重建相同。

**讨论:**理想情况下,应该对构建系统进行测试,就像对最终产品进行测试一样。不幸的是,不存在这样的用于测试构建系统的框架,并且创建适当的测试框架很可能超出了这项工作的范围。

**讨论:**我们应该研究至少添加某种 Makefile 基本测试的可能性。通过特制的“邪恶”依赖项测试增量构建可能是要添加的一种测试。是否有现有的 javac 测试套件可以添加此类测试?

风险和假设

删除非构建项目

  • 风险:错误地删除了对某些团体所需的工作流程或流程的支持
  • 缓解计划:与所有团体沟通,收集需求
  • 应急计划:立即重新实施工作流程支持

罕见平台上的问题

  • 风险:在极少数情况下,新的构建系统将无法工作
  • 缓解计划:在部署之前测试多种场景(不同的硬件和软件,针对不同的群体);确保我们可以在需要时并行使用新旧系统
  • 应急计划:保留旧系统,以便两个系统可以并行使用

结果产品不正确

  • 风险:构建更改会导致构建错误的位
  • 缓解计划:正确测试生成的构建
  • 应急计划:保留旧系统并使用它,直到问题解决

依赖关系

如前所述,此 JEP 依赖于JEP 139:增强 javac 以提高构建速度

该 JEP 将对代码进行大量更改,这些代码也由 BSD/MacOS X 端口进行了修改。构建更改可能会在该项目之前到达 JDK 8,因此我们必须处理它们引入的更改。然而,大多数更改将与 Hotspot 有关,我们在本项目中不考虑它。

未来的 JEP 将在此 JEP 的基础上构建,以改进 HotSpot 和 Javadoc 构建流程。

影响

此更改对实际生成的产品的影响很小。

  • 兼容性:产品的构建方式会有所不同。如果不进行修改,现有的个人或小组构建脚本将无法工作。

  • 可移植性:我们必须确保新的构建系统在所有支持的平台上正常工作。如果可能的话,应该编写它以尽量减少移植到新系统时的移植工作。

  • 文档:现有文档(如构建自述文件)需要更新。