在查看依赖项声明本身之前,需要定义依赖项配置的概念。
什么是依赖项配置
为 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
依赖于库 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、实现或测试夹具。可能需要根据我们执行的任务(针对 lib
的 API 编译、执行应用程序、编译测试等)来更改我们解析 app
依赖项的方式。为了解决此问题,你经常会发现配套配置,这些配置旨在明确声明用法
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
是旨在解决的配置:解决后它们应分别包含编译类路径和应用程序的运行时类路径。
此区别由 Configuration
类型中的 canBeResolved
标志表示。可以解决的配置是我们能够计算依赖项图的配置,因为它包含解决所需的所有必要信息。也就是说,我们将计算依赖项图,解决图中的组件,并最终获取工件。将 canBeResolved
设置为 false
的配置不应解决。此类配置仅用于声明依赖项。原因是,根据用法(编译类路径、运行时类路径),它可以解析为不同的图。尝试解决将 canBeResolved
设置为 false
的配置是错误的。在某种程度上,这类似于不应实例化的抽象类(canBeResolved
=false)和扩展抽象类的具体类(canBeResolved
=true)。可解决的配置将至少扩展一个不可解决的配置(并且可能扩展多个)。
另一方面,在库项目侧(生成器),我们还使用配置来表示可以消耗的内容。例如,库可能会公开 API 或运行时,并且我们会将工件附加到其中一个、另一个或两者。通常,要针对 lib
进行编译,我们需要 lib
的 API,但不需要它的运行时依赖项。因此,lib
项目将公开一个 apiElements
配置,该配置针对寻找其 API 的使用者。此类配置是可消耗的,但不是为了解决而设计的。这通过 Configuration
的 canBeConsumed 标志表示
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
标志组合决定
配置角色 |
可以解决 |
可以消耗 |
依赖项范围 |
false |
false |
针对特定用法解决 |
true |
false |
向使用者公开 |
false |
true |
传统,不使用 |
true |
true |
为了向后兼容,两个标志的默认值都为 true
,但作为插件作者,你应始终确定这些标志的正确值,否则你可能会意外地引入解析错误。
已弃用的配置
配置旨在用于单一角色:声明依赖项、执行解析或定义可消耗变体。过去,某些配置没有定义其预期使用的角色。当配置以非预期的方式使用时,会发出弃用警告。要修复弃用,你需要停止在已弃用的角色中使用配置。所需的确切更改取决于配置的使用方式以及是否有应改为使用的替代配置。
定义自定义配置
你可以自己定义配置,即所谓的自定义配置。自定义配置对于分离特定目的所需的依赖项范围非常有用。
假设您想声明对 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) 此外,您还可以以 平面目录存储库 的形式定义一个或多个文件依赖项的源目录。
即使在单台计算机上, |
文件依赖项允许您将一组文件直接添加到配置中,而无需先将它们添加到存储库中。如果您无法或不想将某些文件放置在存储库中,这将非常有用。或者如果您根本不想使用任何存储库来存储您的依赖项。
要将一些文件作为配置的依赖项添加,您只需传递一个 文件集合 作为依赖项
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 文档。
以下示例声明了 web-service
项目对 utils
和 api
项目的依赖项。方法 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
)或下划线分隔形式(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 托管库 是一个流行的开源 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 中,特定的工件标识符称为分类器,这是一个通常在 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 支持不同的元数据格式。
您还可以在存储库定义中调整要查找的格式。
POM 文件
Gradle 本机支持Maven POM 文件。值得注意的是,默认情况下,Gradle 会首先查找 POM 文件,但如果此文件包含特殊标记,Gradle 将使用Gradle 模块元数据。
Ivy 文件
类似地,Gradle 支持 Apache Ivy 元数据文件。同样,Gradle 将首先查找 ivy.xml
文件,但如果此文件包含特殊标记,Gradle 将改用 Gradle 模块元数据。