Gradle 支持功能的概念:通常情况下,一个库可以拆分成多个相关但不同的库,其中每个功能可以与主库一起使用。
功能允许组件公开多个相关的库,每个库都可以声明自己的依赖项。这些库以变体的形式公开,类似于主库如何为其 API 和运行时公开变体。
这允许许多不同的场景(列表并非详尽无遗)
-
(更好)替代 Maven 可选依赖项
-
主库构建时支持运行时功能的不同互斥实现;用户必须为每个此类功能选择一个且仅一个实现
-
主库构建时支持可选的运行时功能,每个功能都需要一组不同的依赖项
-
主库附带补充功能,如测试夹具
-
主库附带一个主要工件,启用额外的功能需要额外的工件
通过能力选择功能
声明对组件的依赖通常通过提供一组坐标(组、工件、版本,也称为 GAV 坐标)来完成。这允许引擎确定我们正在寻找的组件,但这样的组件可能提供不同的变体。变体通常根据用途选择。例如,我们可能会选择不同的变体来编译组件(在这种情况下,我们需要组件的 API)或在执行代码时(在这种情况下,我们需要组件的运行时)。组件的所有变体都提供许多能力,这些能力也使用 GAV 坐标类似地表示。
能力由 GAV 坐标表示,但您必须将其视为功能描述
-
“我提供 SLF4J 绑定”
-
“我提供对 MySQL 的运行时支持”
-
“我提供 Groovy 运行时”
一般来说,图中存在两个提供相同事物的组件是一个问题(它们冲突)。
这是一个重要的概念,因为
-
默认情况下,变体提供与其组件的 GAV 坐标对应的能力
-
依赖关系图中没有两个变体可以提供相同的能力
-
只要单个组件的多个变体提供不同的能力,就可以选择它们
典型的组件将仅提供具有默认能力的变体。例如,Java 库公开两个变体(API 和运行时),它们提供相同的能力。因此,在一个依赖关系图中同时拥有单个组件的 API 和运行时是一个错误。
但是,想象一下您需要组件的运行时和测试夹具运行时。那么这是允许的,只要库的运行时和测试夹具运行时变体声明了不同的能力。
如果我们这样做,那么消费者将必须声明两个依赖项
-
一个依赖于“主”功能,即库
-
一个依赖于“测试夹具”功能,通过请求其能力
虽然解析引擎独立于生态系统支持多变体组件,但功能目前仅在使用 Java 插件时可用。 |
注册功能
可以通过应用 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 插件 设置配置的方式非常相似。
依赖范围配置的创建方式与主功能相同
-
配置
mongodbSupportApi
,用于为此功能声明 API 依赖项 -
配置
mongodbSupportImplementation
,用于为此功能声明实现依赖项 -
配置
mongodbSupportRuntimeOnly
,用于为此功能声明运行时专用依赖项 -
配置
mongodbSupportCompileOnly
,用于为此功能声明编译时专用依赖项 -
配置
mongodbSupportCompileOnlyApi
,用于为此功能声明编译时专用 API 依赖项
此外,可消费配置的创建方式与主功能相同
-
配置
mongodbSupportApiElements
,供消费者获取此功能的工件和 API 依赖项 -
配置
mongodbSupportRuntimeElements
,供消费者获取此功能的工件和运行时依赖项
功能应具有同名的源集。Gradle 将创建一个 Jar
任务来捆绑从功能源集构建的类,使用与功能的 kebab-case 名称对应的分类器。
注册功能时,请勿使用主源集。此行为将在 Gradle 的未来版本中弃用。 |
大多数用户只需要关心依赖范围配置,以声明此功能的特定依赖项
dependencies {
"mongodbSupportImplementation"("org.mongodb:mongodb-driver-sync:3.9.1")
}
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-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
}
}
}
添加 javadoc 和 sources JAR
与 主 Javadoc 和 sources JAR 类似,您可以配置添加的功能,以便它为 Javadoc 和 sources 生成 JAR。
java {
registerFeature("mongodbSupport") {
usingSourceSet(sourceSets["mongodbSupport"])
withJavadocJar()
withSourcesJar()
}
}
java {
registerFeature('mongodbSupport') {
usingSourceSet(sourceSets.mongodbSupport)
withJavadocJar()
withSourcesJar()
}
}
依赖于功能
如前所述,发布功能时可能会有损。因此,消费者只能在以下情况下依赖于功能
-
使用项目依赖项(在多项目构建中)
-
使用可用的 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")
}
}
}
这将自动将 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")
}
}
}
处理互斥变体
使用能力作为处理功能的方式的主要优势在于,您可以精确地处理变体的兼容性。规则很简单
依赖关系图中没有两个变体可以提供相同的能力
我们可以利用这一点来确保在用户错误配置依赖项时 Gradle 会失败。考虑这样一种情况,您的库支持 MySQL、Postgres 和 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'
}
在这里,生产者声明了 3 个功能,每个功能用于一个数据库运行时支持
-
mysql-support
同时提供db-support
和mysql-support
能力 -
postgres-support
同时提供db-support
和postgres-support
能力 -
mongo-support
同时提供db-support
和mongo-support
能力
然后,如果消费者尝试同时获取 postgres-support
和 mysql-support
功能(这也适用于传递依赖)
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