在查看依赖项声明本身之前,需要定义依赖项配置的概念.

What are dependency configurations

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

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

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

有关使用配置导航,检查和后处理分配的依赖项的元数据和工件的更多示例,请查看解析结果API .

Configuration inheritance and composition

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

Configuration inheritance is heavily used by Gradle core plugins like the Java plugin. For example the testImplementation configuration extends the implementation configuration. The configuration hierarchy has a practical purpose: compiling tests requires the dependencies of the source code under test on top of the dependencies needed write the test class. A Java project that uses JUnit to write and execute test code also needs Guava if its classes are imported in the production source code.

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

testImplementationtestImplementationimplementation配置通过调用Configuration.extendsFrom(org.gradle.api.artifacts.Configuration [])形成继承层次结构. 配置可以扩展任何其他配置,无论其在构建脚本或插件中的定义如何.

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

示例1.从另一个配置扩展一个配置
build.gradle
configurations {
    smokeTest.extendsFrom testImplementation
}

dependencies {
    testImplementation 'junit:junit:4.12'
    smokeTest 'org.apache.httpcomponents:httpclient:4.5.5'
}
build.gradle.kts
val smokeTest by configurations.creating {
    extendsFrom(configurations.testImplementation.get())
}

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

Resolvable and consumable configurations

配置是Gradle中依赖关系解析的基本部分. 在依赖关系解析的上下文中,区分消费者生产者非常有用. 按照这些原则,配置至少具有3个不同的角色:

  1. 声明依赖

  2. 作为使用者 ,解决文件的一系列依赖关系

  3. 作为生产者 ,将工件及其依赖项公开以供其他项目使用(此类消耗性配置通常表示生产者向其消费者提供的变体

例如,要表达一个应用app 依赖lib ,则至少需要一种配置:

例子2.配置用来声明依赖
build.gradle
configurations {
    // declare a "configuration" named "someConfiguration"
    someConfiguration
}
dependencies {
    // add a project dependency to the "someConfiguration" configuration
    someConfiguration project(":lib")
}
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"))
}

配置可以通过扩展从其他配置继承依赖关系. 现在,请注意,上面的代码没有告诉我们有关此配置的目标使用者的任何信息. 特别是,它并没有告诉我们该配置是如何使用的 . 假设lib是一个Java库:它可能会公开不同的东西,例如其API,实现或测试装置. 可能有必要根据我们要执行的任务(根据lib的API进行编译,执行应用程序,编译测试等)来更改如何解析app的依赖关系. 为了解决这个问题,您经常会找到伴随的配置,这些配置旨在明确声明其用法:

例子3.代表具体依赖图的配置
build.gradle
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)
}
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要解决的配置:解决时,它们应分别包含编译类路径和应用程序的运行时类路径.

这种区别由Configuration类型中的canBeResolved标志表示. 可以解析的配置是我们可以为其计算依赖关系图的配置,因为它包含实现解析所需的所有必要信息. 也就是说,我们将计算一个依赖图,解析图中的组件,并最终获得工件. canBeResolved设置为false配置并不意味着可以解决. 这样的配置仅用于声明依赖项 . 原因是根据用法(编译类路径,运行时类路径),它可以解析为不同的图. 尝试解析canBeResolved设置为false的配置是false . 在某种程度上,这类似于不应实例化的抽象类canBeResolved = false),以及扩展了抽象类的具体类( canBeResolved = true). 可解决的配置将扩展至少一个不可解决的配置(并且可能会扩展多个).

另一方面,在图书馆项目方面( 生产者 ),我们还使用配置来表示可以消耗的东西. 例如,该库可以公开一个API或运行时,并且我们会将工件附加到一个或多个. 通常情况下,进行编译lib ,我们需要的API lib ,但我们并不需要它的运行时依赖. 因此, lib项目将公开一个apiElements配置,该配置面向正在寻找其API的消费者. 这样的配置是消耗性的,但并不意味着必须解决. 这是通过ConfigurationcanBeConsumed标志表示的:

例子4.设置配置
build.gradle
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
        canBeConsumed = true
    }
    // A configuration meant for consumers that need the implementation of this component
    exposedRuntime {
        canBeResolved = false
        canBeConsumed = true
    }
}
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标志组合确定:

Table 1. Configuration roles

