修改和新增變體至現有元件以進行發佈

Gradle 的發佈模型基於元件的概念,這些元件由外掛程式定義。例如,Java 程式庫外掛程式定義了一個 java 元件,該元件對應於一個程式庫,但 Java 平台外掛程式定義了另一種元件,名為 javaPlatform,它實際上是一種不同的軟體元件(一個平台)。

有時我們想要為現有元件新增更多變體或修改現有變體。例如,如果您為不同的平台新增了 Java 程式庫的變體,您可能只想在 java 元件本身上宣告此額外變體。一般而言,宣告額外變體通常是發佈額外構件的最佳解決方案。

為了執行此類新增或修改,AdhocComponentWithVariants 介面宣告了兩個名為 addVariantsFromConfigurationwithVariantsFromConfiguration 的方法,它們接受兩個參數

  • 用作變體來源的傳出配置

  • 允許您篩選哪些變體將被發佈的自訂動作

若要利用這些方法,您必須確保您使用的 SoftwareComponent 本身是 AdhocComponentWithVariants,Java 外掛程式(Java、Java 程式庫、Java 平台)建立的元件就是這種情況。然後新增變體非常簡單

InstrumentedJarsPlugin.kt
val javaComponent = components.findByName("java") as AdhocComponentWithVariants
javaComponent.addVariantsFromConfiguration(outgoing) {
    // dependencies for this variant are considered runtime dependencies
    mapToMavenScope("runtime")
    // and also optional dependencies, because we don't want them to leak
    mapToOptional()
}
InstrumentedJarsPlugin.groovy
AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.components.findByName("java")
javaComponent.addVariantsFromConfiguration(outgoing) {
    // dependencies for this variant are considered runtime dependencies
    it.mapToMavenScope("runtime")
    // and also optional dependencies, because we don't want them to leak
    it.mapToOptional()
}

在其他情況下,您可能想要修改 Java 外掛程式已新增的變體之一。例如,如果您啟用 Javadoc 和來源的發佈,這些將成為 java 元件的額外變體。如果您只想發佈其中一個,例如僅 Javadoc 但沒有來源,您可以修改 sources 變體以不發佈

build.gradle.kts
java {
    withJavadocJar()
    withSourcesJar()
}

val javaComponent = components["java"] as AdhocComponentWithVariants
javaComponent.withVariantsFromConfiguration(configurations["sourcesElements"]) {
    skip()
}

publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            from(components["java"])
        }
    }
}
build.gradle
java {
    withJavadocJar()
    withSourcesJar()
}

components.java.withVariantsFromConfiguration(configurations.sourcesElements) {
    skip()
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
        }
    }
}

建立和發佈自訂元件

先前的範例中,我們示範了如何擴充或修改現有元件,例如 Java 外掛程式提供的元件。但 Gradle 也允許您建置自訂元件(不是 Java 程式庫,不是 Java 平台,不是 Gradle 原生支援的元件)。

若要建立自訂元件,您首先需要建立一個空的特設元件。目前,這只能透過外掛程式完成,因為您需要取得 SoftwareComponentFactory 的控制代碼

