Gradle 支持功能的概念:一个库通常可以拆分成多个相关但不同的库,每个功能可以与库一起使用。

功能允许组件公开多个相关库,每个库都可以声明自己的依赖项。这些库作为变体公开,类似于库如何为其 API 和运行时公开变体。

这允许许多不同的场景(列表不详尽)

通过功能选择功能

声明对组件的依赖通常是通过提供一组坐标(组、工件、版本,也称为 GAV 坐标)来完成的。这允许引擎确定我们正在寻找的组件,但这样的组件可能提供不同的变体变体通常根据使用情况选择。例如,我们可能会选择不同的变体来编译组件(在这种情况下,我们需要组件的 API)或执行代码(在这种情况下,我们需要组件的运行时)。组件的所有变体都提供了一些功能,这些功能使用 GAV 坐标类似地表示。

功能由 GAV 坐标表示,但您必须将其视为功能描述

  • "我提供 SLF4J 绑定"

  • "我提供对 MySQL 的运行时支持"

  • "我提供 Groovy 运行时"

一般来说,在图中拥有两个提供相同内容的组件是一个问题(它们冲突)。

这是一个重要的概念,因为

  • 默认情况下,变体提供与组件的 GAV 坐标相对应的功能

  • 依赖关系图中的任何两个变体都不能提供相同的功能

  • 只要多个变体提供了不同的功能,就可以选择单个组件的多个变体。

一个典型的组件会提供具有默认功能的变体。例如,一个 Java 库会公开两个变体(API 和运行时),它们提供相同的功能。因此,在依赖关系图中同时包含单个组件的API运行时是一个错误。

但是,假设您需要组件的运行时测试夹具运行时。只要库的运行时测试夹具运行时变体声明不同的功能,就可以这样做。

如果我们这样做,消费者将需要声明两个依赖项

  • 一个在“主”功能上,即库

  • 一个在“测试夹具”功能上,通过要求其功能

虽然解析引擎独立于生态系统支持多变体组件,但功能目前只能使用 Java 插件。

注册功能

可以通过应用java-library插件来声明功能。以下代码说明了如何声明名为mongodbSupport的功能

示例 1. 注册功能
build.gradle.kts
sourceSets {
    create("mongodbSupport") {
        java {
            srcDir("src/mongodb/java")
        }
    }
}

java {
    registerFeature("mongodbSupport") {
        usingSourceSet(sourceSets["mongodbSupport"])
    }
}
build.gradle
sourceSets {
    mongodbSupport {
        java {
            srcDir 'src/mongodb/java'
        }
    }
}

java {
    registerFeature('mongodbSupport') {
        usingSourceSet(sourceSets.mongodbSupport)
    }
}

Gradle 会自动为您设置许多内容,与Java 库插件设置配置的方式非常相似。

依赖项范围配置的创建方式与主功能相同

  • 配置mongodbSupportApi,用于声明此功能的 API 依赖项

  • 配置mongodbSupportImplementation,用于声明此功能的实现依赖项

  • 配置mongodbSupportRuntimeOnly,用于声明此功能的运行时依赖项

  • 配置mongodbSupportCompileOnly,用于声明此功能的编译时依赖项

  • 配置mongodbSupportCompileOnlyApi,用于声明此功能的编译时 API 依赖项

此外,可消费配置的创建方式与主功能相同

  • 配置mongodbSupportApiElements,供消费者用于获取此功能的工件和 API 依赖项

  • 配置 mongodbSupportRuntimeElements 用于消费者获取此功能的工件和运行时依赖项。

一个功能应该有一个同名的源集。Gradle 将创建一个 Jar 任务,使用与功能的 kebab-case 名称相对应的分类器,将从功能源集构建的类捆绑在一起。

在注册功能时不要使用源集。此行为将在 Gradle 的未来版本中被弃用。

大多数用户只需要关心依赖范围配置,以声明此功能的特定依赖项。

build.gradle.kts
dependencies {
    "mongodbSupportImplementation"("org.mongodb:mongodb-driver-sync:3.9.1")
}
build.gradle
dependencies {
    mongodbSupportImplementation 'org.mongodb:mongodb-driver-sync:3.9.1'
}

