Maven 發佈外掛程式提供將建置成品發佈至 Apache Maven 儲存庫的功能。發佈至 Maven 儲存庫的模組可由 Maven、Gradle(請參閱 宣告相依性)和了解 Maven 儲存庫格式的其他工具使用。您可以在 發佈概觀 中瞭解發佈的基本原理。

用法

若要使用 Maven 發佈外掛程式,請在您的建置指令碼中加入下列內容

build.gradle.kts
plugins {
    `maven-publish`
}
build.gradle
plugins {
    id 'maven-publish'
}

Maven 發佈外掛程式使用專案上的擴充功能,其名稱為 publishing,類型為 PublishingExtension。此擴充功能提供一個命名刊物的容器和一個命名儲存庫的容器。Maven 發佈外掛程式與 MavenPublication 刊物和 MavenArtifactRepository 儲存庫搭配使用。

工作

generatePomFileForPubNamePublicationGenerateMavenPom

為名稱為 PubName 的刊物建立 POM 檔案,填入已知的元資料,例如專案名稱、專案版本和相依性。POM 檔案的預設位置為 build/publications/$pubName/pom-default.xml

publishPubNamePublicationToRepoNameRepositoryPublishToMavenRepository

PubName 發行物發布到名為 RepoName 的存放庫。如果您有一個沒有明確名稱的存放庫定義,RepoName 將會是「Maven」。

publishPubNamePublicationToMavenLocalPublishToMavenLocal

PubName 發行物複製到本機 Maven 快取 — 通常為 <目前使用者的家目錄>/.m2/repository — 以及發行物的 POM 檔案和其他元資料。

publish

依賴於:所有 publishPubNamePublicationToRepoNameRepository 任務

一個彙總任務,將所有已定義的發行物發布到所有已定義的存放庫。它包括將發行物複製到本機 Maven 快取。

publishToMavenLocal

依賴於:所有 publishPubNamePublicationToMavenLocal 任務

將所有已定義的發行物複製到本機 Maven 快取,包括其元資料(POM 檔案等)。

發行物

這個外掛程式提供 發行物,類型為 MavenPublication。若要了解如何定義和使用發行物,請參閱 基本發布 部分。

在 Maven 發行物中,您可以設定四項主要設定

您可以在 完整的發布範例 中看到所有這些動作。MavenPublication 的 API 文件包含其他程式碼範例。

已產生 POM 中的身分值

已產生 POM 檔案的屬性將包含衍生自下列專案屬性的身分值

覆寫預設的身分值很簡單:只要在設定 MavenPublication 時指定 groupIdartifactIdversion 屬性即可。

build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("maven") {
            groupId = "org.gradle.sample"
            artifactId = "library"
            version = "1.1"

            from(components["java"])
        }
    }
}
build.gradle
publishing {
    publications {
        maven(MavenPublication) {
            groupId = 'org.gradle.sample'
            artifactId = 'library'
            version = '1.1'

            from components.java
        }
    }
}
某些儲存庫無法處理所有支援字元。例如,在 Windows 上發佈到檔案系統後援儲存庫時,無法將 : 字元用作識別碼。

Maven 將 groupIdartifactId 限制為有限的字元組 ([A-Za-z0-9_\\-.]+),而 Gradle 會強制執行此限制。對於 version(以及人工製品 extensionclassifier 屬性),Gradle 會處理任何有效的 Unicode 字元。

唯一明確禁止的 Unicode 值為 \/ 和任何 ISO 控制字元。提供的數值會在發佈初期驗證。

自訂已產生 POM

已產生的 POM 檔案可以在發佈前自訂。例如,在將函式庫發佈到 Maven Central 時,您需要設定特定元資料。Maven Publish 外掛提供一個 DSL 來達成此目的。請參閱 DSL 參考中的 MavenPom,以取得可用屬性和方法的完整文件。下列範例顯示如何使用最常見的方法

