功能作為第一層級概念
元件提供許多功能,這些功能通常與用於提供這些功能的軟體架構正交。例如,一個函式庫可能在單一成品中包含多項功能。然而,此類函式庫將會在單一 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