依赖管理 - Gradle 版本目录条目命名最佳实践

我们讨论了Gradle版本目录条目命名的最佳实践,以确保依赖管理的一致性和清晰度

目录

引言

版本目录是Gradle构建工具中相当新的功能。它们通过提供一种标准化方式来定义和访问项目中使用的依赖项目录,从而帮助管理依赖项——确保团队中的所有开发人员在依赖项名称和定义上保持一致,这为每个人节省了时间和认知负担。像大多数Gradle功能一样,它们非常灵活,因此用户必须制定自己的使用约定。

alt_text

在这篇博文中,我们将分享Develocity团队在管理依赖项时采用的一些最佳实践,特别是如何使用版本目录。我们将重点介绍如何根据特定依赖项的**GAV**(**G**roup、**A**rtifact ID、**V**ersion的缩写)坐标来派生版本目录条目名称的约定。

版本目录 #

版本目录是Gradle依赖管理功能的一部分。它们提供了一种方便、标准化的方式来定义项目中可供工程师使用的一组依赖项。版本目录可以直接在Gradle的设置脚本中定义,或者使用单独的TOML文件定义。TOML目录的默认位置是gradle/libs.versions.toml。这将创建一个libs目录以供构建使用。TOML格式的示例条目可以如下所示:

commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.14.0" }

当Gradle找到版本目录时,它会生成访问器,这些访问器可以在构建脚本中以编译安全的方式定义依赖项。因此,我们不必写:

implementation("org.apache.commons:commons-lang3:3.14.0")

并在每个需要此依赖项的构建脚本中重复GAV坐标字符串,我们现在可以写:

implementation(libs.commons.lang3)

从这个例子我们可以看出,Gradle在将目录条目commons-lang3转换为访问器libs.commons.lang3时会应用一些规则。此外,我们可能会问为什么我把条目命名为commons-lang3,而不是例如apache-commonsLang3org_apache_commons_commons-lang3(这两种都是有效的目录条目名称)。

目录条目命名约定 #

由于Gradle除了要求条目名称必须是有效的TOML条目名称外,没有强制任何命名约定,我们发现自己经常讨论为什么我们以这样或那样的方式命名目录条目。在某个时候,我们决定与团队坐下来,制定一份关于未来如何命名版本目录条目的约定列表。我们的目标是停止在每个PR上讨论这个问题。此外,我们希望目录保持一致,并为需要添加新条目的工程师提供指导。在本节中,我们将分享这份约定列表。对于每个约定,我们将提供一个组和构件ID(GA)元组,然后提供一个或多个如何**不**定义目录条目的示例,以及根据约定定义条目名称的正确方法。

条目中的段用短划线分隔 #

条目中的段可以使用短划线或下划线分隔。根据Gradle文档的建议是使用短划线。

  • GA: org.apache.commons:commons-lang3
  • 错误: commons_lang3
  • 正确: commons-lang3

第一个段应源自构件的项目组 #

使用能唯一标识生成构件的项目组的名称。有时项目组与GAV的组ID匹配。例如,在org.slf4j:slf4-api的情况下,组织和项目组相同——slf4j。在其他情况下,项目组是更大组织的一部分,例如commons项目组是org.apache组织的一部分。

  • GA: org.apache.commons:commons-lang3
  • 错误:apache-commonsLangorg-apacheCommonsLang
  • 正确: commons-lang3

如果第一个段会重复,则可以省略 #

我们喜欢保持简单并遵循DRY原则。因此,如果项目组与构件相同,我们不会重复它。

  • GA: dev.failsafe:failsafe
  • 错误: dev-failsafe, failsafe-failsafe
  • 正确: failsafe

组或构件ID中的短划线转换为驼峰式命名 #

当 Gradle 生成访问器时,短划线被转换为点,而驼峰式命名保持不变。这在构建脚本中插入依赖项时对代码补全有影响。因此,如果组或构件包含短划线,我们希望将该部分作为一个单元进行代码补全。所以我们将短划线替换为驼峰式命名。

  • GA: com.networknt:json-schema-validator
  • 错误: networknt-json-schema-validator
  • 正确: networknt-jsonSchemaValidator

