在查看依赖声明本身之前,dependency configuration 的概念需要定义。

什么是依赖配置

为 Gradle 项目声明的每个依赖项都适用于特定范围。例如,一些依赖项应该用于编译源代码,而其他依赖项只需要在运行时可用。 Gradle 在 配置 的帮助下表示依赖项的范围。每个配置都可以用一个唯一的名称来标识。

许多 Gradle 插件会向您的项目添加预定义的配置。例如,Java 插件添加配置来表示源代码编译、执行测试等所需的各种类路径。有关示例,请参见 Java 插件章节

dependency management configurations
图 1. 配置将已声明的依赖项用于特定目的

有关使用配置来导航、检查和后处理元数据和已分配依赖项的构件的更多示例,请查看 解析结果接口

配置继承和组合

一个配置可以扩展其他配置以形成继承层次结构。子配置继承为其任何超级配置声明的整套依赖项。

配置继承被像 Java插件 这样的 Gradle 核心插件大量使用。例如 testImplementation 配置扩展了 implementation 配置。配置层次结构有一个实际目的:编译测试需要在编写测试类所需的依赖项之上测试被测源代码的依赖项。使用 JUnit 编写和执行测试代码的 Java 项目如果在生产源代码中导入其类,则也需要 Guava。

dependency management configuration inheritance
图 2. Java 插件提供的配置继承

在幕后,testImplementationimplementation 配置通过调用方法 Configuration.extendsFrom(org.gradle.api.artifacts.Configuration[]) 形成继承层次结构。配置可以扩展任何其他配置,而不管它在构建脚本或插件中的定义如何。

假设您想编写一套冒烟测试。每个冒烟测试都会进行 HTTP 调用以验证 Web 服务端点。作为底层测试框架,该项目已经使用了 JUnit。您可以定义一个名为 smokeTest 的新配置,它从 testImplementation 配置扩展以重用现有的测试框架依赖项。

KotlinGroovy
build.gradle.kts
val smokeTest by configurations.creating {
    extendsFrom(configurations.testImplementation.get())
}

dependencies {
    testImplementation("junit:junit:4.13")
    smokeTest("org.apache.httpcomponents:httpclient:4.5.5")
}

可解析和可消费的配置

配置是 Gradle 中依赖解析的基本部分。在依赖解析的上下文中,区分 consumerproducer 很有用。按照这些思路,配置至少具有 3 个不同的角色:

  1. 声明依赖关系

  2. 作为 consumer ,解析一组对文件的依赖关系

  3. 作为 producer ,公开构件及其依赖项以供其他项目使用(例如 consumable 配置通常代表生产者向其消费者提供的 变体

例如,要表示应用程序 app depends onlib , at least 需要一个配置:

KotlinGroovy
build.gradle.kts
// declare a "configuration" named "someConfiguration"
val someConfiguration by configurations.creating

dependencies {
    // add a project dependency to the "someConfiguration" configuration
    someConfiguration(project(":lib"))
}

配置可以通过扩展其他配置来继承它们的依赖关系。现在,请注意上面的代码没有告诉我们任何有关此配置的预期 consumer 的信息。特别是,它没有告诉我们配置的含义是 used 。假设 lib 是一个 Java 库:它可能公开不同的东西,例如它的 API、实现或测试装置。根据我们正在执行的任务(针对 lib 的 API 进行编译、执行应用程序、编译测试等),可能需要更改我们解析 app 依赖项的方式。为了解决这个问题,您通常会找到配套配置,这些配置旨在明确声明用法:

KotlinGroovy
build.gradle.kts
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 声明了我的应用程序的依赖项。它只是一个可以容纳依赖项列表的桶。

  • compileClasspathruntimeClasspath 是配置 meant to be resolved :解析后它们应该分别包含应用程序的编译类路径和运行时类路径。

这种区别由 Configuration 类型中的 canBeResolved 标志表示。 can be resolved 的配置是我们可以为其计算依赖图的配置,因为它包含发生解析所需的所有信息。也就是说,我们要计算一个依赖图,解析图中的组件,并最终得到构件。 canBeResolved 设置为 false 的配置并不意味着要解析。有这样的配置only to declare dependencies。原因是根据使用情况(编译类路径、运行时类路径),它can解析为不同的图。尝试解析将 canBeResolved 设置为 false 的配置是错误的。在某种程度上,这类似于不应该实例化的abstract class(canBeResolved =false),以及扩展抽象类的具体类(canBeResolved =true)。可解析配置将扩展至少一个不可解析配置(并且可能扩展不止一个)。

另一方面,在库项目端(producer),我们也使用配置来表示可以使用的内容。例如,库可能公开 API 或运行时,我们会将构件附加到其中之一、另一个或两者。通常,要针对 lib 进行编译,我们需要 lib 的 API,但不需要它的运行时依赖项。因此 lib 项目将公开一个 apiElements 配置,该配置针对寻找其 API 的消费者。这样的配置是可消费,但不是要解决的。这是通过 ConfigurationcanBeConsumed 标志表示的:

示例 4.设置配置
KotlinGroovy
build.gradle.kts
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
        isCanBeConsumed = true
    }
    // A configuration meant for consumers that need the implementation of this component
    create("exposedRuntime") {
        isCanBeResolved = false
        isCanBeConsumed = true
    }
}