build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            pom {
                name = "My Library"
                description = "A concise description of my library"
                url = "http://www.example.com/library"
                properties = mapOf(
                    "myProp" to "value",
                    "prop.with.dots" to "anotherValue"
                )
                licenses {
                    license {
                        name = "The Apache License, Version 2.0"
                        url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
                    }
                }
                developers {
                    developer {
                        id = "johnd"
                        name = "John Doe"
                        email = "john.doe@example.com"
                    }
                }
                scm {
                    connection = "scm:git:git://example.com/my-library.git"
                    developerConnection = "scm:git:ssh://example.com/my-library.git"
                    url = "http://example.com/my-library/"
                }
            }
        }
    }
}
build.gradle
publishing {
    publications {
        mavenJava(MavenPublication) {
            pom {
                name = 'My Library'
                description = 'A concise description of my library'
                url = 'http://www.example.com/library'
                properties = [
                    myProp: "value",
                    "prop.with.dots": "anotherValue"
                ]
                licenses {
                    license {
                        name = 'The Apache License, Version 2.0'
                        url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                    }
                }
                developers {
                    developer {
                        id = 'johnd'
                        name = 'John Doe'
                        email = 'john.doe@example.com'
                    }
                }
                scm {
                    connection = 'scm:git:git://example.com/my-library.git'
                    developerConnection = 'scm:git:ssh://example.com/my-library.git'
                    url = 'http://example.com/my-library/'
                }
            }
        }
    }
}

自訂相依項版本

支援兩種發佈相依項的策略

宣告版本(預設)

此策略會發佈由建置指令碼作者在 dependencies 區塊中使用相依項宣告所定義的版本。任何其他類型的處理,例如透過 變更已解析版本的規則,都不會納入考量。

已解析版本

此策略會發佈在建置期間已解析的版本,可能透過套用解析規則和自動衝突解決。這項優點是已發佈版本與已發佈人工製品測試的版本相符。

已解析版本的範例使用案例

  • 專案對相依項使用動態版本,但偏好公開已解析版本以提供給使用者。

  • 相依項鎖定 結合使用時,您想發佈已鎖定的版本。

  • 專案利用 Gradle 的豐富版本約束,這些約束在轉換成 Maven 時會造成損失。它會發布已解析的版本,而不是依賴轉換。

這是透過使用 versionMapping DSL 方法來完成,此方法允許設定 VersionMappingStrategy

build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            versionMapping {
                usage("java-api") {
                    fromResolutionOf("runtimeClasspath")
                }
                usage("java-runtime") {
                    fromResolutionResult()
                }
            }
        }
    }
}
build.gradle
publishing {
    publications {
        mavenJava(MavenPublication) {
            versionMapping {
                usage('java-api') {
                    fromResolutionOf('runtimeClasspath')
                }
                usage('java-runtime') {
                    fromResolutionResult()
                }
            }
        }
    }
}

在上述範例中,Gradle 會使用在 runtimeClasspath 上解析的版本,作為在 api 中宣告的相依項,這些相依項會對應到 Maven 的 compile 範圍。Gradle 也會使用在 runtimeClasspath 上解析的版本,作為在 implementation 中宣告的相依項,這些相依項會對應到 Maven 的 runtime 範圍。fromResolutionResult() 表示 Gradle 應使用變異的預設類別路徑,而 runtimeClasspathjava-runtime 的預設類別路徑。

儲存庫

此外掛程式提供 儲存庫,其類型為 MavenArtifactRepository。如需了解如何定義和使用儲存庫來發布,請參閱 基本發布 的區段。

以下是定義發布儲存庫的簡單範例

build.gradle.kts
publishing {
    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = uri(layout.buildDirectory.dir("repo"))
        }
    }
}
build.gradle
publishing {
    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir('repo')
        }
    }
}

您會想要設定儲存庫的兩項主要內容為

  • URL (必要)

  • 名稱 (選用)

只要在建置指令碼中具有獨特的名稱,您就可以定義多個儲存庫。您也可以宣告一個 (且僅限一個) 沒有名稱的儲存庫。該儲存庫會採用「Maven」的隱含名稱。