InstrumentedJarsPlugin.kt
class InstrumentedJarsPlugin @Inject constructor(
    private val softwareComponentFactory: SoftwareComponentFactory) : Plugin<Project> {
InstrumentedJarsPlugin.groovy
private final SoftwareComponentFactory softwareComponentFactory

@Inject
InstrumentedJarsPlugin(SoftwareComponentFactory softwareComponentFactory) {
    this.softwareComponentFactory = softwareComponentFactory
}

宣告自訂元件發佈什麼仍然透過 AdhocComponentWithVariants API 完成。對於自訂元件,第一步是建立自訂傳出變體,遵循本章中的說明。在這個階段,您應該擁有的是可用於跨專案依賴關係的變體,但我們現在要將其發佈到外部儲存庫。

InstrumentedJarsPlugin.kt
// create an adhoc component
val adhocComponent = softwareComponentFactory.adhoc("myAdhocComponent")
// add it to the list of components that this project declares
components.add(adhocComponent)
// and register a variant for publication
adhocComponent.addVariantsFromConfiguration(outgoing) {
    mapToMavenScope("runtime")
}
InstrumentedJarsPlugin.groovy
// create an adhoc component
def adhocComponent = softwareComponentFactory.adhoc("myAdhocComponent")
// add it to the list of components that this project declares
project.components.add(adhocComponent)
// and register a variant for publication
adhocComponent.addVariantsFromConfiguration(outgoing) {
    it.mapToMavenScope("runtime")
}

首先,我們使用工廠建立一個新的特設元件。然後,我們透過 addVariantsFromConfiguration 方法新增變體,這在上一節中有更詳細的描述。

在簡單的情況下,Configuration 和變體之間存在一對一的映射,在這種情況下,您可以發佈從單個 Configuration 發出的所有變體,因為它們實際上是同一件事。但是,在某些情況下,Configuration 與額外的 配置發佈相關聯,我們也將其稱為次要變體。此類配置在多專案建置中很有意義,但在外部發佈時則不然。例如,專案之間共用檔案目錄就是這種情況,但是無法將目錄直接發佈到 Maven 儲存庫(僅限於 jar 或 zip 等封裝內容)。查看 ConfigurationVariantDetails 類別,以了解如何跳過特定變體的發佈。如果已針對配置呼叫 addVariantsFromConfiguration,則可以使用 withVariantsFromConfiguration 執行對結果變體的進一步修改。

當發佈像這樣的特設元件時

  • Gradle 模組中繼資料將精確地表示已發佈的變體。特別是,所有傳出變體都將繼承已發佈配置的依賴關係、構件和屬性。

  • 將產生 Maven 和 Ivy 中繼資料檔案,但您需要透過 ConfigurationVariantDetails 類別宣告依賴關係如何映射到 Maven 範圍。

實際上,這表示以這種方式建立的元件可以像「本機元件」一樣被 Gradle 使用。

新增自訂構件至發佈

您應該擁抱 Gradle 的變體感知模型,而不是從構件的角度思考。預期單個模組可能需要多個構件。但是,如果額外構件代表選用功能,則情況很少止於此,它們也可能具有不同的依賴關係等等。

Gradle 透過Gradle 模組中繼資料支援發佈額外變體,這些變體使構件為依賴關係解析引擎所知。請參閱文件的變體感知共用章節,以了解如何宣告此類變體,並查看如何發佈自訂元件

如果您直接將額外構件附加到發佈,它們將在「上下文之外」發佈。這表示它們根本未在中繼資料中引用,然後只能透過依賴關係上的分類器直接尋址。與 Gradle 模組中繼資料相反,Maven pom 中繼資料將不包含有關額外構件的資訊,無論它們是透過變體還是直接新增的,因為變體無法以 pom 格式表示。

以下章節說明如果您確定中繼資料(例如 Gradle 或 POM 中繼資料)與您的使用案例無關,則如何直接發佈構件。例如,如果您的專案不需要被其他專案使用,並且發佈的唯一結果是構件本身。

一般而言,有兩個選項

  • 僅使用構件建立發佈

  • 根據具有中繼資料的元件將構件新增至發佈(不建議,而是調整元件或使用特設元件發佈,兩者也都會產生適合您構件的中繼資料)

若要建立基於構件的發佈,請首先定義自訂構件並將其附加到您選擇的 Gradle 配置。以下範例定義了由 rpm 工作(未顯示)產生的 RPM 構件,並將該構件附加到 conf 配置

build.gradle.kts
configurations {
    create("conf")
}
val rpmFile = layout.buildDirectory.file("rpms/my-package.rpm")
val rpmArtifact = artifacts.add("conf", rpmFile.get().asFile) {
    type = "rpm"
    builtBy("rpm")
}
build.gradle
configurations {
    conf
}
def rpmFile = layout.buildDirectory.file('rpms/my-package.rpm')
def rpmArtifact = artifacts.add('conf', rpmFile.get().asFile) {
    type = 'rpm'
    builtBy 'rpm'
}

artifacts.add() 方法 — 來自 ArtifactHandler — 傳回 PublishArtifact 類型的構件物件,然後可以在定義發佈時使用,如下列範例所示

build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("maven") {
            artifact(rpmArtifact)
        }
    }
}
build.gradle
publishing {
    publications {
        maven(MavenPublication) {
            artifact rpmArtifact
        }
    }
}
  • artifact() 方法接受發佈構件作為引數 — 就像範例中的 rpmArtifact — 以及 Project.file(java.lang.Object) 接受的任何引數類型,例如 File 實例、字串檔案路徑或封存工作。

  • 發佈外掛程式支援不同的構件配置屬性,因此請始終查看外掛程式文件以獲取更多詳細資訊。 Maven 發佈外掛程式Ivy 發佈外掛程式都支援 classifierextension 屬性。

  • 自訂構件需要在發佈中是不同的,通常透過 classifierextension 的唯一組合。請參閱您使用的外掛程式的文件,以了解精確的要求。

  • 如果您將 artifact() 與封存工作一起使用,Gradle 會自動使用該工作中的 classifierextension 屬性來填入構件的中繼資料。

