声明依赖关系
在查看依赖项声明本身之前,需要定义依赖项配置的概念。
什么是依赖配置
为 Gradle 项目声明的每个依赖项都适用于特定范围。例如,一些依赖项应该用于编译源代码,而其他依赖项只需要在运行时可用。 Gradle 在Configuration的帮助下表示依赖项的范围。每个配置都可以通过唯一的名称来标识。
许多 Gradle 插件都会向您的项目添加预定义的配置。例如,Java 插件添加配置来表示源代码编译、执行测试等所需的各种类路径。有关示例,请参阅Java 插件章节。
有关使用配置来导航、检查和后处理分配的依赖项的元数据和工件的更多示例,请查看解析结果 API。
配置继承和组合
一个配置可以扩展其他配置以形成继承层次结构。子配置继承为其任何超级配置声明的整套依赖项。
配置继承被 Gradle 核心插件(例如Java 插件)大量使用。例如,testImplementation
配置扩展implementation
配置。配置层次结构有一个实际目的:编译测试需要在编写测试类所需的依赖项之上添加被测源代码的依赖项。使用 JUnit 编写和执行测试代码的 Java 项目如果在生产源代码中导入其类,也需要 Guava。
在底层,配置testImplementation
通过implementation
调用方法Configuration.extendsFrom(org.gradle.api.artifacts.Configuration[])形成继承层次结构。配置可以扩展任何其他配置,无论其在构建脚本或插件中的定义如何。
假设您想编写一套冒烟测试。每个冒烟测试都会进行 HTTP 调用来验证 Web 服务端点。该项目已经使用 JUnit 作为底层测试框架。您可以定义一个名为的新配置smokeTest
,该配置从testImplementation
配置扩展以重用现有的测试框架依赖项。
val smokeTest by configurations.creating {
extendsFrom(configurations.testImplementation.get())
}
dependencies {
testImplementation("junit:junit:4.13")
smokeTest("org.apache.httpcomponents:httpclient:4.5.5")
}
configurations {
smokeTest.extendsFrom testImplementation
}
dependencies {
testImplementation 'junit:junit:4.13'
smokeTest 'org.apache.httpcomponents:httpclient:4.5.5'
}
可解析和消耗性配置
配置是 Gradle 中依赖解析的基本部分。在依赖性解析的上下文中,区分消费者和生产者很有用。沿着这些思路,配置至少有 3 个不同的角色:
-
声明依赖关系
-
作为消费者,解决一组对文件的依赖关系
-
作为生产者,公开工件及其依赖项以供其他项目使用(此类消耗性配置通常代表生产者向其消费者提供的变体)
例如,要表达一个应用程序app
依赖于library lib
,至少需要一项配置:
// declare a "configuration" named "someConfiguration"
val someConfiguration by configurations.creating
dependencies {
// add a project dependency to the "someConfiguration" configuration
someConfiguration(project(":lib"))
}
configurations {
// declare a "configuration" named "someConfiguration"
someConfiguration
}
dependencies {
// add a project dependency to the "someConfiguration" configuration
someConfiguration project(":lib")
}
配置可以通过扩展来继承其他配置的依赖关系。现在,请注意,上面的代码没有告诉我们有关此配置的预期使用者的任何信息。特别是,它没有告诉我们如何使用该配置。假设这lib
是一个 Java 库:它可能会公开不同的内容,例如 API、实现或测试装置。可能有必要app
根据我们正在执行的任务(根据 的 API 进行编译lib
、执行应用程序、编译测试等)来更改解决依赖关系的方式。为了解决这个问题,您经常会找到配套配置,它们旨在明确声明用法:
configurations {
// declare a configuration that is going to resolve the compile classpath of the application
compileClasspath {
extendsFrom(someConfiguration)
}
// declare a configuration that is going to resolve the runtime classpath of the application
runtimeClasspath {
extendsFrom(someConfiguration)
}
}
configurations {
// declare a configuration that is going to resolve the compile classpath of the application
compileClasspath.extendsFrom(someConfiguration)
// declare a configuration that is going to resolve the runtime classpath of the application
runtimeClasspath.extendsFrom(someConfiguration)
}
此时,我们有 3 种不同的配置,具有不同的角色:
-
someConfiguration
声明我的应用程序的依赖项。它只是依赖项的集合。 -
compileClasspath
和是要解析的runtimeClasspath
配置:解析后它们应分别包含应用程序的编译类路径和运行时类路径。
canBeResolved
这种区别由类型中的标志来表示Configuration
。可以解析的配置是我们可以计算依赖图的配置,因为它包含解析发生的所有必要信息。也就是说,我们将计算依赖图,解析图中的组件,并最终获得工件。已canBeResolved
设置为 的配置false
并不意味着将被解析。这样的配置只是为了声明依赖关系。原因是根据使用情况(编译类路径、运行时类路径),它可以解析为不同的图。尝试解析已canBeResolved
设置为 的配置是错误的false
。在某种程度上,这类似于不应该被实例化的抽象类( = false ) 和扩展抽象类 ( = true ) 的具体类。可解析配置将扩展至少一种不可解析配置(并且可以扩展多个)。canBeResolved
canBeResolved
另一方面,在库项目端(生产者),我们也使用配置来表示可以使用的内容。例如,库可能会公开 API 或运行时,我们会将工件附加到其中之一、另一个或两者。通常,要针对 进行编译lib
,我们需要 的 API lib
,但不需要其运行时依赖项。因此该lib
项目将公开一个apiElements
配置,该配置针对寻找其 API 的消费者。这样的配置是可消耗的,但并不意味着要解决。这是通过a 的canBeConsumedConfiguration
标志来表达的:
configurations {
// A configuration meant for consumers that need the API of this component
create("exposedApi") {
// This configuration is an "outgoing" configuration, it's not meant to be resolved
isCanBeResolved = false
// As an outgoing configuration, explain that consumers may want to consume it
assert(isCanBeConsumed)
}
// A configuration meant for consumers that need the implementation of this component
create("exposedRuntime") {
isCanBeResolved = false
assert(isCanBeConsumed)
}
}
configurations {
// A configuration meant for consumers that need the API of this component
exposedApi {
// This configuration is an "outgoing" configuration, it's not meant to be resolved
canBeResolved = false
// As an outgoing configuration, explain that consumers may want to consume it
assert canBeConsumed
}
// A configuration meant for consumers that need the implementation of this component
exposedRuntime {
canBeResolved = false
assert canBeConsumed
}
}
canBeResolved
简而言之,配置的角色由和canBeConsumed
标志组合确定:
配置角色 |
可以解决 |
可以食用 |
依赖范围 |
错误的 |
错误的 |
解决特定用途 |
真的 |
错误的 |
暴露给消费者 |
错误的 |
真的 |
旧版,请勿使用 |
真的 |
真的 |
为了向后兼容,两个标志都有一个默认值true
,但作为插件作者,您应该始终确定这些标志的正确值,否则您可能会意外地引入解析错误。
已弃用的配置
配置旨在用于单个角色:声明依赖项、执行解析或定义可使用的变体。过go,某些配置没有定义它们的用途。当以非预期方式使用配置时,会发出弃用警告。要修复弃用问题,您需要停止使用已弃用角色中的配置。所需的确切更改取决于配置的使用方式以及是否存在应使用的替代配置。
定义自定义配置
您可以自己定义配置,即所谓的自定义配置。自定义配置对于分离专用目的所需的依赖关系范围非常有用。
假设您想要声明对Jasper Ant 任务的依赖关系,以便预编译 JSP 文件,这些文件不应出现在编译源代码的类路径中。通过引入自定义配置并在任务中使用它来实现该目标相当简单。
val jasper by configurations.creating
repositories {
mavenCentral()
}
dependencies {
jasper("org.apache.tomcat.embed:tomcat-embed-jasper:9.0.2")
}
tasks.register("preCompileJsps") {
val jasperClasspath = jasper.asPath
val projectLayout = layout
doLast {
ant.withGroovyBuilder {
"taskdef"("classname" to "org.apache.jasper.JspC",
"name" to "jasper",
"classpath" to jasperClasspath)
"jasper"("validateXml" to false,
"uriroot" to projectLayout.projectDirectory.file("src/main/webapp").asFile,
"outputDir" to projectLayout.buildDirectory.file("compiled-jsps").get().asFile)
}
}
}
configurations {
jasper
}
repositories {
mavenCentral()
}
dependencies {
jasper 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.2'
}
tasks.register('preCompileJsps') {
def jasperClasspath = configurations.jasper.asPath
def projectLayout = layout
doLast {
ant.taskdef(classname: 'org.apache.jasper.JspC',
name: 'jasper',
classpath: jasperClasspath)
ant.jasper(validateXml: false,
uriroot: projectLayout.projectDirectory.file('src/main/webapp').asFile,
outputDir: projectLayout.buildDirectory.file("compiled-jsps").get().asFile)
}
}
您可以使用对象管理项目配置configurations
。配置有一个名称并且可以相互扩展。要了解有关此 API 的更多信息,请查看ConfigurationContainer。
不同类型的依赖关系
模块依赖
模块依赖是最常见的依赖。它们引用存储库中的模块。
dependencies {
runtimeOnly(group = "org.springframework", name = "spring-core", version = "2.5")
runtimeOnly("org.springframework:spring-aop:2.5")
runtimeOnly("org.hibernate:hibernate:3.0.5") {
isTransitive = true
}
runtimeOnly(group = "org.hibernate", name = "hibernate", version = "3.0.5") {
isTransitive = true
}
}
dependencies {
runtimeOnly group: 'org.springframework', name: 'spring-core', version: '2.5'
runtimeOnly 'org.springframework:spring-core:2.5',
'org.springframework:spring-aop:2.5'
runtimeOnly(
[group: 'org.springframework', name: 'spring-core', version: '2.5'],
[group: 'org.springframework', name: 'spring-aop', version: '2.5']
)
runtimeOnly('org.hibernate:hibernate:3.0.5') {
transitive = true
}
runtimeOnly group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
runtimeOnly(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
transitive = true
}
}
有关更多示例和完整参考,请参阅API 文档中的DependencyHandler类。
Gradle 为模块依赖项提供了不同的表示法。有字符串符号和地图符号。模块依赖项有一个允许进一步配置的 API。查看ExternalModuleDependency以了解有关 API 的所有信息。该API提供属性和配置方法。通过字符串表示法,您可以定义属性的子集。使用地图符号,您可以定义所有属性。要使用映射或字符串表示法访问完整的 API,您可以将单个依赖项与闭包一起分配给配置。
如果您声明模块依赖项,Gradle 会在存储库中查找模块元数据文件( |
在 Maven 中,一个模块只能有一个工件。 在 Gradle 和 Ivy 中,一个模块可以有多个工件。每个工件可以有一组不同的依赖关系。 |
文件依赖关系
项目有时不依赖二进制存储库产品(例如 JFrog Artifactory 或 Sonatype Nexus)来托管和解决外部依赖项。通常的做法是将这些依赖项托管在共享驱动器上,或者将它们与项目源代码一起签入版本控制。这些依赖项被称为文件依赖项,原因是它们代表一个没有附加任何元数据(如有关传递依赖项、来源或其作者的信息)的文件。
以下示例解析目录ant
、libs
和中的文件依赖性tools
。
configurations {
create("antContrib")
create("externalLibs")
create("deploymentTools")
}
dependencies {
"antContrib"(files("ant/antcontrib.jar"))
"externalLibs"(files("libs/commons-lang.jar", "libs/log4j.jar"))
"deploymentTools"(fileTree("tools") { include("*.exe") })
}
configurations {
antContrib
externalLibs
deploymentTools
}
dependencies {
antContrib files('ant/antcontrib.jar')
externalLibs files('libs/commons-lang.jar', 'libs/log4j.jar')
deploymentTools(fileTree('tools') { include '*.exe' })
}
正如您在代码示例中看到的,每个依赖项都必须定义其在文件系统中的确切位置。创建文件引用最重要的方法是 Project.files(java.lang.Object…)、 ProjectLayout.files(java.lang.Object…) 和Project.fileTree(java.lang.Object) 或者,您可以还以平面目录存储库的形式定义一个或多个文件依赖项的源目录。
a 中的文件顺序 |
文件依赖项允许您直接将一组文件添加到配置中,而无需先将它们添加到存储库中。如果您不能或不想将某些文件放入存储库中,这会很有用。或者,如果您根本不想使用任何存储库来存储依赖项。
要添加一些文件作为配置的依赖项,您只需将文件集合作为依赖项传递即可:
dependencies {
runtimeOnly(files("libs/a.jar", "libs/b.jar"))
runtimeOnly(fileTree("libs") { include("*.jar") })
}
dependencies {
runtimeOnly files('libs/a.jar', 'libs/b.jar')
runtimeOnly fileTree('libs') { include '*.jar' }
}
文件依赖项不包含在项目的已发布依赖项描述符中。但是,文件依赖项包含在同一构建内的传递项目依赖项中。这意味着它们不能在当前构建之外使用,但可以在同一构建内使用。
您可以声明哪些任务为文件依赖项生成文件。例如,当文件由构建生成时,您可以执行此操作。
dependencies {
implementation(files(layout.buildDirectory.dir("classes")) {
builtBy("compile")
})
}
tasks.register("compile") {
doLast {
println("compiling classes")
}
}
tasks.register("list") {
val compileClasspath: FileCollection = configurations["compileClasspath"]
dependsOn(compileClasspath)
doLast {
println("classpath = ${compileClasspath.map { file: File -> file.name }}")
}
}
dependencies {
implementation files(layout.buildDirectory.dir('classes')) {
builtBy 'compile'
}
}
tasks.register('compile') {
doLast {
println 'compiling classes'
}
}
tasks.register('list') {
FileCollection compileClasspath = configurations.compileClasspath
dependsOn compileClasspath
doLast {
println "classpath = ${compileClasspath.collect { File file -> file.name }}"
}
}
$ gradle -q list compiling classes classpath = [classes]
文件依赖项的版本控制
建议明确表达文件依赖关系的意图和具体版本。 Gradle 的版本冲突解决不考虑文件依赖关系。因此,为文件名分配一个版本以指示其附带的不同更改集非常重要。例如,commons-beanutils-1.3.jar
让您可以通过发行说明跟踪库的更改。
因此,项目的依赖关系更容易维护和组织。通过分配的版本更容易发现潜在的 API 不兼容性。
项目依赖
软件项目通常将软件组件分解为模块,以提高可维护性并防止强耦合。模块可以定义彼此之间的依赖关系,以便在同一项目中重用代码。
Gradle 可以对模块之间的依赖关系进行建模。这些依赖项称为项目依赖项,因为每个模块都由一个 Gradle 项目表示。
dependencies {
implementation(project(":shared"))
}
dependencies {
implementation project(':shared')
}
在运行时,构建会自动确保项目依赖项以正确的顺序构建并添加到类路径中进行编译。创作多项目构建一章更详细地讨论了如何设置和配置多项目构建。
有关更多信息,请参阅ProjectDependency的 API 文档。
以下示例声明了项目对utils
和api
项目的依赖关系web-service
。方法Project.project(java.lang.String)通过路径创建对特定子项目的引用。
dependencies {
implementation(project(":utils"))
implementation(project(":api"))
}
dependencies {
implementation project(':utils')
implementation project(':api')
}
类型安全的项目依赖项
类型安全的项目访问器是一项必须显式启用的孵化功能。实施可能随时改变。
要添加对类型安全项目访问器的支持,请将其添加到您的settings.gradle(.kts)
文件中:
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
该表示法的一个问题project(":some:path")
是您必须记住您想要依赖的每个项目的路径。另外,更改项目路径需要您更改所有使用项目依赖项的地方,但很容易错过一个或多个出现的地方(因为您必须依赖于搜索和替换)。
从 Gradle 7 开始,Gradle 为项目依赖项提供了一个实验性的类型安全 API。与上面相同的示例现在可以重写为:
dependencies {
implementation(projects.utils)
implementation(projects.api)
}
dependencies {
implementation projects.utils
implementation projects.api
}
类型安全 API 的优点是提供 IDE 完成功能,因此您无需弄清楚项目的实际名称。
如果您添加或删除使用 Kotlin DSL 的项目,并且忘记更新依赖项,构建脚本编译将会失败。
项目访问器是从项目路径映射的。例如,如果项目路径为,:commons:utils:some:lib
则项目访问器将为projects.commons.utils.some.lib
(这是 的简写符号projects.getCommons().getUtils().getSome().getLib()
)。
some-lib
带有 kebab case ( ) 或 Snake case ( )的项目名称some_lib
将在访问器中转换为驼峰式大小写:projects.someLib
。
Gradle 发行版特定的依赖项
Gradle API 依赖
您可以使用DependencyHandler.gradleApi()方法声明对当前版本 Gradle 的 API 的依赖关系。当您开发自定义 Gradle 任务或插件时,这非常有用。
dependencies {
implementation(gradleApi())
}
dependencies {
implementation gradleApi()
}
Gradle TestKit 依赖项
您可以使用 DependencyHandler.gradleTestKit ()方法声明对当前版本 Gradle 的 TestKit API 的依赖项。这对于编写和执行 Gradle 插件和构建脚本的功能测试非常有用。
dependencies {
testImplementation(gradleTestKit())
}
dependencies {
testImplementation gradleTestKit()
}
TestKit章节通过示例解释了TestKit的使用。
本地 Groovy 依赖
您可以使用DependencyHandler.localGroovy()方法声明对随 Gradle 分发的 Groovy 的依赖项。当您在 Groovy 中开发自定义 Gradle 任务或插件时,这非常有用。
dependencies {
implementation(localGroovy())
}
dependencies {
implementation localGroovy()
}
记录依赖关系
plugins {
`java-library`
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.ow2.asm:asm:7.1") {
because("we require a JDK 9 compatible bytecode generator")
}
}
plugins {
id 'java-library'
}
repositories {
mavenCentral()
}
dependencies {
implementation('org.ow2.asm:asm:7.1') {
because 'we require a JDK 9 compatible bytecode generator'
}
}
示例:使用具有自定义原因的依赖性洞察报告
gradle -q dependencyInsight --dependency asm
> gradle -q dependencyInsight --dependency asm org.ow2.asm:asm:7.1 Variant compile: | Attribute Name | Provided | Requested | |--------------------------------|----------|--------------| | org.gradle.status | release | | | org.gradle.category | library | library | | org.gradle.libraryelements | jar | classes | | org.gradle.usage | java-api | java-api | | org.gradle.dependency.bundling | | external | | org.gradle.jvm.environment | | standard-jvm | | org.gradle.jvm.version | | 11 | Selection reasons: - Was requested: we require a JDK 9 compatible bytecode generator org.ow2.asm:asm:7.1 \--- compileClasspath A web-based, searchable dependency report is available by adding the --scan option.
解决模块依赖项中的特定工件
每当 Gradle 尝试解析 Maven 或 Ivy 存储库中的模块时,它都会查找元数据文件和默认工件文件(JAR)。如果无法解析这些工件文件,则构建将失败。在某些情况下,您可能需要调整 Gradle 解析依赖项的工件的方式。
-
该依赖项仅提供不带任何元数据(例如 ZIP 文件)的非标准工件。
-
模块元数据声明多个工件,例如作为 Ivy 依赖描述符的一部分。
-
您只想下载特定的工件,而无需在元数据中声明任何传递依赖项。
Gradle 是一种多语言构建工具,不仅限于解析 Java 库。假设您想要使用 JavaScript 作为客户端技术来构建 Web 应用程序。大多数项目将外部 JavaScript 库签入版本控制。外部 JavaScript 库与可重用 Java 库没有什么不同,那么为什么不从存储库下载它呢?
Google Hosted Libraries是流行的开源 JavaScript 库的分发平台。借助仅工件符号,您可以下载 JavaScript 库文件,例如 JQuery。该@
字符将依赖项的坐标与工件的文件扩展名分开。
repositories {
ivy {
url = uri("https://ajax.googleapis.com/ajax/libs")
patternLayout {
artifact("[organization]/[revision]/[module].[ext]")
}
metadataSources {
artifact()
}
}
}
configurations {
create("js")
}
dependencies {
"js"("jquery:jquery:3.2.1@js")
}
repositories {
ivy {
url 'https://ajax.googleapis.com/ajax/libs'
patternLayout {
artifact '[organization]/[revision]/[module].[ext]'
}
metadataSources {
artifact()
}
}
}
configurations {
js
}
dependencies {
js 'jquery:jquery:3.2.1@js'
}
某些模块提供同一工件的不同“风格”,或者它们发布属于特定模块版本但具有不同用途的多个工件。 Java 库通常会发布包含已编译类文件的工件,另一个库仅包含源代码,第三个库包含 Javadoc。
在 JavaScript 中,库可能以未压缩或缩小的工件形式存在。在 Gradle 中,特定的工件标识符称为classifier,该术语通常用于 Maven 和 Ivy 依赖项管理。
假设我们想要下载 JQuery 库的缩小版而不是未压缩的文件。您可以提供分类器min
作为依赖项声明的一部分。
repositories {
ivy {
url = uri("https://ajax.googleapis.com/ajax/libs")
patternLayout {
artifact("[organization]/[revision]/[module](.[classifier]).[ext]")
}
metadataSources {
artifact()
}
}
}
configurations {
create("js")
}
dependencies {
"js"("jquery:jquery:3.2.1:min@js")
}
repositories {
ivy {
url 'https://ajax.googleapis.com/ajax/libs'
patternLayout {
artifact '[organization]/[revision]/[module](.[classifier]).[ext]'
}
metadataSources {
artifact()
}
}
}
configurations {
js
}
dependencies {
js 'jquery:jquery:3.2.1:min@js'
}
支持的元数据格式
外部模块依赖项需要模块元数据(因此,通常,Gradle 可以找出模块的传递依赖项)。为此,Gradle 支持不同的元数据格式。
您还可以调整在存储库定义中查找的格式。
Gradle 模块元数据文件
Gradle 模块元数据经过专门设计,支持 Gradle 依赖管理模型的所有功能,因此是首选格式。您可以在此处找到其规格。
POM文件
Gradle 原生支持Maven POM 文件。值得注意的是,默认情况下 Gradle 将首先查找 POM 文件,但如果该文件包含特殊标记,Gradle 将改用Gradle 模块元数据。
常春藤档案
同样,Gradle 支持Apache Ivy 元数据文件。同样,Gradle 将首先查找一个ivy.xml
文件,但如果该文件包含特殊标记,Gradle 将使用Gradle 模块元数据。