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 任务

将所有已定义的发布物(包括其元数据(POM 文件等))复制到本地 Maven 缓存。

发布物

此插件提供 发布物,类型为 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 发布插件为此目的提供了 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 = "https://apache.ac.cn/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 = 'https://apache.ac.cn/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。以下示例对以“SNAPSHOT”结尾的版本使用一个 URL,对其余版本使用不同的 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 本地仓库

为了与本地 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.0com.new-example:lib:2.0.0 的重定位。

重定位 POM 只需要发布一次,一旦发布,就应该再次从构建文件配置中删除它。

请注意,重定位 POM 并非适用于所有情况;当构件已拆分为两个或更多单独的构件时,重定位 POM 可能无济于事。

追溯发布重定位信息

在构件的坐标过去已更改,但当时未发布重定位信息之后,可以追溯发布重定位信息。

与上述相同的建议适用。为了简化用户的迁移,重要的是要注意重定位 POM 中指定的 version。重定位 POM 应允许用户一步移动到新的构件,然后允许他们分步更新到最新版本。例如,当 com.new-example:lib:5.0.0 的坐标在版本 2.0.0 中更改时,理想情况下,重定位 POM 应为旧坐标 com.example:lib:2.0.0 发布,重定位到 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

示例 9. 发布 Java 库
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 = "https://apache.ac.cn/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 = 'https://apache.ac.cn/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 中创建校验和文件。如果您想验证校验和文件是否正确创建,或将它们用于以后的发布,请考虑配置具有 file:// URL 的 自定义 Maven 仓库,并将其用作发布目标。

移除延迟配置行为

在 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
                }
            }
        }
    }
}