現在您可以發佈 RPM。

如果您真的想根據元件將構件新增到發佈,而不是調整元件本身,您可以組合 from components.someComponentartifact someArtifact 表示法。

限制發佈至特定儲存庫

當您定義了多個發佈或儲存庫時,您通常希望控制哪些發佈發佈到哪些儲存庫。例如,考慮以下範例,該範例定義了兩個發佈 — 一個僅包含二進制檔案,另一個包含二進制檔案和相關來源 — 以及兩個儲存庫 — 一個供內部使用,另一個供外部使用者使用

build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("binary") {
            from(components["java"])
        }
        create<MavenPublication>("binaryAndSources") {
            from(components["java"])
            artifact(tasks["sourcesJar"])
        }
    }
    repositories {
        // change URLs to point to your repos, e.g. http://my.org/repo
        maven {
            name = "external"
            url = uri(layout.buildDirectory.dir("repos/external"))
        }
        maven {
            name = "internal"
            url = uri(layout.buildDirectory.dir("repos/internal"))
        }
    }
}
build.gradle
publishing {
    publications {
        binary(MavenPublication) {
            from components.java
        }
        binaryAndSources(MavenPublication) {
            from components.java
            artifact sourcesJar
        }
    }
    repositories {
        // change URLs to point to your repos, e.g. http://my.org/repo
        maven {
            name = 'external'
            url = layout.buildDirectory.dir('repos/external')
        }
        maven {
            name = 'internal'
            url = layout.buildDirectory.dir('repos/internal')
        }
    }
}

發佈外掛程式將建立工作,允許您將任一發佈發佈到任一儲存庫。它們還將這些工作附加到 publish 聚合工作。但假設您想要將僅限二進制檔案的發佈限制為外部儲存庫,而將包含來源的二進制檔案發佈限制為內部儲存庫。為此,您需要使發佈成為有條件的

Gradle 允許您基於條件透過 Task.onlyIf(String, org.gradle.api.specs.Spec) 方法跳過您想要的任何工作。以下範例示範了如何實作我們剛才提到的約束

