Maven Publish 插件提供了将构建工件发布到 Apache Maven 仓库的能力。发布到 Maven 仓库的模块可以被 Maven、Gradle(参见声明依赖项)以及其他理解 Maven 仓库格式的工具使用。您可以在发布概述中了解发布的基础知识。
用法
要使用 Maven Publish 插件,请在您的构建脚本中包含以下内容
plugins {
`maven-publish`
}
plugins {
id 'maven-publish'
}
Maven Publish 插件使用项目上名为 publishing
的扩展,类型为 PublishingExtension。此扩展提供了命名出版物的容器和命名仓库的容器。Maven Publish 插件与 MavenPublication 出版物和 MavenArtifactRepository 仓库一起工作。
任务
generatePomFileForPubNamePublication
— GenerateMavenPom-
为名为 PubName 的出版物创建一个 POM 文件,填充已知的元数据,例如项目名称、项目版本和依赖项。POM 文件的默认位置是 build/publications/$pubName/pom-default.xml。
publishPubNamePublicationToRepoNameRepository
— PublishToMavenRepository-
将 PubName 出版物发布到名为 RepoName 的仓库。如果您有一个没有显式名称的仓库定义,RepoName 将是“Maven”。
publishPubNamePublicationToMavenLocal
— PublishToMavenLocal-
将 PubName 出版物以及出版物的 POM 文件和其他元数据复制到本地 Maven 缓存 — 通常是 <当前用户的主目录>/.m2/repository。
publish
-
依赖于:所有
publishPubNamePublicationToRepoNameRepository
任务一个聚合任务,将所有定义的出版物发布到所有定义的仓库。它不包括将出版物复制到本地 Maven 缓存。
publishToMavenLocal
-
依赖于:所有
publishPubNamePublicationToMavenLocal
任务将所有定义的出版物复制到本地 Maven 缓存,包括它们的元数据(POM 文件等)。
出版物
此插件提供类型为 MavenPublication 的出版物。要了解如何定义和使用出版物,请参阅基本发布部分。
您可以在 Maven 出版物中配置四个主要内容
-
一个组件 — 通过 MavenPublication.from(org.gradle.api.component.SoftwareComponent)。
-
自定义工件 — 通过 MavenPublication.artifact(java.lang.Object) 方法。有关自定义 Maven 工件的可用配置选项,请参见 MavenArtifact。
-
标准元数据,如
artifactId
、groupId
和version
。 -
POM 文件的其他内容 — 通过 MavenPublication.pom(org.gradle.api.Action)。
您可以在完整的发布示例中看到所有这些操作。MavenPublication
的 API 文档包含其他代码示例。
生成 POM 中的标识值
生成的 POM 文件的属性将包含从以下项目属性派生的标识值
-
groupId
- Project.getGroup() -
artifactId
- Project.getName() -
version
- Project.getVersion()
覆盖默认标识值很简单:只需在配置 MavenPublication 时指定 groupId
、artifactId
或 version
属性即可。
publishing {
publications {
create<MavenPublication>("maven") {
groupId = "org.gradle.sample"
artifactId = "library"
version = "1.1"
from(components["java"])
}
}
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'org.gradle.sample'
artifactId = 'library'
version = '1.1'
from components.java
}
}
}
某些仓库无法处理所有支持的字符。例如,在 Windows 上发布到文件系统支持的仓库时,不能使用 : 字符作为标识符。 |
Maven 将 groupId
和 artifactId
限制为有限的字符集 ([A-Za-z0-9_\\-.]+
),Gradle 强制执行此限制。对于 version
(以及工件 extension
和 classifier
属性),Gradle 将处理任何有效的 Unicode 字符。
唯一明确禁止的 Unicode 值是 \
、/
和任何 ISO 控制字符。提供的值在发布早期进行验证。
自定义生成的 POM
生成的 POM 文件可以在发布之前进行自定义。例如,将库发布到 Maven Central 时,您需要设置某些元数据。Maven Publish 插件为此目的提供了一个 DSL。有关可用属性和方法的完整文档,请参见 DSL 参考中的 MavenPom。以下示例展示了如何使用最常见的属性和方法
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/"
}
}
}
}
}
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
publishing {
publications {
create<MavenPublication>("mavenJava") {
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
}
}
}
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 应使用变体的默认类路径,而 runtimeClasspath
是 java-runtime
的默认类路径。
仓库
此插件提供类型为 MavenArtifactRepository 的仓库。要了解如何定义和使用仓库进行发布,请参阅基本发布部分。
以下是定义发布仓库的简单示例
publishing {
repositories {
maven {
// change to point to your repo, e.g. http://my.org/repo
url = uri(layout.buildDirectory.dir("repo"))
}
}
}
publishing {
repositories {
maven {
// change to point to your repo, e.g. http://my.org/repo
url = layout.buildDirectory.dir('repo')
}
}
}
您将要配置的两个主要内容是仓库的
-
URL(必需)
-
名称(可选)
您可以定义多个仓库,只要它们在构建脚本中具有唯一的名称。您也可以声明一个(且只有一个)没有名称的仓库。该仓库将采用“Maven”的隐式名称。
您还可以配置连接到仓库所需的任何身份验证详细信息。有关更多详细信息,请参见 MavenArtifactRepository。
支持的仓库
Gradle 的 maven-publish
插件与 Maven Deploy 插件兼容,并支持发布到符合相同标准和协议的任何仓库。这包括各种 Maven 兼容的仓库,例如
-
本地仓库(例如
~/.m2/repository
) -
自定义远程 Maven 仓库
-
仓库管理器,如 Sonatype Nexus 和 JFrog Artifactory
-
GitHub Packages
-
内部公司 Maven 仓库
发布到 Maven Central
通常称为 Maven Central 并在 central.sonatype.com 托管的服务是分发 Java 和 Kotlin 库的广泛使用的仓库。
截至 2025 年 6 月 30 日,Maven Central 不再支持 Maven Deploy 插件使用的传统部署协议。此更改也标志着 OSSRH (OSS Repository Hosting) 服务的弃用。
未来,发布到 Maven Central 需要专用插件。有多种选择。有关指导和建议,请参见 Gradle Cookbook 中发布到 Maven Central 页面。
快照和发布仓库
通常的做法是将快照和发布版本发布到不同的 Maven 仓库。实现此目的的简单方法是根据项目版本配置仓库 URL。以下示例对以“SNAPSHOT”结尾的版本使用一个 URL,对其余版本使用另一个 URL
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)
}
}
}
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
时
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)
}
}
}
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 Publish 插件通过自动为 publishing.publications
容器中的每个 MavenPublication 创建一个 PublishToMavenLocal 任务,使这变得容易。任务名称遵循 publishPubNamePublicationToMavenLocal
的模式。这些任务中的每一个都连接到 publishToMavenLocal
聚合任务。您不需要在 publishing.repositories
部分中包含 mavenLocal()
。
发布 Maven 重新定位信息
当项目更改其发布的工件的 groupId
或 artifactId
(即*坐标*)时,告知用户新工件可以在哪里找到非常重要。Maven 可以通过*重新定位*功能帮助解决此问题。其工作方式是,项目在旧坐标下发布一个额外的工件,该工件仅包含一个最小的*重新定位 POM*;该 POM 文件指定新工件可以在哪里找到。然后,Maven 仓库浏览器和构建工具可以通知用户工件的坐标已更改。
为此,项目添加了一个额外的 MavenPublication
,指定一个 MavenPomRelocation
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"
}
}
}
}
}
}
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
。所有其他属性都是可选的。
当新工件具有不同版本时,指定 自定义 |
重新定位 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 中指定的 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
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)
}
}
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 时将评估子项目
subprojects {
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
artifactId = tasks.jar.get().archiveBaseName.get()
}
}
}
}
subprojects {
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifactId = jar.archiveBaseName
}
}
}
}
这种逻辑现在必须封装在 afterEvaluate {}
块中。
subprojects {
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
afterEvaluate {
artifactId = tasks.jar.get().archiveBaseName.get()
}
}
}
}
}
subprojects {
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
afterEvaluate {
artifactId = jar.archiveBaseName
}
}
}
}
}