如何在 Gradle 中为库创建特性变体
Gradle 支持**特性变体**的概念,允许将单个库拆分为多个相关但独立的模块。每个**特性变体**可以声明自己的一组依赖项,并且可以与主库一同单独消费。
例如
plugins {
id("java-library")
}
java {
registerFeature("jsonSupport") {
usingSourceSet(sourceSets.create("jsonSupport"))
}
}
dependencies {
"jsonSupportApi"("com.fasterxml.jackson.core:jackson-databind:2.16.0")
}
plugins {
id("application")
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.example:library:1.0") {
capabilities {
requireCapability("org.example:library-json-support")
}
}
}
为什么要使用特性变体?
特性变体相比传统的依赖管理提供了几个优势
-
改进的模块化:在不同的库功能之间有清晰的边界。
-
细粒度的依赖管理:消费者只包含他们特别需要的依赖项。
-
支持多种变体:单个库可以暴露针对不同用例定制的变体(例如,
debug
版本 vs.release
版本构建)。
常见用例包括
-
提供可选依赖项(是 Maven 可选依赖项 的强大替代方案)。
-
提供运行时特性的多个互斥实现,要求用户精确选择一个变体。
-
支持各自具有独特依赖项的可选运行时特性。
-
分发补充模块,例如测试 fixture 或集成支持。
-
启用可以与主库制品可选地一同包含的其他制品。
步骤 1:通过能力 (Capabilities) 选择特性
依赖项通常使用被称为 GAV(group、artifact、version)的坐标声明,这些坐标标识了组件。然而,单个组件可以提供多个变体,每个变体适合不同的用途——例如编译或运行时执行。
每个变体提供一组能力 (capabilities),这些能力也通过 GAV 坐标标识,但最好理解为特性描述,例如
-
“我提供一个 SLF4J 绑定”
-
“我提供对 MySQL 的运行时支持”
-
“我提供一个 Groovy 运行时”
重要的是要注意
-
默认情况下,每个变体都提供一个与组件的 GAV 坐标相匹配的能力 (capability)。
-
在依赖图中,提供相同能力的两个变体不能共存。
-
如果单个组件的多个变体提供了不同的能力,它们可以共存。
例如,一个典型的 Java 库有 API 和运行时变体,两者都提供相同的能力。因此,在依赖图中同时包含这两个变体是错误的。然而,只要这些变体声明了不同的能力,就可以同时使用运行时和 test-fixtures 运行时变体。
为了实现这一点,消费者必须显式声明单独的依赖项
-
主库的一个依赖项(“main”特性)。
-
另一个显式要求附加特性(例如 test fixtures)能力的依赖项。
java {
registerFeature("testFixtures") {
usingSourceSet(sourceSets.create("testFixtures"))
capability("com.example", "library-test-fixtures", version.toString())
}
}
虽然解析引擎独立于生态系统支持多变体组件,但**特性变体**目前**仅由 Java 插件支持**。 |
步骤 2:注册特性
可以通过应用 java-library
插件来声明特性。
以下代码演示了如何声明一个名为 mongodbSupport
的特性
sourceSets {
create("mongodbSupport") {
java {
srcDir("src/mongodb/java")
}
}
}
java {
registerFeature("mongodbSupport") {
usingSourceSet(sourceSets["mongodbSupport"])
}
}
sourceSets {
mongodbSupport {
java {
srcDir 'src/mongodb/java'
}
}
}
java {
registerFeature('mongodbSupport') {
usingSourceSet(sourceSets.mongodbSupport)
}
}
Gradle 将自动为您设置许多东西,这与 Java Library Plugin 设置配置的方式非常相似。
创建特性变体时,Gradle 会自动配置以下依赖范围
-
featureNameApi
用于特性的 API 依赖项。 -
featureNameImplementation
用于特定于实现的依赖项。 -
featureNameRuntimeOnly
用于仅运行时依赖项。 -
featureCompileOnly
用于仅编译时依赖项。
在此示例中,名为“mongodbSupport”的特性会自动创建这些配置
-
mongodbSupportApi
- 用于*声明此特性的 API 依赖项* -
mongodbSupportImplementation
- 用于*声明此特性的实现依赖项* -
mongodbSupportRuntimeOnly
- 用于*声明此特性的仅运行时依赖项* -
mongodbSupportCompileOnly
- 用于*声明此特性的仅编译时依赖项*
此外,Gradle 还公开了两个特定于变体的配置供外部消费
-
mongodbSupportRuntimeElements
- 消费者用于获取此特性的制品和 API 依赖项 -
mongodbSupportApiElements
- 消费者用于获取此特性的制品和运行时依赖项
特性变体应具有一个名称相同的相应*源集*。
Gradle 会为每个特性的源集自动创建一个 Jar
任务,并使用与特性名称匹配的分类器 (classifier)。
注册特性时,请勿使用 *main* 源集。此行为将在未来版本的 Gradle 中弃用。 |
大多数用户只会关心依赖范围配置,用于声明此特性的特定依赖项
dependencies {
"mongodbSupportImplementation"("org.mongodb:mongodb-driver-sync:3.9.1")
}
dependencies {
mongodbSupportImplementation 'org.mongodb:mongodb-driver-sync:3.9.1'
}
按照约定,Gradle 使用与主组件相同的 group 和 version 映射特性变体的能力,而能力的名称则由主组件名称后跟一个 -
和特性名称的 kebab-case 版本构成。
例如,如果您的组件具有
-
group:
org.gradle.demo
-
name:
provider
-
version:
1.0
并且您定义了一个名为 mongodbSupport
的特性,则该特性的能力将是
-
org.gradle.demo:provider-mongodb-support:1.0
如果您选择自定义能力名称或添加其他能力,建议遵循此约定。
步骤 3:发布特性
仅支持使用 maven-publish 和 ivy-publish 插件发布特性变体。 |
Java Library 插件会自动为您注册其他变体,除了标准的发布设置外,无需额外配置
plugins {
`java-library`
`maven-publish`
}
// ...
publishing {
publications {
create("myLibrary", MavenPublication::class.java) {
from(components["java"])
}
}
}
plugins {
id 'java-library'
id 'maven-publish'
}
// ...
publishing {
publications {
myLibrary(MavenPublication) {
from components.java
}
}
}
根据所使用的元数据格式,发布特性可能会有所不同
-
使用 Gradle 模块元数据,所有特性都得到完全保留,消费者可以充分利用特性变体。
-
使用 POM 元数据(Maven),特性被表示为**可选依赖项**,特性制品会使用不同的分类器 (classifier) 发布。
-
使用 Ivy 元数据,特性作为附加配置发布,默认配置不会自动包含这些附加配置。
添加 Javadoc 和 Sources JAR
与 主要的 Javadoc 和 sources JARs 类似,您可以配置特性变体来生成自己的 Javadoc 和 sources JARs
java {
registerFeature("mongodbSupport") {
usingSourceSet(sourceSets["mongodbSupport"])
withJavadocJar()
withSourcesJar()
}
}
java {
registerFeature('mongodbSupport') {
usingSourceSet(sourceSets.mongodbSupport)
withJavadocJar()
withSourcesJar()
}
}
步骤 4:特性依赖
消费特性变体时,重要的是要注意特性的支持可能是有限的或“有损的”,具体取决于特性如何发布。消费者项目可以在这些条件下依赖特性变体
-
在多项目 Gradle 构建中使用项目依赖。
-
使用 Gradle 模块元数据,这必须由生产者显式发布。
-
使用 Ivy 元数据,显式指定对表示所需特性的配置的依赖项。
消费者通过显式要求其能力来声明特定的特性依赖项。例如,如果生产者定义了一个用于“MySQL 支持”的特性变体,如下所示
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")
}
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 支持”特性
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")
}
}
}
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")
}
}
}
此设置会自动在消费者的运行时 classpath 中包含 mysql-connector-java 依赖项。如果多个依赖项被分组在特性变体下,当需要该能力时,所有这些依赖项都会被包含。
类似地,当使用 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")
}
}
}
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")
}
}
}
步骤 5:处理互斥变体
使用**能力 (capabilities)** 来管理**特性变体**提供了一种处理变体之间兼容性的精确方法。
需要记住的关键规则是
在依赖图中,没有任何两个变体可以提供相同的能力。
这条规则允许 Gradle 在互斥变体之间强制执行排他性。
例如,假设您有一个库提供数据库特性的多个互斥实现(例如 MySQL
、PostgreSQL
和 MongoDB
)。通过为每个变体分配一个共享能力,您可以确保这些变体不能在同一个依赖图中共存。
例如,生产者可能定义变体如下
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")
}
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'
}
这里
-
mysql-support
变体提供能力:db-support
和mysql-support
。 -
postgres-support
变体提供能力:db-support
和postgres-support
。 -
mongo-support
变体提供能力:db-support
和mongo-support
。
如果消费者尝试包含多个冲突的特性,例如同时包含 MySQL
和 PostgreSQL
支持
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")
}
}
}
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