build.gradle.kts
tasks.withType<PublishToMavenRepository>().configureEach {
    val predicate = provider {
        (repository == publishing.repositories["external"] &&
            publication == publishing.publications["binary"]) ||
        (repository == publishing.repositories["internal"] &&
            publication == publishing.publications["binaryAndSources"])
    }
    onlyIf("publishing binary to the external repository, or binary and sources to the internal one") {
        predicate.get()
    }
}
tasks.withType<PublishToMavenLocal>().configureEach {
    val predicate = provider {
        publication == publishing.publications["binaryAndSources"]
    }
    onlyIf("publishing binary and sources") {
        predicate.get()
    }
}
build.gradle
tasks.withType(PublishToMavenRepository) {
    def predicate = provider {
        (repository == publishing.repositories.external &&
            publication == publishing.publications.binary) ||
        (repository == publishing.repositories.internal &&
            publication == publishing.publications.binaryAndSources)
    }
    onlyIf("publishing binary to the external repository, or binary and sources to the internal one") {
        predicate.get()
    }
}
tasks.withType(PublishToMavenLocal) {
    def predicate = provider {
        publication == publishing.publications.binaryAndSources
    }
    onlyIf("publishing binary and sources") {
        predicate.get()
    }
}
gradle publish 的輸出
> gradle publish
> Task :compileJava
> Task :processResources
> Task :classes
> Task :jar
> Task :generateMetadataFileForBinaryAndSourcesPublication
> Task :generatePomFileForBinaryAndSourcesPublication
> Task :sourcesJar
> Task :publishBinaryAndSourcesPublicationToExternalRepository SKIPPED
> Task :publishBinaryAndSourcesPublicationToInternalRepository
> Task :generateMetadataFileForBinaryPublication
> Task :generatePomFileForBinaryPublication
> Task :publishBinaryPublicationToExternalRepository
> Task :publishBinaryPublicationToInternalRepository SKIPPED
> Task :publish

BUILD SUCCESSFUL in 0s
10 actionable tasks: 10 executed

您可能還想定義自己的聚合工作來協助您的工作流程。例如,假設您有幾個應發佈到外部儲存庫的發佈。一次發佈所有這些發佈而不發佈內部發佈可能會非常有用。

以下範例示範了如何透過定義聚合工作 — publishToExternalRepository — 該工作依賴於所有相關的發佈工作來完成此操作

build.gradle.kts
tasks.register("publishToExternalRepository") {
    group = "publishing"
    description = "Publishes all Maven publications to the external Maven repository."
    dependsOn(tasks.withType<PublishToMavenRepository>().matching {
        it.repository == publishing.repositories["external"]
    })
}
build.gradle
tasks.register('publishToExternalRepository') {
    group = 'publishing'
    description = 'Publishes all Maven publications to the external Maven repository.'
    dependsOn tasks.withType(PublishToMavenRepository).matching {
        it.repository == publishing.repositories.external
    }
}

此特定範例透過將 TaskCollection.withType(java.lang.Class)PublishToMavenRepository 工作類型一起使用來自動處理相關發佈工作的引入或移除。如果您要發佈到與 Ivy 相容的儲存庫,則可以使用 PublishToIvyRepository 執行相同的操作。

配置發佈工作

發佈外掛程式會在專案評估後建立其非聚合工作,這表示您無法直接從建置腳本中引用它們。如果您想要配置這些工作中的任何一項,則應使用延遲工作配置。這可以透過專案的 tasks 集合以多種方式完成。

例如,假設您想要變更 generatePomFileForPubNamePublication 工作寫入其 POM 檔案的位置。您可以透過使用 TaskCollection.withType(java.lang.Class) 方法來完成此操作,如本範例所示

build.gradle.kts
tasks.withType<GenerateMavenPom>().configureEach {
    val matcher = Regex("""generatePomFileFor(\w+)Publication""").matchEntire(name)
    val publicationName = matcher?.let { it.groupValues[1] }
    destination = layout.buildDirectory.file("poms/${publicationName}-pom.xml").get().asFile
}
build.gradle
tasks.withType(GenerateMavenPom).all {
    def matcher = name =~ /generatePomFileFor(\w+)Publication/
    def publicationName = matcher[0][1]
    destination = layout.buildDirectory.file("poms/${publicationName}-pom.xml").get().asFile
}

上面的範例使用正則表達式從工作名稱中提取發佈的名稱。這是為了避免可能產生的所有 POM 檔案的檔案路徑之間發生衝突。如果您只有一個發佈,則不必擔心此類衝突,因為只會有一個 POM 檔案。