您也可以設定連線到儲存庫所需的任何驗證詳細資料。如需更多詳細資料,請參閱 MavenArtifactRepository

快照和發行版儲存庫

將快照和發行版發布到不同的 Maven 儲存庫是很常見的做法。達成此目標的簡單方法是根據專案版本設定儲存庫 URL。下列範例使用一個 URL 來處理版本結尾為「SNAPSHOT」的情況,並使用另一個 URL 來處理其他情況

build.gradle.kts
publishing {
    repositories {
        maven {
            val releasesRepoUrl = layout.buildDirectory.dir("repos/releases")
            val snapshotsRepoUrl = layout.buildDirectory.dir("repos/snapshots")
            url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
        }
    }
}
build.gradle
publishing {
    repositories {
        maven {
            def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
            def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
            url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
        }
    }
}

同樣地,您可以使用 專案或系統屬性 來決定要發佈到哪個儲存庫。以下範例使用發佈儲存庫,如果專案屬性 release 已設定,例如當使用者執行 gradle -Prelease publish

build.gradle.kts
publishing {
    repositories {
        maven {
            val releasesRepoUrl = layout.buildDirectory.dir("repos/releases")
            val snapshotsRepoUrl = layout.buildDirectory.dir("repos/snapshots")
            url = uri(if (project.hasProperty("release")) releasesRepoUrl else snapshotsRepoUrl)
        }
    }
}
build.gradle
publishing {
    repositories {
        maven {
            def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
            def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
            url = project.hasProperty('release') ? releasesRepoUrl : snapshotsRepoUrl
        }
    }
}

發佈到 Maven Local

對於與本機 Maven 安裝整合,有時將模組發佈到 Maven 本機儲存庫(通常位於 <目前使用者的家目錄>/.m2/repository),連同其 POM 檔案和其他元資料會很有用。在 Maven 術語中,這稱為「安裝」模組。

Maven 發佈外掛透過自動為 publishing.publications 容器中的每個 MavenPublication 建立 PublishToMavenLocal 任務,讓這件事變得容易。任務名稱遵循 publishPubNamePublicationToMavenLocal 的模式。這些任務中的每個任務都連線到 publishToMavenLocal 彙總任務。您不需要在 publishing.repositories 區段中放入 mavenLocal()

發佈 Maven 搬移資訊

當專案變更其發佈的成品的 groupIdartifactId座標)時,讓使用者知道可以在哪裡找到新的成品很重要。Maven 可以透過搬移功能協助這件事。這項功能運作的方式是專案在舊座標下發佈一個額外的成品,僅包含一個最小的搬移 POM;該 POM 檔案指定可以在哪裡找到新的成品。Maven 儲存庫瀏覽器和建置工具隨後可以通知使用者成品的座標已變更。

為此,專案新增一個額外的 MavenPublication,指定一個 MavenPomRelocation

build.gradle.kts
publishing {
    publications {
        // ... artifact publications

        // Specify relocation POM
        create<MavenPublication>("relocation") {
            pom {
                // Old artifact coordinates
                groupId = "com.example"
                artifactId = "lib"
                version = "2.0.0"

                distributionManagement {
                    relocation {
                        // New artifact coordinates
                        groupId = "com.new-example"
                        artifactId = "lib"
                        version = "2.0.0"
                        message = "groupId has been changed"
                    }
                }
            }
        }
    }
}
build.gradle
publishing {
    publications {
        // ... artifact publications

        // Specify relocation POM
        relocation(MavenPublication) {
            pom {
                // Old artifact coordinates
                groupId = "com.example"
                artifactId = "lib"
                version = "2.0.0"

                distributionManagement {
                    relocation {
                        // New artifact coordinates
                        groupId = "com.new-example"
                        artifactId = "lib"
                        version = "2.0.0"
                        message = "groupId has been changed"
                    }
                }
            }
        }
    }
}