简而言之,配置的角色由 canBeResolvedcanBeConsumed 标志组合决定:

表 1. 配置角色

配置角色

可以解决

可以食用

依赖桶

false

false

解决某些用法

true

false

暴露给消费者

false

true

旧的,不要使用

true

true

为了向后兼容,这两个标志都有默认值 true ,但作为插件作者,您应该始终为这些标志确定正确的值,否则您可能会不小心引入解析错误。

为依赖项选择正确的配置

选择声明依赖项的配置很重要。但是,没有固定的规则必须将依赖项放入哪个配置中。它主要取决于配置的组织方式,这通常是所应用插件的属性。

例如,在 java 插件中,创建的配置是 记录在案 并且应该根据它在代码中的作用作为确定在哪里声明依赖项的基础。

作为建议,插件应该清楚地记录它们的配置链接在一起的方式,并且应该尽可能地努力隔离它们的 角色

定义自定义配置

您可以自己定义配置,即所谓的 custom configurations 。自定义配置对于分离专用目的所需的依赖项范围很有用。

假设您想声明对 碧玉Ant任务 的依赖,以便预编译 JSP 文件,这些 JSP 文件应该 not 最终出现在用于编译源代码的类路径中。通过引入自定义配置并在任务中使用它来实现该目标相当简单。

KotlinGroovy
build.gradle.kts
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 对象管理项目配置。配置有一个名字并且可以相互扩展。要了解有关此 API 的更多信息,请查看 ConfigurationContainer

不同类型的依赖

模块依赖

模块依赖是最常见的依赖。它们引用仓库中的模块。

示例 6.模块依赖
KotlinGroovy
build.gradle.kts
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
    }
}

有关更多示例和完整参考,请参阅 API 文档中的 DependencyHandler 类。

Gradle 为模块依赖提供了不同的表示法。有一个字符串表示法和一个映射表示法。模块依赖项具有允许进一步配置的 API。查看 ExternalModuleDependency 以了解有关 API 的所有信息。此 API 提供属性和配置方法。通过字符串表示法,您可以定义属性的子集。使用地图符号,您可以定义所有属性。要使用映射或字符串表示法访问完整的 API,您可以将单个依赖项与闭包一起分配给配置。

如果您声明模块依赖项,Gradle 会在仓库中查找模块元数据文件(.module.pomivy.xml)。如果存在这样的模块元数据文件,则会对其进行解析并下载此模块的构件(例如 hibernate-3.0.5.jar )及其依赖项(例如 cglib )。如果不存在这样的模块元数据文件,从 Gradle 6.0 开始,您需要配置 元数据源定义 以直接查找名为 hibernate-3.0.5.jar 的构件文件。

在 Maven 中,一个模块只能有一个构件。

在 Gradle 和 Ivy 中,一个模块可以有多个构件。每个构件都可以有一组不同的依赖关系。

文件依赖

项目有时不依赖二进制仓库产品(例如 JFrog Artifactory 或 Sonatype Nexus)来托管和解析外部依赖项。通常的做法是将这些依赖项托管在共享驱动器上,或者将它们与项目源代码一起检入版本控制。这些依赖项被称为 file dependencies ,原因是它们代表的文件没有附加任何 metadata (如有关传递依赖项、来源或其作者的信息)。

dependency management file dependencies
图 3. 从本地文件系统和共享驱动器解决文件依赖关系

