依赖项的集中声明
使用版本目录
版本目录是依赖项列表,表示为依赖项坐标,用户在构建脚本中声明依赖项时可以选择该列表。
例如,可以使用字符串表示法声明依赖项,也可以从版本目录中选择依赖项坐标
dependencies {
implementation(libs.groovy.core)
}
dependencies {
implementation(libs.groovy.core)
}
在此上下文中,libs
是一个目录,groovy
表示该目录中可用的依赖项。版本目录相较于在构建脚本中直接声明依赖项提供了许多优势
使用 libs.someLib
符号添加依赖项的工作方式与直接在构建脚本中硬编码组、工件和版本完全相同。
声明版本目录
可以在 settings.gradle(.kts)
文件中声明版本目录。在上面的示例中,为了通过 libs
目录提供 groovy
,我们需要将别名与 GAV(组、工件、版本)坐标相关联
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
library("groovy-core", "org.codehaus.groovy:groovy:3.0.5")
library("groovy-json", "org.codehaus.groovy:groovy-json:3.0.5")
library("groovy-nio", "org.codehaus.groovy:groovy-nio:3.0.5")
library("commons-lang3", "org.apache.commons", "commons-lang3").version {
strictly("[3.8, 4.0[")
prefer("3.9")
}
}
}
}
dependencyResolutionManagement {
versionCatalogs {
libs {
library('groovy-core', 'org.codehaus.groovy:groovy:3.0.5')
library('groovy-json', 'org.codehaus.groovy:groovy-json:3.0.5')
library('groovy-nio', 'org.codehaus.groovy:groovy-nio:3.0.5')
library('commons-lang3', 'org.apache.commons', 'commons-lang3').version {
strictly '[3.8, 4.0['
prefer '3.9'
}
}
}
}
别名及其到类型安全访问器的映射
别名必须由一系列标识符组成,这些标识符由破折号 (-
,推荐)、下划线 (_
) 或点 (.
) 分隔。标识符本身必须由 ASCII 字符组成,最好是小写,最后可以跟数字。
例如
-
guava
是一个有效的别名 -
groovy-core
是一个有效的别名 -
commons-lang3
是一个有效的别名 -
androidx.awesome.lib
也是一个有效的别名 -
但
this.#is.not!
然后,为每个子组生成类型安全访问器。例如,给定名为 libs
的版本目录中的以下别名
guava
、groovy-core
、groovy-xml
、groovy-json
、androidx.awesome.lib
我们将生成以下类型安全访问器
-
libs.guava
-
libs.groovy.core
-
libs.groovy.xml
-
libs.groovy.json
-
libs.androidx.awesome.lib
其中 libs
前缀来自版本目录名称。
如果您想避免生成子组访问器,我们建议依靠大小写进行区分。例如,别名 groovyCore
、groovyJson
和 groovyXml
将分别映射到 libs.groovyCore
、libs.groovyJson
和 libs.groovyXml
访问器。
在声明别名时,值得注意的是,任何 -
、_
和 .
字符都可以用作分隔符,但生成的目录将全部标准化为 .
:例如,foo-bar
作为别名会自动转换为 foo.bar
。
一些关键字是保留的,因此不能用作别名。以下单词不能用作别名
-
extensions
-
class
-
convention
此外,以下单词不能用作依赖项的别名的第一个子组(对于捆绑包、版本和插件,此限制不适用)
-
bundles
-
versions
-
plugins
因此,例如,对于依赖项,别名 versions-dependency
无效,但 versionsDependency
或 dependency-versions
有效。
具有相同版本号的依赖项
在 声明版本目录 中的第一个示例中,我们可以看到我们为 groovy
库的各个组件声明了 3 个别名,并且它们都共享相同的版本号。
我们可以声明一个版本并引用它,而不是重复相同的版本号
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("groovy", "3.0.5")
version("checkstyle", "8.37")
library("groovy-core", "org.codehaus.groovy", "groovy").versionRef("groovy")
library("groovy-json", "org.codehaus.groovy", "groovy-json").versionRef("groovy")
library("groovy-nio", "org.codehaus.groovy", "groovy-nio").versionRef("groovy")
library("commons-lang3", "org.apache.commons", "commons-lang3").version {
strictly("[3.8, 4.0[")
prefer("3.9")
}
}
}
}
dependencyResolutionManagement {
versionCatalogs {
libs {
version('groovy', '3.0.5')
version('checkstyle', '8.37')
library('groovy-core', 'org.codehaus.groovy', 'groovy').versionRef('groovy')
library('groovy-json', 'org.codehaus.groovy', 'groovy-json').versionRef('groovy')
library('groovy-nio', 'org.codehaus.groovy', 'groovy-nio').versionRef('groovy')
library('commons-lang3', 'org.apache.commons', 'commons-lang3').version {
strictly '[3.8, 4.0['
prefer '3.9'
}
}
}
}
单独声明的版本也可以通过类型安全访问器获得,这使得它们可用于比依赖项版本更多的用例,特别是对于工具
checkstyle {
// will use the version declared in the catalog
toolVersion = libs.versions.checkstyle.get()
}
checkstyle {
// will use the version declared in the catalog
toolVersion = libs.versions.checkstyle.get()
}
如果声明的版本别名也是某些更具体别名的前缀,如 libs.versions.zinc
和 libs.versions.zinc.apiinfo
,则可以通过类型安全访问器的 asProvider()
使用更通用版本的价值
scala {
zincVersion = libs.versions.zinc.asProvider().get()
}
scala {
zincVersion = libs.versions.zinc.asProvider().get()
}
在目录中声明的依赖项通过与它们名称相对应的扩展公开给构建脚本。在上面的示例中,因为在设置中声明的目录名为 libs
,所以扩展可以通过当前构建的所有构建脚本中的名称 libs
使用。使用以下符号声明依赖项…
dependencies {
implementation(libs.groovy.core)
implementation(libs.groovy.json)
implementation(libs.groovy.nio)
}
dependencies {
implementation libs.groovy.core
implementation libs.groovy.json
implementation libs.groovy.nio
}
…与编写 完全相同
dependencies {
implementation("org.codehaus.groovy:groovy:3.0.5")
implementation("org.codehaus.groovy:groovy-json:3.0.5")
implementation("org.codehaus.groovy:groovy-nio:3.0.5")
}
dependencies {
implementation 'org.codehaus.groovy:groovy:3.0.5'
implementation 'org.codehaus.groovy:groovy-json:3.0.5'
implementation 'org.codehaus.groovy:groovy-nio:3.0.5'
}
在目录中声明的版本是 丰富版本。有关完整版本声明支持文档,请参阅 版本目录构建器 API。
依赖项包
因为在不同的项目中经常系统地一起使用一些依赖项,所以版本目录提供了“依赖项包”的概念。一个包基本上是几个依赖项的别名。例如,您可以编写以下内容,而不是像上面那样声明 3 个单独的依赖项
dependencies {
implementation(libs.bundles.groovy)
}
dependencies {
implementation libs.bundles.groovy
}
名为 groovy
的包需要在目录中声明
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("groovy", "3.0.5")
version("checkstyle", "8.37")
library("groovy-core", "org.codehaus.groovy", "groovy").versionRef("groovy")
library("groovy-json", "org.codehaus.groovy", "groovy-json").versionRef("groovy")
library("groovy-nio", "org.codehaus.groovy", "groovy-nio").versionRef("groovy")
library("commons-lang3", "org.apache.commons", "commons-lang3").version {
strictly("[3.8, 4.0[")
prefer("3.9")
}
bundle("groovy", listOf("groovy-core", "groovy-json", "groovy-nio"))
}
}
}
dependencyResolutionManagement {
versionCatalogs {
libs {
version('groovy', '3.0.5')
version('checkstyle', '8.37')
library('groovy-core', 'org.codehaus.groovy', 'groovy').versionRef('groovy')
library('groovy-json', 'org.codehaus.groovy', 'groovy-json').versionRef('groovy')
library('groovy-nio', 'org.codehaus.groovy', 'groovy-nio').versionRef('groovy')
library('commons-lang3', 'org.apache.commons', 'commons-lang3').version {
strictly '[3.8, 4.0['
prefer '3.9'
}
bundle('groovy', ['groovy-core', 'groovy-json', 'groovy-nio'])
}
}
}
语义再次等效:添加单个包等效于单独添加构成包的所有依赖项。
插件
除了库之外,版本目录还支持声明插件版本。虽然库由其组、工件和版本坐标表示,但 Gradle 插件仅由其 ID 和版本标识。因此,它们需要单独声明
您不能在设置文件或设置插件中使用在版本目录中声明的插件(因为目录本身在设置中定义,所以这将是一个先有鸡还是先有蛋的问题)。 |
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
plugin("versions", "com.github.ben-manes.versions").version("0.45.0")
}
}
}
dependencyResolutionManagement {
versionCatalogs {
libs {
plugin('versions', 'com.github.ben-manes.versions').version('0.45.0')
}
}
}
然后,插件在 plugins
块中可访问,并且可以使用以下方法在构建的任何项目中使用
plugins {
`java-library`
checkstyle
alias(libs.plugins.versions)
}
plugins {
id 'java-library'
id 'checkstyle'
// Use the plugin `versions` as declared in the `libs` version catalog
alias(libs.plugins.versions)
}
使用多个目录
除了传统的 libs
目录之外,还可以通过 Settings
API 声明任意数量的目录。这允许您以对项目有意义的方式在多个源中分离依赖项声明。
dependencyResolutionManagement {
versionCatalogs {
create("testLibs") {
val junit5 = version("junit5", "5.7.1")
library("junit-api", "org.junit.jupiter", "junit-jupiter-api").versionRef(junit5)
library("junit-engine", "org.junit.jupiter", "junit-jupiter-engine").versionRef(junit5)
}
}
}
dependencyResolutionManagement {
versionCatalogs {
testLibs {
def junit5 = version('junit5', '5.7.1')
library('junit-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef(junit5)
library('junit-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef(junit5)
}
}
}
每个目录都会生成一个扩展,应用于所有项目以访问其内容。因此,通过选择一个减少潜在冲突的名称来降低冲突的可能性是有意义的。例如,一种选择是选择一个以 |
libs.versions.toml 文件
除了上述设置 API 之外,Gradle 还提供了一个传统文件来声明目录。如果在根构建的 gradle
子目录中找到 libs.versions.toml
文件,那么将使用此文件的目录自动声明。
声明 libs.versions.toml
文件并不会使其成为依赖项的唯一真实来源:它是一个可以声明依赖项的传统位置。一旦开始使用目录,强烈建议在目录中声明所有依赖项,而不是在构建脚本中硬编码组/工件/版本字符串。请注意,插件可能会添加依赖项,这些依赖项是在此文件外部定义的。
就像 src/main/java
是查找 Java 源的约定,这并不会阻止声明其他源目录(在构建脚本或插件中),libs.versions.toml
文件的存在也不会阻止在其他地方声明依赖项。
但是,此文件的存在确实表明大多数(如果不是全部)依赖项都将在该文件中声明。因此,对于大多数用户来说,更新依赖项版本只需更改此文件中的一个行。
默认情况下,libs.versions.toml
文件将成为 libs
目录的输入。可以更改默认目录的名称,例如,如果您已经具有同名的扩展
dependencyResolutionManagement {
defaultLibrariesExtensionName = "projectLibs"
}
dependencyResolutionManagement {
defaultLibrariesExtensionName = 'projectLibs'
}
版本目录 TOML 文件格式
该 TOML 文件包含 4 个主要部分
-
[versions]
部分用于声明依赖项可以引用的版本 -
[libraries]
部分用于声明别名以进行坐标 -
[bundles]
部分用于声明依赖项包 -
[plugins]
部分用于声明插件
例如
[versions] groovy = "3.0.5" checkstyle = "8.37" [libraries] groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" } groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" } commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } } [bundles] groovy = ["groovy-core", "groovy-json", "groovy-nio"] [plugins] versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }
版本可以声明为单个字符串,在这种情况下,它们将被解释为必需版本,或作为 丰富版本
[versions]
my-lib = { strictly = "[1.0, 2.0[", prefer = "1.2" }
版本声明支持的成员为
依赖项声明可以声明为简单字符串,在这种情况下,它们将被解释为 group:artifact:version
坐标,或将版本声明与组和名称分开
对于别名,别名及其到类型安全访问器的映射 部分中描述的规则也适用。 |
[versions] common = "1.4" [libraries] my-lib = "com.mycompany:mylib:1.4" my-other-lib = { module = "com.mycompany:other", version = "1.4" } my-other-lib2 = { group = "com.mycompany", name = "alternate", version = "1.4" } mylib-full-format = { group = "com.mycompany", name = "alternate", version = { require = "1.4" } } [plugins] short-notation = "some.plugin.id:1.4" long-notation = { id = "some.plugin.id", version = "1.4" } reference-notation = { id = "some.plugin.id", version.ref = "common" }
如果您想要引用在 [versions]
部分中声明的版本,则应使用 version.ref
属性
[versions]
some = "1.4"
[libraries]
my-lib = { group = "com.mycompany", name="mylib", version.ref="some" }
TOML 文件格式非常宽松,允许您编写“点”属性作为完整对象声明的快捷方式。例如,以下内容
a.b.c="d"
等效于
a.b = { c = "d" }
或
a = { b = { c = "d" } }
有关详细信息,请参阅 TOML 规范。
类型不安全的 API
可以通过类型不安全的 API 访问版本目录。在没有生成访问器的情况下,可以使用此 API。它通过版本目录扩展进行访问
val versionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
println("Library aliases: ${versionCatalog.libraryAliases}")
dependencies {
versionCatalog.findLibrary("groovy-json").ifPresent {
implementation(it)
}
}
def versionCatalog = extensions.getByType(VersionCatalogsExtension).named("libs")
println "Library aliases: ${versionCatalog.libraryAliases}"
dependencies {
versionCatalog.findLibrary("groovy-json").ifPresent {
implementation(it)
}
}
查看 版本目录 API 以了解所有受支持的方法。
共享目录
版本目录用于单个构建(可能是多项目构建),但也可以在构建之间共享。例如,组织可能希望创建不同团队的不同项目可能使用的依赖项目录。
从 TOML 文件导入目录
版本目录构建器 API 支持包含来自外部文件中的模型。这使得在需要时,可以为 buildSrc
重用主构建的目录。例如,buildSrc/settings.gradle(.kts)
文件可以使用以下内容包含此文件
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
dependencyResolutionManagement {
versionCatalogs {
libs {
from(files("../gradle/libs.versions.toml"))
}
}
}
使用 VersionCatalogBuilder.from(Object dependencyNotation) 方法时,将只接受一个文件。这意味着像 Project.files(java.lang.Object…) 这样的符号必须引用一个文件,否则构建将失败。 如果需要更复杂结构(从多个文件导入版本目录),建议使用基于代码的方法,而不是 TOML 文件。 |
因此,此技术可用于从不同文件声明多个目录
dependencyResolutionManagement {
versionCatalogs {
// declares an additional catalog, named 'testLibs', from the 'test-libs.versions.toml' file
create("testLibs") {
from(files("gradle/test-libs.versions.toml"))
}
}
}
dependencyResolutionManagement {
versionCatalogs {
// declares an additional catalog, named 'testLibs', from the 'test-libs.versions.toml' file
testLibs {
from(files('gradle/test-libs.versions.toml'))
}
}
}
版本目录插件
虽然从本地文件导入目录很方便,但它并不能解决在组织中或外部使用者之间共享目录的问题。共享目录的一种选择是编写一个设置插件,在 Gradle 插件门户网站或内部存储库上发布它,并让使用者在其设置文件中应用该插件。
或者,Gradle 提供了一个版本目录插件,它提供了声明然后发布目录的功能。
为此,您需要应用 version-catalog
插件
plugins {
`version-catalog`
`maven-publish`
}
plugins {
id 'version-catalog'
id 'maven-publish'
}
然后,此插件将公开 catalog 扩展,您可以使用它来声明目录
catalog {
// declare the aliases, bundles and versions in this block
versionCatalog {
library("my-lib", "com.mycompany:mylib:1.2")
}
}
catalog {
// declare the aliases, bundles and versions in this block
versionCatalog {
library('my-lib', 'com.mycompany:mylib:1.2')
}
}
然后,可以通过应用 maven-publish
或 ivy-publish
插件并配置发布以使用 versionCatalog
组件来发布此类目录
publishing {
publications {
create<MavenPublication>("maven") {
from(components["versionCatalog"])
}
}
}
publishing {
publications {
maven(MavenPublication) {
from components.versionCatalog
}
}
}
发布此类项目时,将自动生成(并上传)一个 libs.versions.toml
文件,然后可以 从其他 Gradle 构建中使用。
导入已发布的目录
可以通过设置 API 导入 版本目录插件 生成的目录
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from("com.mycompany:catalog:1.0")
}
}
}
dependencyResolutionManagement {
versionCatalogs {
libs {
from("com.mycompany:catalog:1.0")
}
}
}
覆盖目录版本
如果目录声明了一个版本,则可以在导入目录时覆盖该版本
dependencyResolutionManagement {
versionCatalogs {
create("amendedLibs") {
from("com.mycompany:catalog:1.0")
// overwrite the "groovy" version declared in the imported catalog
version("groovy", "3.0.6")
}
}
}
dependencyResolutionManagement {
versionCatalogs {
amendedLibs {
from("com.mycompany:catalog:1.0")
// overwrite the "groovy" version declared in the imported catalog
version("groovy", "3.0.6")
}
}
}
在上面的示例中,任何使用 groovy
版本作为引用的依赖项都将自动更新为使用 3.0.6
。
同样,覆盖版本并不意味着实际已解析的依赖项版本将相同:这只会更改导入的内容,也就是说,在声明依赖项时使用的内容。实际版本将受到传统冲突解决(如果有)的影响。 |
使用平台控制传递版本
一个 平台是一个特殊的软件组件,可用于控制传递依赖项版本。在大多数情况下,它完全由 依赖项约束 组成,这些约束将建议依赖项版本或强制某些版本。因此,无论何时需要在项目之间共享依赖项版本,这都是一个完美的工具。在这种情况下,项目通常会以这种方式组织
-
一个
platform
项目,该项目为不同子项目中发现的各种依赖项定义约束 -
许多依赖于平台的子项目和声明无版本的依赖项
在 Java 生态系统中,Gradle 为此提供了一个 插件。
还可以找到作为 Maven BOM 发布的平台,Gradle 原生支持。
使用 platform
关键字创建对平台的依赖项
dependencies {
// get recommended versions from the platform project
api(platform(project(":platform")))
// no version required
api("commons-httpclient:commons-httpclient")
}
dependencies {
// get recommended versions from the platform project
api platform(project(':platform'))
// no version required
api 'commons-httpclient:commons-httpclient'
}
此 platform
符号是一种简写符号,它实际上在后台执行多个操作
-
它将 org.gradle.category 属性 设置为
platform
,这意味着 Gradle 将选择依赖项的platform 组件。 -
它默认设置 endorseStrictVersions 行为,这意味着如果平台声明了严格依赖项,则将强制执行这些依赖项。
这意味着默认情况下,对平台的依赖项会触发继承该平台中定义的所有 严格版本,这对于平台作者确保所有使用者在依赖项版本方面尊重其决策非常有用。可以通过显式调用 doNotEndorseStrictVersions
方法来关闭此功能。
导入 Maven BOM
Gradle 提供了导入 物料清单 (BOM) 文件 的支持,这些文件实际上是使用 <dependencyManagement>
来控制直接和传递依赖项的依赖项版本的 .pom
文件。Gradle 中的 BOM 支持类似于在 Maven 中依赖 BOM 时使用 <scope>import</scope>
。然而,在 Gradle 中,它是通过对 BOM 的常规依赖项声明完成的
dependencies {
// import a BOM
implementation(platform("org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE"))
// define dependencies without versions
implementation("com.google.code.gson:gson")
implementation("dom4j:dom4j")
}
dependencies {
// import a BOM
implementation platform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE')
// define dependencies without versions
implementation 'com.google.code.gson:gson'
implementation 'dom4j:dom4j'
}
在示例中,gson
和 dom4j
的版本由 Spring Boot BOM 提供。这样,如果你正在为 Spring Boot 等平台开发,则不必自己声明任何版本,而是可以依赖平台提供的版本。
Gradle 将 BOM 的 <dependencyManagement>
块中的所有条目视为类似于 Gradle 的依赖项约束。这意味着 <dependencyManagement>
块中定义的任何版本都可能影响依赖项解析结果。为了符合 BOM 的资格,.pom
文件需要设置 <packaging>pom</packaging>
。
然而,BOM 通常不仅提供版本作为建议,还提供一种覆盖图中找到的任何其他版本的方法。在导入 BOM 时,可以通过使用 enforcedPlatform
关键字(而不是 platform
)来启用此行为
dependencies {
// import a BOM. The versions used in this file will override any other version found in the graph
implementation(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE"))
// define dependencies without versions
implementation("com.google.code.gson:gson")
implementation("dom4j:dom4j")
// this version will be overridden by the one found in the BOM
implementation("org.codehaus.groovy:groovy:1.8.6")
}
dependencies {
// import a BOM. The versions used in this file will override any other version found in the graph
implementation enforcedPlatform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE')
// define dependencies without versions
implementation 'com.google.code.gson:gson'
implementation 'dom4j:dom4j'
// this version will be overridden by the one found in the BOM
implementation 'org.codehaus.groovy:groovy:1.8.6'
}
如果你的软件组件可以被其他人使用,则需要谨慎考虑使用 |
我应该使用平台还是目录?
由于平台和目录都涉及依赖项版本,并且都可以用于在项目中共享依赖项版本,因此可能会混淆使用哪一个以及是否一个优于另一个。
简而言之,您应该
-
仅将目录用于定义项目依赖项及其版本并生成类型安全访问器
-
使用平台将版本应用于依赖项图并影响依赖项解析
目录有助于集中依赖项版本,并且正如其名称所暗示的那样,它只是您可以从中选择的依赖项目录。我们建议在所有情况下都使用它来声明依赖项的坐标。Gradle 将使用它来生成类型安全访问器,为外部依赖项提供简短符号,并且它允许轻松地在不同项目之间共享这些坐标。使用目录不会对下游使用者产生任何影响:对他们来说是透明的。
平台是一个更重量级的构造:它是依赖项图的组件,就像任何其他库一样。如果您依赖于平台,则该平台本身就是图中的一个组件。具体而言,这意味着
总之,使用目录始终是一种良好的工程实践,因为它集中了通用定义,允许共享依赖项版本或插件版本,但它是构建的“实现细节”:它对使用者不可见,并且目录中未使用的元素只是被忽略。
平台旨在影响依赖项解析图,例如通过对传递依赖项添加约束:它是一种用于构建依赖项图并影响解析结果的解决方案。
在实践中,您的项目既可以使用目录,还可以声明一个平台,该平台本身使用该目录
plugins {
`java-platform`
}
dependencies {
constraints {
api(libs.mylib)
}
}
plugins {
id 'java-platform'
}
dependencies {
constraints {
api(libs.mylib)
}
}