作为第一级概念的能力

组件提供了许多功能,这些功能通常与用于提供这些功能的软件体系结构正交。例如,一个库可能在单个构件中包含多个特性。但是,这样的库将以单个 GAV(组、构件和版本)坐标发布。这意味着,在单一坐标下,组件的不同“特征”可能共存。

使用 Gradle,显式声明组件提供的功能变得很有趣。为此,Gradle 提供了 能力 的概念。

通常通过组合不同的 capabilities 来构建功能。

在理想的世界中,组件不应该声明对显式 GAV 的依赖,而是根据功能表达它们的需求:

  • "give me a component which provides logging"

  • "give me a scripting engine"

  • "give me a scripting engine that supports Groovy"

通过对 capabilities 建模,依赖管理引擎可以更智能,并在依赖图中有 incompatible capabilities 时告诉您,或者在图中不同模块提供相同的 capability 时要求您选择。

声明外部模块的功能

值得注意的是,Gradle 支持为您构建的组件声明功能,但也支持为外部组件声明功能,以防它们不支持。

例如,如果您的构建文件包含以下依赖项:

KotlinGroovy
build.gradle.kts
dependencies {
    // This dependency will bring log4:log4j transitively
    implementation("org.apache.zookeeper:zookeeper:3.4.9")

    // We use log4j over slf4j
    implementation("org.slf4j:log4j-over-slf4j:1.7.10")
}

实际上,很难确定您最终会在类路径上使用两个日志记录框架。事实上, zookeeper 会引入 log4j ,而我们要使用的是 log4j-over-slf4j 。我们可以通过添加一条规则来抢先检测冲突,该规则将声明两个日志记录框架提供相同的功能:

KotlinGroovy
build.gradle.kts
dependencies {
    // Activate the "LoggingCapability" rule
    components.all(LoggingCapability::class.java)
}

class LoggingCapability : ComponentMetadataRule {
    val loggingModules = setOf("log4j", "log4j-over-slf4j")

    override
    fun execute(context: ComponentMetadataContext) = context.details.run {
        if (loggingModules.contains(id.name)) {
            allVariants {
                withCapabilities {
                    // Declare that both log4j and log4j-over-slf4j provide the same capability
                    addCapability("log4j", "log4j", id.version)
                }
            }
        }
    }
}

通过添加此规则,我们将确保 Gradle will 检测冲突并正确失败:

> Could not resolve all files for configuration ':compileClasspath'.
   > Could not resolve org.slf4j:log4j-over-slf4j:1.7.10.
     Required by:
         project :
      > Module 'org.slf4j:log4j-over-slf4j' has been rejected:
           Cannot select module with conflict on capability 'log4j:log4j:1.7.10' also provided by [log4j:log4j:1.2.16(compile)]
   > Could not resolve log4j:log4j:1.2.16.
     Required by:
         project : > org.apache.zookeeper:zookeeper:3.4.9
      > Module 'log4j:log4j' has been rejected:
           Cannot select module with conflict on capability 'log4j:log4j:1.2.16' also provided by [org.slf4j:log4j-over-slf4j:1.7.10(compile)]

请参阅 文档的功能部分 了解如何修复功能冲突。

为本地组件声明附加功能

所有组件都有一个 implicit capability 对应于与组件相同的 GAV 坐标。但是,也可以为组件声明额外的explicit capabilities。只要在不同 GAV 坐标上发布的库是同一 API 的alternate implementation,这就很方便:

KotlinGroovy
build.gradle.kts
configurations {
    apiElements {
        outgoing {
            capability("com.acme:my-library:1.0")
            capability("com.other:module:1.1")
        }
    }
    runtimeElements {
        outgoing {
            capability("com.acme:my-library:1.0")
            capability("com.other:module:1.1")
        }
    }
}

功能必须附加到 outgoing configurations ,这是组件的 耗材配置

此示例表明我们声明了两个功能:

  1. com.acme:my-library:1.0 ,对应库的 implicit capability

  2. com.other:module:1.1 ,对应于该库的另一个功能

值得注意的是我们需要做 1. 因为一旦您开始声明 explicit 功能,就需要声明 all 功能,包括 implicit 功能。

第二个能力可以特定于这个库,或者它可以对应于外部组件提供的能力。在那种情况下,如果 com.other:module 出现在同一个依赖图中,构建将失败并且消费者 必须选择要使用的模块

功能发布到 Gradle 模块元数据。但是,它们在 POM 或 Ivy 元数据文件中有no equivalent。因此,在发布此类组件时,Gradle 会警告您此功能仅适用于 Gradle 消费者:

Maven publication 'maven' contains dependencies that cannot be represented in a published pom file.
  - Declares capability com.acme:my-library:1.0
  - Declares capability com.other:module:1.1