功能作為第一層級概念

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