以下示例解析目录 antlibstools 中的文件依赖项。

KotlinGroovy
build.gradle.kts
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") })
}

正如您在代码示例中看到的,每个依赖项都必须定义其在文件系统中的确切位置。创建文件引用最突出的方法是 Project.files(java.lang.Object…​)ProjectLayout.files(java.lang.Object…​)Project.fileTree(java.lang.Object) 或者,您也可以以 平面目录厂库 的形式定义一个或多个文件依赖项的源目录。

FileTree 中的文件顺序不稳定,即使在单台计算机上也是如此。这意味着以这种构造为种子的依赖配置可能会产生具有不同排序的解析结果,可能会影响使用结果作为输入的任务的可缓存性。建议尽可能使用更简单的 files

文件依赖项允许您直接将一组文件添加到配置中,而无需先将它们添加到仓库中。如果您不能或不想将某些文件放在仓库中,这将很有用。或者,如果您根本不想使用任何仓库来存储您的依赖项。

要添加一些文件作为配置的依赖项,您只需将 文件收集 作为依赖项传递:

示例 8.文件依赖
KotlinGroovy
build.gradle.kts
dependencies {
    runtimeOnly(files("libs/a.jar", "libs/b.jar"))
    runtimeOnly(fileTree("libs") { include("*.jar") })
}

文件依赖项不包含在您的项目的已发布依赖项描述符中。但是,文件依赖项包含在同一构建中的传递项目依赖项中。这意味着它们不能在当前构建之外使用,但可以在同一个构建中使用。

FileTree 中的文件顺序不稳定,即使在单台计算机上也是如此。这意味着以这种构造为种子的依赖配置可能会产生具有不同排序的解析结果,可能会影响使用结果作为输入的任务的可缓存性。建议尽可能使用更简单的 files

您可以声明哪些任务为文件依赖项生成文件。例如,当文件由构建生成时,您可能会这样做。

KotlinGroovy
build.gradle.kts
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 }}")
    }
}
$ gradle -q list
compiling classes
classpath = [classes]

文件依赖的版本控制

建议明确表达文件依赖的意图和具体版本。 Gradle 的 版本冲突解决 不考虑文件依赖性。因此,为文件名指定一个版本以指示随附的不同更改集非常重要。例如 commons-beanutils-1.3.jar 允许您通过发行说明跟踪库的更改。

因此,项目的依赖项更易于维护和组织。通过分配的版本更容易发现潜在的 API 不兼容性。

项目依赖

软件项目通常将软件组件分解成模块以提高可维护性并防止强耦合。模块可以定义彼此之间的依赖关系,以在同一项目中重用代码。

dependency management project dependencies
图 4. 项目之间的依赖关系

Gradle 可以对模块之间的依赖关系进行建模。这些依赖项称为project dependencies,因为每个模块都由一个 Gradle 项目表示。

示例 10.项目依赖
KotlinGroovy
build.gradle.kts
dependencies {
    implementation(project(":shared"))
}

在运行时,构建会自动确保项目依赖项以正确的顺序构建并添加到类路径中进行编译。 编写多项目构建 章更详细地讨论了如何设置和配置多项目构建。

有关详细信息,请参阅 ProjectDependency 的 API 文档。

以下示例声明了 web-service 项目对 utilsapi 项目的依赖关系。 Project.project(java.lang.String) 方法通过路径创建对特定子项目的引用。

示例 11.声明项目依赖
KotlinGroovy
web-service/build.gradle.kts
dependencies {
    implementation(project(":utils"))
    implementation(project(":api"))
}

类型安全的项目依赖

类型安全的项目访问器是一项必须明确启用的孵化功能。实施可能随时改变。要添加对类型安全项目访问器的支持,请将其添加到您的 settings.gradle(.kts) 文件中:

enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

project(":some:path") 表示法的一个问题是您必须记住要依赖的每个项目的路径。此外,更改项目路径需要更改所有使用项目依赖项的地方,但很容易错过一个或多个地方(因为你必须依赖搜索和替换)。

从 Gradle 7 开始,Gradle 为项目依赖项提供了一个实验性的类型安全 API。与上面相同的示例现在可以重写为:

KotlinGroovy
web-service/build.gradle.kts
dependencies {
    implementation(projects.utils)
    implementation(projects.api)
}