只有在 relocation 下變更的屬性需要指定,也就是 artifactId 和 / 或 groupId。所有其他屬性都是選用的。

指定 version 在新的成品有不同版本時會很有用,例如因為版本編號已重新從 1.0.0 開始。

自訂 message 允許說明成品座標變更的原因。

搬移 POM 應為舊成品的下一版本而建立。例如,當成品座標 com.example:lib:1.0.0 變更,而具有新座標的成品繼續版本編號並發佈為 com.new-example:lib:2.0.0 時,搬移 POM 應指定從 com.example:lib:2.0.0 搬移到 com.new-example:lib:2.0.0

搬移 POM 只需發布一次,發布後應移除其建置檔案設定。

請注意,搬移 POM 並不適用於所有情況;當一個成品已分割為兩個或多個獨立成品時,搬移 POM 可能無濟於事。

追溯發布搬移資訊

在成品座標過去已變更,且當時未發布搬移資訊的情況下,可以追溯發布搬移資訊。

適用上述相同建議。為簡化使用者的遷移,務必注意搬移 POM 中指定的 版本。搬移 POM 應允許使用者一步驟移至新成品,然後再允許他們在另一個步驟更新至最新版本。例如,當 com.new-example:lib:5.0.0 的座標在版本 2.0.0 中變更時,理想情況下應為舊座標 com.example:lib:2.0.0 發布搬移 POM,並搬移至 com.new-example:lib:2.0.0。使用者接著可以從 com.example:lib 切換至 com.new-example,然後再從版本 2.0.0 分別更新至 5.0.0,並逐步處理重大變更(如有)。

當搬移資訊追溯發布時,不必等到專案的下次定期發布,可以在這段期間發布。如上所述,搬移 POM 發布後,應從建置檔案中再次移除搬移資訊。

避免重複依賴項

當成品的座標已變更,但成品內類別的套件名稱保持不變時,可能會發生依賴項衝突。專案可能會(傳遞性地)依賴舊成品,但同時也依賴新成品,而這兩個成品都包含相同的類別,且可能包含不相容的變更。

若要偵測此類衝突的重複依賴項,可以將 功能發布為 Gradle 模組元資料 的一部分。如需使用 Java 函式庫 專案的範例,請參閱 宣告本機元件的其他功能

執行預先執行

要在將搬移資訊發佈到遠端儲存庫之前驗證其是否如預期般運作,可以先將其發佈到本機 Maven 儲存庫。然後,可以建立一個本機測試 Gradle 或 Maven 專案,其中包含搬移成品作為相依性。

完整範例

下列範例示範如何簽署並發佈 Java 函式庫,包括來源、Javadoc 和自訂 POM

build.gradle.kts
plugins {
    `java-library`
    `maven-publish`
    signing
}

group = "com.example"
version = "1.0"

java {
    withJavadocJar()
    withSourcesJar()
}

publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            artifactId = "my-library"
            from(components["java"])
            versionMapping {
                usage("java-api") {
                    fromResolutionOf("runtimeClasspath")
                }
                usage("java-runtime") {
                    fromResolutionResult()
                }
            }
            pom {
                name = "My Library"
                description = "A concise description of my library"
                url = "http://www.example.com/library"
                properties = mapOf(
                    "myProp" to "value",
                    "prop.with.dots" to "anotherValue"
                )
                licenses {
                    license {
                        name = "The Apache License, Version 2.0"
                        url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
                    }
                }
                developers {
                    developer {
                        id = "johnd"
                        name = "John Doe"
                        email = "john.doe@example.com"
                    }
                }
                scm {
                    connection = "scm:git:git://example.com/my-library.git"
                    developerConnection = "scm:git:ssh://example.com/my-library.git"
                    url = "http://example.com/my-library/"
                }
            }
        }
    }
    repositories {
        maven {
            // change URLs to point to your repos, e.g. http://my.org/repo
            val releasesRepoUrl = uri(layout.buildDirectory.dir("repos/releases"))
            val snapshotsRepoUrl = uri(layout.buildDirectory.dir("repos/snapshots"))
            url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
        }
    }
}

