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 Library 插件 设置配置的方式非常相似。

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

  • 配置 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-case 功能名称。

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

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

发布功能

根据元数据文件格式,发布功能可能会有损

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

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

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

仅在使用 maven-publishivy-publish 插件时才支持发布功能。Java Library 插件将负责为您注册额外的变体,因此无需额外的配置,只需常规发布即可

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