5月31日 22:22

Maven Release Plugin 如何安全完成版本发布?

Maven Release Plugin 用来把“准备版本、打标签、发布制品、推进下一个快照版本”这组动作自动化。它适合仍以 Maven 仓库发布 JAR、WAR 或内部 SDK 的团队,尤其是多模块项目,不适合把所有发布逻辑都塞进一个临时脚本。它的价值在于可追溯:哪个 Git tag 对应哪个 Maven version,哪个构建产物进入了 releases 仓库,都能对上。

发布前要先把 POM 配完整

Release Plugin 依赖 SCM 和 distributionManagement。没有 SCM,它不知道标签打到哪里;没有发布仓库,它只能改版本和打 tag,不能把制品 deploy 出去。

xml
<scm> <connection>scm:git:https://github.com/acme/demo.git</connection> <developerConnection>scm:git:git@github.com:acme/demo.git</developerConnection> <url>https://github.com/acme/demo</url> <tag>HEAD</tag> </scm> <distributionManagement> <repository> <id>company-releases</id> <url>https://repo.example.com/repository/maven-releases/</url> </repository> <snapshotRepository> <id>company-snapshots</id> <url>https://repo.example.com/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement>

插件配置可以从保守开始:

xml
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> <version>3.0.1</version> <configuration> <tagNameFormat>v@{project.version}</tagNameFormat> <autoVersionSubmodules>true</autoVersionSubmodules> <releaseProfiles>release</releaseProfiles> <goals>clean deploy</goals> </configuration> </plugin>

仓库认证不要写进 POM,应放到 ~/.m2/settings.xml 或 CI Secret 注入的 settings 文件里:

xml
<servers> <server> <id>company-releases</id> <username>${env.MAVEN_REPO_USER}</username> <password>${env.MAVEN_REPO_PASSWORD}</password> </server> </servers>

发布命令通常分两步。先 mvn -B release:prepare,它会检查工作区、确认没有 SNAPSHOT 依赖、把版本从 1.2.0-SNAPSHOT 改成 1.2.0、提交、打 tag,再改成下一个快照版本。再执行 mvn -B release:perform,它会检出 tag 并执行 deploy。第一次接入一定先跑 mvn -B release:prepare -DdryRun=true,确认版本号、tag 名称和提交内容没问题。

CI 示例可以这样写:

yaml
name: Maven Release on: workflow_dispatch: inputs: releaseVersion: required: true developmentVersion: required: true jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-java@v4 with: distribution: temurin java-version: '17' cache: maven - run: mvn -B release:prepare release:perform -DreleaseVersion=${{ inputs.releaseVersion }} -DdevelopmentVersion=${{ inputs.developmentVersion }}

发布流程还要考虑分支模型。主干开发团队通常在 main 上保持可发布状态,releaseVersion 由人工输入或从 tag 推导;Git Flow 团队可能从 release 分支执行 prepare,再把版本提交合回主干。两种方式都能用,但不要让插件一边改版本一边跨分支合并,这会让冲突难以理解。比较稳的做法是发布分支只做版本冻结和缺陷修复,功能变更不要混进来。

多模块项目更要提前决定版本策略。autoVersionSubmodules=true 会让所有子模块使用同一个版本,简单、清晰,也适合一起发布的服务组。如果子模块各自独立发布,Release Plugin 的交互式流程会变得很重,甚至不如使用 flatten-maven-plugin、versions-maven-plugin 或专门的发布脚本。边界在于模块之间是否必须同进同出,不能只因为“这是多模块项目”就默认全部一起发。

还有一个现实问题是发布说明。Release Plugin 不会自动替你判断哪些变更值得写进 changelog,它最多保证版本和 tag 可追溯。团队如果需要面向用户的发布说明,最好在 CI 中根据 PR 标题或 conventional commits 生成草稿,再由负责人确认。不要把“构建成功”等同于“发布准备充分”。

追问

release:prepare 和 release:perform 到底差在哪?

prepare 改的是源码仓库状态:检查、改版本、提交和打标签。perform 面向发布仓库:从标签检出干净代码,然后构建并 deploy。边界很清楚,prepare 成功不代表制品已经发布,perform 失败也不应该随手删 tag。常见坑是把两步放在本地执行,中途网络失败后工作区、tag 和远程仓库状态不一致,恢复成本很高。

为什么发布前不能有 SNAPSHOT 依赖?

正式版本需要可重复构建,而 SNAPSHOT 依赖随时可能变化。今天同一个 tag 构建出的包和明天不一致,发布审计就失去意义。取舍是内部快速迭代阶段可以用 SNAPSHOT,但进入 release 分支前必须替换为正式版本。踩坑点是传递依赖里藏着 SNAPSHOT,表面 POM 看不出来,需要 mvn dependency:tree | grep SNAPSHOT 检查。

Maven Release Plugin 适合所有项目发布吗?

不适合。它适合 Maven 制品发布和版本号强绑定的项目,比如 Java SDK、内部 starter、多模块服务。容器镜像发布、Helm Chart 发布或前后端混合发布,通常需要 CI 编排更多步骤,Release Plugin 只能负责 Maven 这部分。边界在于“最终交付物是不是 Maven artifact”,如果不是,就不要强行让它接管整个发布流程。

发布失败后应该 rollback 还是手动修?

如果失败发生在 prepare 阶段且还没推送远程,mvn release:rollback 通常够用。如果 tag 已推送或制品已 deploy,就要按团队发布规范处理,不能简单回滚本地文件。取舍点是保持历史真实还是追求清爽,正式发布场景通常宁可追加修复版本,也不要改写已公开的 tag。常见坑是删除远程 tag 后重新发布同版本,结果仓库里已有不可覆盖的 release artifact。

在 CI 中运行 Release Plugin 要注意什么?

CI 必须能推送 tag,也必须能访问 Maven 发布仓库,所以 Git 凭据和 Maven settings 都要提前准备。actions/checkout 要设置 fetch-depth: 0,否则插件可能拿不到完整 SCM 信息。为了减少误发布,建议只允许手动触发或受保护分支触发,并把 releaseVersion、developmentVersion 显式输入。坑在于使用默认 token 权限不足,prepare 到 push tag 时才失败,前面版本文件已经被改过。

小结

Maven Release Plugin 的重点不是命令多熟,而是发布边界清楚:POM 里声明 SCM 和仓库,settings 里放凭据,CI 中固定触发入口。先 dryRun,再 prepare,最后 perform;失败后先判断已经影响到本地、远程 tag 还是制品仓库,再决定回滚方式。

标签:Maven