配置角色

可以解决

可以食用

依赖桶

false

false

Resolve for certain usage

true

false

暴露给消费者

false

true

旧版,请勿使用

true

true

为了向后兼容,两个标志的默认值均为true ,但作为插件作者,您应始终为这些标志确定正确的值,否则可能会意外引入解析错误.

Choosing the right configuration for dependencies

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

例如,在java插件中,创建的配置已记录在案,并应根据其在代码中的作用来确定在哪里声明依赖项.

作为建议,插件应明确记录其配置链接在一起的方式,并应尽最大努力隔离其角色 .

Defining custom configurations

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

比方说,你想声明的依赖碧玉Ant任务应该在classpath最终编译源代码预编译JSP文件的目的. 通过引入自定义配置并在任务中使用它来实现该目标相当简单.

例子5.声明和使用定制配置
build.gradle
configurations {
    jasper
}

repositories {
    mavenCentral()
}

dependencies {
    jasper 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.2'
}

task preCompileJsps {
    doLast {
        ant.taskdef(classname: 'org.apache.jasper.JspC',
                    name: 'jasper',
                    classpath: configurations.jasper.asPath)
        ant.jasper(validateXml: false,
                   uriroot: file('src/main/webapp'),
                   outputDir: file("$buildDir/compiled-jsps"))
    }
}
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") {
    doLast {
        ant.withGroovyBuilder {
            "taskdef"("classname" to "org.apache.jasper.JspC",
                      "name" to "jasper",
                      "classpath" to jasper.asPath)
            "jasper"("validateXml" to false,
                     "uriroot" to file("src/main/webapp"),
                     "outputDir" to file("$buildDir/compiled-jsps"))
        }
    }
}

项目的配置由configurations对象管理. 配置具有名称,并且可以互相扩展. 要了解有关此API的更多信息,请查看ConfigurationContainer .

Different kinds of dependencies

Module dependencies

模块依赖性是最常见的依赖性. 它们引用存储库中的模块.

例子6.模块依赖性
build.gradle
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
    }
}
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中,一个模块可以具有多个工件. 每个工件可以具有不同的依赖关系集.

File dependencies

项目有时不依赖于二进制存储库产品(例如JFrog Artifactory或Sonatype Nexus)来托管和解决外部依赖项. 通常的做法是将这些依赖项托管在共享驱动器上,或者将其与项目源代码一起放入版本控制中. 这些依赖项称为文件依赖项 ,原因是它们表示文件没有附加任何元数据 (例如有关传递性依赖项,来源或其作者的信息).

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

以下示例从antlibstools目录解析文件依赖性.

例子7.声明多个文件依赖
build.gradle
configurations {
    antContrib
    externalLibs
    deploymentTools
}

dependencies {
    antContrib files('ant/antcontrib.jar')
    externalLibs files('libs/commons-lang.jar', 'libs/log4j.jar')
    deploymentTools(fileTree('tools') { include '*.exe' })
}
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).或者,您可以还以平面目录存储库的形式定义一个或多个文件依赖项的源目录.

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

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

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

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

您可以声明产生文件依赖性的任务. 例如,当文件是由生成生成的时,您可能会这样做.

例子9.生成的文件依赖性
build.gradle
dependencies {
    implementation files("$buildDir/classes") {
        builtBy 'compile'
    }
}

task compile {
    doLast {
        println 'compiling classes'
    }
}

task list(dependsOn: configurations.compileClasspath) {
    doLast {
        println "classpath = ${configurations.compileClasspath.collect { File file -> file.name }}"
    }
}
build.gradle.kts
dependencies {
    implementation(files("$buildDir/classes") {
        builtBy("compile")
    })
}

tasks.register("compile") {
    doLast {
        println("compiling classes")
    }
}

tasks.register("list") {
    dependsOn(configurations["compileClasspath"])
    doLast {
        println("classpath = ${configurations["compileClasspath"].map { file: File -> file.name }}")
    }
}
$ gradle -q list
compiling classes
classpath = [classes]

Versioning of file dependencies

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

因此,项目的依存关系更易于维护和组织. 通过分配的版本发现潜在的API不兼容要容易得多.

Project dependencies

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

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

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

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