按照惯例,Gradle 将功能名称映射到一个功能,其组和版本分别与主组件的组和版本相同,但其名称为主组件名称后跟一个 -,然后是 kebab-cased 功能名称。

例如,如果组件的组是 org.gradle.demo,其名称是 provider,其版本是 1.0,并且功能名为 mongodbSupport,则功能的变体将具有 org.gradle.demo:provider-mongodb-support:1.0 功能。

如果您自己选择功能名称或向变体添加更多功能,建议遵循相同的约定。

发布功能

根据元数据文件格式,发布功能可能会造成损失。

  • 使用 Gradle 模块元数据,所有内容都会发布,消费者将获得功能的全部好处。

  • 使用 POM 元数据(Maven),功能作为可选依赖项发布,功能的工件使用不同的分类器发布。

  • 使用 Ivy 元数据,功能作为额外的配置发布,这些配置不会default 配置扩展。

仅使用 maven-publishivy-publish 插件支持发布功能。Java 库插件将负责为您注册额外的变体,因此不需要额外的配置,只需要常规的出版物。

build.gradle.kts
plugins {
    `java-library`
    `maven-publish`
}
// ...
publishing {
    publications {
        create("myLibrary", MavenPublication::class.java) {
            from(components["java"])
        }
    }
}
build.gradle
plugins {
    id 'java-library'
    id 'maven-publish'
}
// ...
publishing {
    publications {
        myLibrary(MavenPublication) {
            from components.java
        }
    }
}

添加 javadoc 和 sources JAR

主要的 Javadoc 和 sources JAR 类似,您可以配置添加的功能,以便它为 Javadoc 和 sources 生成 JAR。

build.gradle.kts
java {
    registerFeature("mongodbSupport") {
        usingSourceSet(sourceSets["mongodbSupport"])
        withJavadocJar()
        withSourcesJar()
    }
}
build.gradle
java {
    registerFeature('mongodbSupport') {
        usingSourceSet(sourceSets.mongodbSupport)
        withJavadocJar()
        withSourcesJar()
    }
}

对功能的依赖项

如前所述,功能在发布时可能会造成损失。因此,消费者只能在以下情况下依赖功能。

  • 在多项目构建中,使用项目依赖项

  • 在 Gradle 模块元数据可用时,发布者必须发布它

  • 在 Ivy 世界中,通过声明对与功能匹配的配置的依赖项

消费者可以通过声明所需的功能来指定它需要生产者的特定功能。例如,如果生产者声明了像这样的“MySQL 支持”功能

build.gradle.kts
group = "org.gradle.demo"

sourceSets {
    create("mysqlSupport") {
        java {
            srcDir("src/mysql/java")
        }
    }
}

java {
    registerFeature("mysqlSupport") {
        usingSourceSet(sourceSets["mysqlSupport"])
    }
}

dependencies {
    "mysqlSupportImplementation"("mysql:mysql-connector-java:8.0.14")
}
build.gradle
group = 'org.gradle.demo'

sourceSets {
    mysqlSupport {
        java {
            srcDir 'src/mysql/java'
        }
    }
}

java {
    registerFeature('mysqlSupport') {
        usingSourceSet(sourceSets.mysqlSupport)
    }
}

dependencies {
    mysqlSupportImplementation 'mysql:mysql-connector-java:8.0.14'
}

然后消费者可以通过以下方式声明对 MySQL 支持功能的依赖项

build.gradle.kts
dependencies {
    // This project requires the main producer component
    implementation(project(":producer"))

    // But we also want to use its MySQL support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }
}
build.gradle
dependencies {
    // This project requires the main producer component
    implementation(project(":producer"))

    // But we also want to use its MySQL support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }
}

这将自动将 mysql-connector-java 依赖项添加到运行时类路径。如果有多个依赖项,所有依赖项都将被引入,这意味着功能可以用于将有助于功能的依赖项分组在一起。

类似地,如果使用 Gradle 模块元数据 发布了具有功能的外部库,则可以依赖该库提供的功能

