能力作为第一层概念

组件提供了许多功能,这些功能通常与用于提供这些功能的软件架构正交。例如,库可能在单个工件中包含多个功能。然而,这样的库将在单个 GAV(组、工件和版本)坐标上发布。这意味着,在单个坐标处,组件的不同“特征”可能共存。

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

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

在理想的情况下,组件不应声明对显式 GAV 的依赖关系,而应以功能的形式表达其需求:

  • “给我一个提供日志记录的组件”

  • “给我一个脚本引擎”

  • “给我一个支持 Groovy 的脚本引擎”

通过对功能进行建模,依赖关系管理引擎可以变得更加智能,并在依赖关系图中出现不兼容的功能时告诉您,或者在图中的不同模块提供相同功能时要求您进行选择。

声明外部模块的功能

值得注意的是,Gradle 支持为您构建的组件声明功能,也支持为外部组件声明功能(如果它们没有声明)。

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

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")
}
build.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'
}

事实上,很难弄清楚您最终会在类路径上有两个日志框架。其实,zookeeper就会带进来log4j,我们要用的地方就是哪里log4j-over-slf4j。我们可以通过添加一条规则来预先检测冲突,该规则将声明两个日志框架提供相同的功能:

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)
                }
            }
        }
    }
}
build.gradle
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 的替代实现时,这很方便:

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")
        }
    }
}
build.gradle
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")
        }
    }
}

功能必须附加到传出配置,这是组件的消耗性配置。

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

  1. com.acme:my-library:1.0,对应于库的隐​​式能力

  2. 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