编译避免
我们最近注意到社区中有一些关于通过忽略不影响依赖项 ABI 的更改来加快 Gradle 在 JVM 上的编译速度的讨论。这是一个好主意!事实上,Gradle 从一开始就使用 ABI 来实现 Java,而无需任何额外的配置 自 3.4 版本起。我们将此功能称为 **编译避免**。这篇文章解释了基于 ABI 的编译对于一般工作流程意味着什么。剧透:利用编译避免是任何构建中最好的性能增强之一。
什么是应用程序二进制接口?
应用程序二进制接口 (ABI) 是从编译软件生成的接口,它定义了内部和外部交互。ABI 代表了在编译时对消费者可见的内容。在编译项目时,其任何依赖项的 ABI 中是否存在更改将决定编译是否是最新的,或者是否需要重新编译。这些 ABI 包括对消费者项目可见的所有关于依赖项的公共信息,例如
- 所有公共方法及其参数类型和返回值语句
- 所有公共属性和字段
- 用于编译到 ABI 的所有依赖项。
当一个人在源代码中访问库时,他们使用库的 API。当机器访问编译后的二进制文件时,它使用 ABI。
为什么 ABI 与构建性能相关?
现代构建系统在编译代码时会考虑 ABI 兼容性,以避免在编译代码库的增量更改时尽可能多的编译。
内部实现细节的更改是 **ABI 兼容的**:它们不会更改公共接口。实际上,项目的内部实现细节比公共组件更改得更频繁。当公共信息没有改变时,任何下游项目都不需要重新编译。跳过这些额外的工作可以对大型项目的构建性能产生巨大影响,无论是在本地还是在 CI 上。
对公共接口的更改是 ABI 不兼容的,因为它们会更改公共接口。ABI 不兼容的更改需要重新编译所有下游依赖项。必须重新编译 ABI 不兼容更改下游的所有依赖项会导致构建时间大幅增加。
什么是编译避免?
Gradle 在发生 ABI 兼容更改时优化构建。我们称这种优化为 编译避免。
要了解其工作原理,请想象两个项目。:app
依赖于 :lib
。以下是一些我们通常可以对 :lib
进行的 ABI 兼容更改,这些更改不会导致 :app
(或任何依赖于 :app
的项目)需要重新编译
- 对方法体进行任何更改
- 添加、删除或更改私有方法、字段或内部类
- 重命名参数
- 更改注释
- 更改类路径中 jar 或目录的名称
- 添加、删除或更改资源
当 :lib
中发生 ABI 兼容更改时,并且运行依赖于其类的 ‘:app’ 中的任务时,Gradle 不会重新编译项目 :app
或任何依赖于 :app
的项目。在大型多项目构建中,这可以节省大量时间。
编译避免与增量编译有何不同?
并非所有更改都符合上述要求。在某些情况下,您需要更改:lib
的公共 ABI,这会导致需要编译:app
。幸运的是,另一个开箱即用的功能称为**增量编译**被使用。这将智能地重新编译:lib
中发生更改的类,以便下游的:app
仍然比完全编译更快。
增量编译与编译避免不同,但非常互补。
“编译避免”是指完全避免为给定项目调用编译器。
另一方面,“增量编译”确实意味着调用编译器,但在这样做时尝试减少需要重新编译的代码量。这是通过在正常编译过程中跟踪类之间的引用来实现的,并且只重新编译受给定更改影响的代码。
使用增量编译,我们查看所有与编译避免相比发生更改的类,编译避免查看的是项目。编译避免跨依赖项目工作,而不仅仅是在项目内部像增量编译一样工作。也就是说,增量编译优化了项目中单个类的编译。增量编译发生在单个项目内,而编译避免则查看多个项目之间的关系。增量编译仍然可以跨项目节省时间,因为它减少了需要重新编译的内容。
我使用的是增量编译还是编译避免?
总之,如果您进行 ABI 兼容的更改,那么您将使用增量编译和编译避免:对您进行更改的源代码的编译任务使用增量编译,对下游项目使用编译避免。
或者,如果您的更改不是 ABI 兼容的,您只能从增量编译中获益。
ABI JAR 怎么样?
一些构建系统生成 ABI JAR 来实现编译避免。有时称为头文件 JAR,它们具有整体接口,没有内部细节。ABI JAR 只包含公共方法、字段、常量和嵌套类型,所有方法体都被删除,可用于评估任何更改是否表明需要重新编译。使用 Gradle,我们不需要 ABI JAR,因为当有编译器任务时,我们会对其输入进行规范化并生成 ABI 的唯一哈希值。然后,Gradle 使用此哈希值来检查是否存在任何更改。
不同语言的说明
Groovy 有一个可选的实验性功能。
Kotlin 有一个实验性功能,它是作为Kotlin Gradle 插件的一部分由JetBrains开发的。
我该如何使用它?
Gradle 自动使用编译避免。增量编译和编译避免在使用 Gradle 构建的 Java 项目中默认启用多年。因此,下次您在编辑后担心重新编译代码时,请放心,Gradle 会自动为您提供性能提升。
不确定您是否正在使用增量编译或编译避免,或者您认为您有一个应该起作用但没有起作用的用例?在 Slack 上找到我们,我们喜欢看到用例。