不要包含隐含的术语 #

某些术语,例如 javasdk 在我们构建 JVM 代码时是隐含的,因此应省略。不要添加对您的项目版本目录上下文而言显而易见的内容。

  • GA: com.amazonaws:aws-java-sdk-core
  • 错误: aws-javaSdkCore
  • 正确: aws-core

当用作库时,插件依赖项总是以-plugin为后缀 #

这是我们团队中构建工程师的一个特殊规则,因为我们有时会在其他Gradle插件之上构建自定义插件。在这些情况下,我们定义了对插件构件的实现依赖。为了将它们与“普通”库依赖区分开来,我们添加了-plugin后缀。此规则不适用于在目录的[plugins]部分中管理的插件,该部分是版本目录中的专用部分,我们在此博客文章中不作介绍。

  • GA: com.bmuschko:gradle-docker-plugin
  • 错误: bmuschko-gradleDockerPlugin, plugin-bmuschko-docker
  • 正确: bmuschko-docker-plugin

总结 #

现在我们已经讨论了从GAV派生版本目录条目名称的约定,让我们将其应用于一些实际示例。

示例:SLF4J #

SLF4J 附带一个 API 构件和几个可供选择的实现。我们将查看 API 构件,其坐标为 org.slf4j4:slf4-api。对该构件应用约定,我们…

  • 将项目组识别为slf4j
  • 我们省略了组中的org顶级域名部分
  • 我们不想重复自己,所以我们从slf4j-api中删除slf4j

这给了我们slf4j-api作为此依赖项的版本目录条目名称。将其放入libs.versions.toml文件将如下所示:

[libraries]
slf4j-api = { module = "org.slf4j:slf4j-api", version = "2.0.13" }

并在构建脚本中使用此条目将如下所示:

dependencies {
    implementation(libs.slf4j.api)
}

示例:Jackson #

Jackson 是一个广泛使用的序列化库,包含许多组件。这次我们看两个例子。首先,我们为 com.fasterxml.jackson.core:jackson-databind 派生一个条目名称。

  • 项目组是jackson
  • 我们删除了组ID中的其余部分
  • 我们不重复自己,所以我们从构件名称中删除了jackson

结果名称是 jackson-databind

现在让我们看看另一个 Jackson 构件:com.fasterxml.jackson.dataformat:jackson-dataformat-csv。在这种情况下:

  • 项目组再次是jackson
  • 我们删除了组ID的其余部分
  • 我们不重复构件中的jackson
  • 我们将dataformat-csv中的短划线转换为驼峰式命名

我们最终得到jackson-dataformatCsv。如你所见,我在这里决定不将coredataformat作为名称中的单独段。其他选项是使用jackson-core-databindjackson-dataformat-csv,或者jacksonCore-databindjacksonDataformat-csv。我认为这纯粹是个人喜好以及你希望依赖项的代码补全如何工作的问题。

我们再次将其放入TOML文件:

[libraries]
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version = "2.17.1" }
jackson-dataformatCsv = { module = "com.fasterxml.jackson.core:jackson-dataformat-csv", version = "2.17.1" }

在构建脚本中:

dependencies {
    implementation(libs.jackson.databind)
    implementation(libs.jackson.dataformatCsv)
}

总结 #

版本目录是Gradle的依赖管理功能之一。它们有助于集中化依赖项的GAV坐标,并在构建脚本中提供对这些坐标的编译安全访问。然而,Gradle没有强制执行任何关于如何命名版本目录条目的约定。在这篇博文中,我们分享了Develocity工程团队为从GAV坐标派生版本目录条目名称而采用的一些最佳实践。我们展示了如何使用两个示例来应用这些约定——SLF4J API以及Jackson项目的两个依赖项。

讨论