build.gradle.kts
dependencies {
    // This project requires the main producer component
    implementation("org.gradle.demo:producer:1.0")

    // But we also want to use its MongoDB support
    runtimeOnly("org.gradle.demo:producer:1.0") {
        capabilities {
            requireCapability("org.gradle.demo:producer-mongodb-support")
        }
    }
}
build.gradle
dependencies {
    // This project requires the main producer component
    implementation('org.gradle.demo:producer:1.0')

    // But we also want to use its MongoDB support
    runtimeOnly('org.gradle.demo:producer:1.0') {
        capabilities {
            requireCapability("org.gradle.demo:producer-mongodb-support")
        }
    }
}

处理相互排斥的变体

使用功能作为处理功能的方式的主要优势在于,您可以精确地处理变体的兼容性。规则很简单

依赖关系图中的任何两个变体都不能提供相同的功能

我们可以利用这一点来确保在用户错误配置依赖项时 Gradle 会失败。考虑这样一种情况,您的库支持 MySQL、Postgres 和 MongoDB,但它只允许同时选择其中一个。我们可以通过确保每个功能也提供相同的功能来模拟此限制,从而使这些功能无法在同一图中一起使用。

build.gradle.kts
java {
    registerFeature("mysqlSupport") {
        usingSourceSet(sourceSets["mysqlSupport"])
        capability("org.gradle.demo", "producer-db-support", "1.0")
        capability("org.gradle.demo", "producer-mysql-support", "1.0")
    }
    registerFeature("postgresSupport") {
        usingSourceSet(sourceSets["postgresSupport"])
        capability("org.gradle.demo", "producer-db-support", "1.0")
        capability("org.gradle.demo", "producer-postgres-support", "1.0")
    }
    registerFeature("mongoSupport") {
        usingSourceSet(sourceSets["mongoSupport"])
        capability("org.gradle.demo", "producer-db-support", "1.0")
        capability("org.gradle.demo", "producer-mongo-support", "1.0")
    }
}

dependencies {
    "mysqlSupportImplementation"("mysql:mysql-connector-java:8.0.14")
    "postgresSupportImplementation"("org.postgresql:postgresql:42.2.5")
    "mongoSupportImplementation"("org.mongodb:mongodb-driver-sync:3.9.1")
}
build.gradle
java {
    registerFeature('mysqlSupport') {
        usingSourceSet(sourceSets.mysqlSupport)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-mysql-support', '1.0')
    }
    registerFeature('postgresSupport') {
        usingSourceSet(sourceSets.postgresSupport)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-postgres-support', '1.0')
    }
    registerFeature('mongoSupport') {
        usingSourceSet(sourceSets.mongoSupport)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-mongo-support', '1.0')
    }
}

dependencies {
    mysqlSupportImplementation 'mysql:mysql-connector-java:8.0.14'
    postgresSupportImplementation 'org.postgresql:postgresql:42.2.5'
    mongoSupportImplementation 'org.mongodb:mongodb-driver-sync:3.9.1'
}

这里,生产者声明了 3 个功能,每个数据库运行时支持一个

  • mysql-support 提供 db-supportmysql-support 功能

  • postgres-support 提供 db-supportpostgres-support 功能

  • mongo-support 提供 db-supportmongo-support 功能

如果消费者尝试同时获取postgres-supportmysql-support功能(这也适用于传递依赖),

build.gradle.kts
dependencies {
    // This project requires the main producer component
    implementation(project(":producer"))

    // Let's try to ask for both MySQL and Postgres support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-postgres-support")
        }
    }
}
build.gradle
dependencies {
    implementation(project(":producer"))

    // Let's try to ask for both MySQL and Postgres support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-postgres-support")
        }
    }
}

依赖解析将失败并出现以下错误

Cannot choose between
   org.gradle.demo:producer:1.0 variant mysqlSupportRuntimeElements and
   org.gradle.demo:producer:1.0 variant postgresSupportRuntimeElements
   because they provide the same capability: org.gradle.demo:producer-db-support:1.0