依赖关系的中央声明
使用版本目录
版本目录是依赖项列表,表示为依赖项坐标,用户在构建脚本中声明依赖项时可以从中选择。
例如,可以从版本目录中选取依赖项坐标,而不是使用字符串表示法声明依赖项:
dependencies {
implementation(libs.groovy.core)
}
dependencies {
implementation(libs.groovy.core)
}
在此上下文中,libs
是一个目录,groovy
表示该目录中可用的依赖项。与直接在构建脚本中声明依赖项相比,版本目录具有许多优点:
使用符号添加依赖项的libs.someLib
工作方式与直接在构建脚本中硬编码组、工件和版本的工作方式完全相同。
声明版本目录
版本目录可以在settings.gradle(.kts)
文件中声明。在上面的示例中,为了groovy
通过libs
目录提供可用,我们需要将别名与 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
。
有些关键字是保留的,因此不能用作别名。接下来的单词不能用作别名:
-
扩展
-
班级
-
习俗
除此之外,下一个单词不能用作依赖项别名的第一个子组(对于捆绑包、版本和插件,此限制不适用):
-
捆绑
-
版本
-
插件
例如,对于依赖项,别名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()
}
目录中声明的依赖项通过与其名称相对应的扩展名公开给构建脚本。在上面的示例中,因为在设置中声明的目录名为named ,所以可以通过当前构建的所有构建脚本中的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
目录之外,您还可以通过 API 声明任意数量的目录Settings
。这允许您以对您的项目有意义的方式分离多个源中的依赖项声明。
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 还提供了一个常规文件来声明目录。如果在根构建的子目录libs.versions.toml
中找到文件gradle
,则会自动使用该文件的内容声明一个目录。
声明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 {
// 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为此提供了一个插件。
发现Gradle 原生支持的以 Maven BOM 形式发布的平台也很常见。
使用关键字创建对平台的依赖关系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 将选择依赖项的平台组件。 -
它默认设置endorsStrictVersions行为,这意味着如果平台声明严格依赖关系,它们将被强制执行。
这意味着默认情况下,对平台的依赖会触发该平台中定义的所有严格版本的继承,这对于平台作者确保所有消费者尊重他们在依赖项版本方面的决定非常有用。可以通过显式调用该方法来关闭此功能doNotEndorseStrictVersions
。
导入 Maven BOM
Gradle 提供对导入物料清单 (BOM) 文件的支持,这些文件是用于控制直接依赖项和传递依赖项的依赖项版本的有效.pom
文件。 <dependencyManagement>
Gradle 中的 BOM 支持的工作方式与<scope>import</scope>
依赖 Maven 中的 BOM 时的使用类似。然而,在 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 块中的所有条目的方式与Gradle 的依赖关系约束<dependencyManagement>
类似。这意味着块中定义的任何版本都可能影响依赖项解析结果。为了符合 BOM 的资格,需要设置一个文件。<dependencyManagement>
.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)
}
}