能力作为第一层概念
组件提供了许多功能,这些功能通常与用于提供这些功能的软件架构正交。例如,库可能在单个工件中包含多个功能。然而,这样的库将在单个 GAV(组、工件和版本)坐标上发布。这意味着,在单个坐标处,组件的不同“特征”可能共存。
使用 Gradle,显式声明组件提供的功能变得很有趣。为此,Gradle 提供了能力的概念。
功能通常是通过组合不同的功能来构建的。
在理想的情况下,组件不应声明对显式 GAV 的依赖关系,而应以功能的形式表达其需求:
-
“给我一个提供日志记录的组件”
-
“给我一个脚本引擎”
-
“给我一个支持 Groovy 的脚本引擎”
通过对功能进行建模,依赖关系管理引擎可以变得更加智能,并在依赖关系图中出现不兼容的功能时告诉您,或者在图中的不同模块提供相同功能时要求您进行选择。
声明外部模块的功能
值得注意的是,Gradle 支持为您构建的组件声明功能,也支持为外部组件声明功能(如果它们没有声明)。
例如,如果您的构建文件包含以下依赖项:
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")
}
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
。我们可以通过添加一条规则来预先检测冲突,该规则将声明两个日志框架提供相同的功能:
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)
}
}
}
}
}
dependencies {
// Activate the "LoggingCapability" rule
components.all(LoggingCapability)
}
@CompileStatic
class LoggingCapability implements ComponentMetadataRule {
final static Set<String> LOGGING_MODULES = ["log4j", "log4j-over-slf4j"] as Set<String>
void execute(ComponentMetadataContext context) {
context.details.with {
if (LOGGING_MODULES.contains(id.name)) {
allVariants {
it.withCapabilities {
// Declare that both log4j and log4j-over-slf4j provide the same capability
it.addCapability("log4j", "log4j", id.version)
}
}
}
}
}
}
通过添加此规则,我们将确保 Gradle能够检测到冲突并正确失败:
> 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)]
请参阅文档的功能部分以了解如何解决功能冲突。
声明本地组件的附加功能
所有组件都具有与组件相同的 GAV 坐标相对应的隐式功能。然而,也可以为组件声明附加的显式功能。当在不同 GAV 坐标发布的库是同一 API 的替代实现时,这很方便:
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")
}
}
}
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")
}
}
}
功能必须附加到传出配置,这是组件的消耗性配置。
此示例显示我们声明了两个功能:
-
com.acme:my-library:1.0
,对应于库的隐式能力 -
com.other:module:1.1
,对应于该库的另一个功能
值得注意的是,我们需要做 1. 因为一旦开始声明显式功能,那么所有功能都需要声明,包括隐式功能。
第二功能可以特定于该库,或者它可以对应于外部组件提供的功能。在这种情况下,如果com.other:module
出现在同一个依赖图中,构建将失败,消费者将必须选择要使用的模块。
功能发布到 Gradle 模块元数据。然而,它们在 POM 或 Ivy 元数据文件中没有等效项。因此,当发布此类组件时,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