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 库插件设置配置的方式非常相似。
依赖项范围配置的创建方式与主功能相同
-
配置
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-cased 功能名称。
例如,如果组件的组是 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 库插件将负责为您注册额外的变体,因此不需要额外的配置,只需要常规的出版物。
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