类型安全的 API 具有提供 IDE 完成的优势,因此您无需弄清楚项目的实际名称。

如果您添加或删除使用 Kotlin DSL 的项目,如果您忘记更新依赖项,构建脚本编译将失败。

项目访问器映射自 project path 。例如,如果项目路径为 :commons:utils:some:lib,则项目访问器将为 projects.commons.utils.some.lib(这是 projects.getCommons().getUtils().getSome().getLib() 的简写符号)。

带有 kebab 大小写 (some-lib) 或蛇形大小写 (some_lib) 的项目名称将在访问器中转换为驼峰大小写:projects.someLib

模块依赖的本地分支

如果模块本身是使用 Gradle 构建的,则模块依赖项可以替换为对该模块源的本地分支的依赖项。这可以通过利用 复合构建 来完成。例如,这允许您通过使用和构建本地修补版本而不是发布的二进制版本来修复您在应用程序中使用的库中的问题。有关详细信息,请参阅复合构建 部分。

Gradle 分发特定的依赖项

Gradle API 依赖

您可以使用 DependencyHandler.gradleApi() 方法声明对当前版本 Gradle 的 API 的依赖。这在您开发自定义 Gradle 任务或插件时很有用。

KotlinGroovy
build.gradle.kts
dependencies {
    implementation(gradleApi())
}

Gradle TestKit 依赖

您可以使用 DependencyHandler.gradleTestKit() 方法声明对当前版本 Gradle 的 TestKit API 的依赖。这对于为 Gradle 插件和构建脚本编写和执行功能测试很有用。

KotlinGroovy
build.gradle.kts
dependencies {
    testImplementation(gradleTestKit())
}

TestKit 章节 举例说明了 TestKit 的使用。

本地 Groovy 依赖

您可以使用 DependencyHandler.localGroovy() 方法声明对随 Gradle 分发的 Groovy 的依赖项。当您在 Groovy 中开发自定义 Gradle 任务或插件时,这很有用。

KotlinGroovy
build.gradle.kts
dependencies {
    implementation(localGroovy())
}

记录依赖关系

当您声明依赖项或 依赖约束 时,您可以为声明提供自定义原因。这使得构建脚本中的依赖声明和 依赖洞察报告 更易于解释。

KotlinGroovy
build.gradle.kts
plugins {
    `java-library`
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.ow2.asm:asm:7.1") {
        because("we require a JDK 9 compatible bytecode generator")
    }
}

示例:使用具有自定义原因的依赖性洞察报告

Output of 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 库没有什么不同,那么为什么不从仓库下载它呢?

谷歌托管库 是流行的开源 JavaScript 库的分发平台。在 artifact-only 符号的帮助下,您可以下载一个 JavaScript 库文件,例如 JQuery。 @ 字符将依赖项的坐标与构件的文件扩展名分开。

KotlinGroovy
build.gradle.kts
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")
}

一些模块发布了同一构件的不同“风味”,或者它们发布了属于特定模块版本但具有不同用途的多个构件。一个 Java 库通常发布带有编译类文件的构件,另一个只发布源代码,第三个包含 Javadoc。

在 JavaScript 中,库可能以未压缩或缩小的形式存在。在 Gradle 中,一个特定的构件标识符称为 classifier ,这个术语通常用于 Maven 和 Ivy 依赖管理。

假设我们想要下载 JQuery 库的缩小版而不是未压缩的文件。您可以提供分类器 min 作为依赖声明的一部分。

KotlinGroovy
build.gradle.kts
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")
}

支持的元数据格式

外部模块依赖关系需要模块元数据(因此,通常 Gradle 可以找出模块的传递依赖关系)。为此,Gradle 支持不同的元数据格式。

您还可以调整将在 仓库定义 中查找的格式。

Gradle 模块元数据文件

Gradle 模块元数据专门设计用于支持 Gradle 依赖管理模型的所有功能,因此是首选格式。你可以找到它的 规格在这里

POM文件

Gradle 原生支持 Maven POM 文件 。值得注意的是,默认情况下 Gradle 会首先查找 POM 文件,但如果此文件包含特殊标记,Gradle 将使用 Gradle 模块元数据 代替。

常春藤档案

同样,Gradle 支持 Apache Ivy 元数据文件 。同样,Gradle 将首先查找 ivy.xml 文件,但如果该文件包含特殊标记,Gradle 将改用 Gradle 模块元数据