signing {
    sign(publishing.publications["mavenJava"])
}

tasks.javadoc {
    if (JavaVersion.current().isJava9Compatible) {
        (options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
    }
}
build.gradle
plugins {
    id 'java-library'
    id 'maven-publish'
    id 'signing'
}

group = 'com.example'
version = '1.0'

java {
    withJavadocJar()
    withSourcesJar()
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            artifactId = 'my-library'
            from components.java
            versionMapping {
                usage('java-api') {
                    fromResolutionOf('runtimeClasspath')
                }
                usage('java-runtime') {
                    fromResolutionResult()
                }
            }
            pom {
                name = 'My Library'
                description = 'A concise description of my library'
                url = 'http://www.example.com/library'
                properties = [
                    myProp: "value",
                    "prop.with.dots": "anotherValue"
                ]
                licenses {
                    license {
                        name = 'The Apache License, Version 2.0'
                        url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                    }
                }
                developers {
                    developer {
                        id = 'johnd'
                        name = 'John Doe'
                        email = 'john.doe@example.com'
                    }
                }
                scm {
                    connection = 'scm:git:git://example.com/my-library.git'
                    developerConnection = 'scm:git:ssh://example.com/my-library.git'
                    url = 'http://example.com/my-library/'
                }
            }
        }
    }
    repositories {
        maven {
            // change URLs to point to your repos, e.g. http://my.org/repo
            def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
            def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
            url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
        }
    }
}

signing {
    sign publishing.publications.mavenJava
}


javadoc {
    if(JavaVersion.current().isJava9Compatible()) {
        options.addBooleanOption('html5', true)
    }
}

結果是會發佈下列成品

  • POM:my-library-1.0.pom

  • Java 元件的主要 JAR 成品:my-library-1.0.jar

  • 已明確設定的來源 JAR 成品:my-library-1.0-sources.jar

  • 已明確設定的 Javadoc JAR 成品:my-library-1.0-javadoc.jar

簽署外掛程式用於為每個成品產生簽章檔。此外,還會為所有成品和簽章檔產生檢查總和檔。

publishToMavenLocal` 沒有在 $USER_HOME/.m2/repository 中建立檢查總和檔。如果您想要驗證檢查總和檔是否正確建立,或將其用於後續發佈,請考慮設定一個自訂 Maven 儲存庫,其中包含 file:// URL,並將其用作發佈目標。

移除延遲設定行為

在 Gradle 5.0 之前,publishing {} 區塊(預設)會隱含地視為在專案評估後執行其內部所有邏輯。此行為造成相當多的混淆,並在 Gradle 4.8 中已棄用,因為它是唯一以這種方式運作的區塊。

您可能在發佈區塊內或在依賴延遲設定行為的外掛程式中有一些邏輯。例如,下列邏輯假設在設定 artifactId 時會評估子專案

build.gradle.kts
subprojects {
    publishing {
        publications {
            create<MavenPublication>("mavenJava") {
                from(components["java"])
                artifactId = tasks.jar.get().archiveBaseName.get()
            }
        }
    }
}
build.gradle
subprojects {
    publishing {
        publications {
            mavenJava(MavenPublication) {
                from components.java
                artifactId = jar.archiveBaseName
            }
        }
    }
}

此類邏輯現在必須包在 afterEvaluate {} 區塊中。

build.gradle.kts
subprojects {
    publishing {
        publications {
            create<MavenPublication>("mavenJava") {
                from(components["java"])
                afterEvaluate {
                    artifactId = tasks.jar.get().archiveBaseName.get()
                }
            }
        }
    }
}
build.gradle
subprojects {
    publishing {
        publications {
            mavenJava(MavenPublication) {
                from components.java
                afterEvaluate {
                    artifactId = jar.archiveBaseName
                }
            }
        }
    }
}