在运行时,该构建会自动确保以正确的顺序构建项目依赖项,并将其添加到类路径中进行编译. " 创作多项目构建 "一章讨论了如何设置和配置多项目构建.

有关更多信息,请参见ProjectDependency的API文档.

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

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

Local forks of module dependencies

如果模块本身是使用Gradle构建的,则模块依赖性可以由对该模块源的本地fork的依赖性替代. 这可以通过使用复合构建来完成. 例如,这允许您通过使用并构建本地修补版本而不是已发布的二进制版本来解决在应用程序中使用的库中的问题. 有关详细信息,请参见组合构建部分.

Gradle distribution-specific dependencies

Gradle API dependency

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

例子12. Gradle API依赖性
build.gradle
dependencies {
    implementation gradleApi()
}
build.gradle.kts
dependencies {
    implementation(gradleApi())
}

Gradle TestKit dependency

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

例子13. Gradle TestKit的依赖
build.gradle
dependencies {
    testImplementation gradleTestKit()
}
build.gradle.kts
dependencies {
    testImplementation(gradleTestKit())
}

TestKit章节通过示例解释了TestKit的用法.

Local Groovy dependency

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

例子14. Gradle的Groovy依赖
build.gradle
dependencies {
    implementation localGroovy()
}
build.gradle.kts
dependencies {
    implementation(localGroovy())
}

Documenting dependencies

声明依赖项或依赖项约束时 ,可以提供声明的自定义原因. 这使得构建脚本中的依赖项声明和依赖关系见解报告更易于解释.

例子15.给出在依赖声明中选择某个模块版本的原因
build.gradle
plugins {
    id 'java-library'
}

repositories {
    jcenter()
}

dependencies {
    implementation('org.ow2.asm:asm:7.1') {
        because 'we require a JDK 9 compatible bytecode generator'
    }
}
build.gradle.kts
plugins {
    `java-library`
}

repositories {
    jcenter()
}

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

Example: Using the dependency insight report with custom reasons

gradle -q dependencyInsight --dependency asm输出
> gradle -q dependencyInsight --dependency asm
org.ow2.asm:asm:7.1
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         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.

Resolving specific artifacts from a module dependency

每当Gradle尝试从Maven或Ivy存储库解析模块时,它都会查找元数据文件和默认工件文件JAR. 如果这些工件文件都无法解析,则构建将失败. 在某些情况下,您可能需要调整Gradle解析依赖项工件的方式.

  • 依赖项仅提供非标准工件,没有任何元数据(例如ZIP文件).

  • 模块元数据声明了多个工件,例如,作为常春藤依赖描述符的一部分.

  • 您只想下载特定的工件,而无需在元数据中声明任何传递依赖项.

Gradle是一个多语言构建工具,不仅限于解析Java库. 假设您想使用JavaScript作为客户端技术来构建Web应用程序. 大多数项目将外部JavaScript库检入版本控制. 外部JavaScript库与可重用Java库没有什么不同,那么为什么不从存储库中下载它呢?

Google托管库是流行的开源JavaScript库的发行平台. 借助纯工件符号,您可以下载JavaScript库文件,例如JQuery. @字符将依赖项的坐标与工件的文件扩展名分开.

Example 16. Resolving a JavaScript artifact for a declared dependency
build.gradle
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'
}
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库通常会发布带有已编译类文件的工件,另一个库中仅包含源代码,而第三个库中包含Javadocs.

在JavaScript中,库可能以未压缩或缩小的工件形式存在. 在Gradle中,特定的工件标识符称为分类器 ,该术语通常在Maven和Ivy依赖管理中使用.

假设我们要下载JQuery库的精简构件,而不是未压缩的文件. 您可以提供分类器min作为依赖项声明的一部分.

例17.用分类器解析一个JavaScript工件以获得声明的依赖关系
build.gradle
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'
}
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")
}

Supported Metadata formats

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

您还可以调整将在存储库定义中查找哪种格式.

Gradle Module Metadata files

Gradle模块元数据经过专门设计,可支持Gradle依赖性管理模型的所有功能,因此是首选格式. 您可以在此处找到其规格 .

POM files

Gradle本机支持Maven POM文件 . 值得注意的是,默认情况下,Gradle将首先查找POM文件,但是如果该文件包含特殊标记,则Gradle将改为使用Gradle Module元数据 .

Ivy files

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