一, 什么是版本管理
首先, 这里说的版本管理 (version management) 不是指版本控制 (version control), 但是本文假设你拥有基本的版本控制的知识, 了解 subversion 的基本用法. 版本管理中说得版本是指构件(artifact) 的版本, 而非源码的版本(如 subversion 中常见的 rXXX, 或者 Git 中一次提交都有个 sha1 的 commit 号).
比如我有一个项目, 其 artifactId 为 myapp, 随着项目的进展, 我们会生成这样一些 jar:myapp-1.0-SNAPSHOT.jar,myapp-1.0.jar,myapp-1.1-SNAPSHOT.jar,myapp-1.0.1.jar 等等. 你可能会说, 这很简单啊, 我在 POM 中改个 version,mvn clean install 不就完了? 但这只是表面, 本文我将讲述, snapshot 和 release 版本的区别, 如何自动化版本发布(如果你的项目有几十个 module, 你就会觉得手工改 POM 来升级版本是很痛苦的事情), 结合自动化发布的过程, 这里还会介绍 maven-release-plugin. 此外, 一些 scm 概念也会被涉及到, 比如 tag 和 branch.
二, 前提: 版本控制
不管怎样, 我们都需要建立一个项目并提交到 SCM 中, 这里我以 subversion 为例. 你得有一个配置好的 subversion repository, 这里我建立了一个空的 SVN 仓库, 其地址为: https://192.168.1.100:8443/svn/myapp/ 现在, 该目录下只有三个空的典型的子目录:/trunk/, branches/, tags/. 分别用来存放主干, 分支, 以及标签.
接着将项目导入到 SVN 仓库中, 到项目根目录, 运行如下命令:
- SVN import -m 'project initialization' https://192.168.1.100:8443/svn/myapp/trunk
- (注意, 这么做你会将目录下所有文件导入到 SVN 库中, 但是这其中某些目录和文件是不应该被导入的, 如 / target 目录, 以及 eclipse 相关的项目文件)
目前, 我们将项目的版本设置为 1.0-SNAPSHOT.
三, 为什么用 SNAPSHOT?
我先说说如果没有 SNAPSHOT 会是什么样子. 假设你的项目有 2 个模块, A,B, 其中 A 依赖 B. 这三个模块分别由甲, 乙两个个人负责开发. 在开发过程中, 因为 A 是依赖于 B 的, 因此乙每次做一个改动都会影响到甲, 于是, 乙提交了一些更改后, 需要让甲看到. 这个时候, 怎么做呢? 乙对甲说,"你签出我的代码, build 一下就 OK 了", 甲有点不情愿, 但还是照做了, 签出代码, SVN clean install, 然后, 发现 build 出错了, 有个测试没有 pass. 甲郁闷了, 对乙说,"你的代码根本不能用, 我不想 build, 你 build 好了给我", 乙看了看确实自己的代码 build 不过, 于是回去解决了, 然后打了个 jar 包, 扔给甲, 甲对了对 groupId,artifactId, 放到了自己的. m2/repository / 目录下, OK, 能用了.
于是乙每次更新都这样做, 打包, 复制, 然后甲粘贴, 使用...... 渐渐的, 大家发现这是个很笨的办法, 这是纯手工劳动阿, 程序员最 BS 的就是重复劳动. 一天, 甲对乙说,"你知道 nexus 么? 你把你的 jar 发布到 nexus 上就可以了, 我要用就自动去下载, 这多棒!" 乙说 "哦? 有这好东西, 我去看看" 于是乙发现了 nexus 这块新大陆, 并成功的发布了 B 到 nexus 上.(见, Nexus 入门指南,(图文) https://juvenshun.iteye.com/blog/349534 ).
但是, 请注意, 我们这里的一切都假设没有 SNAPSHOT, 因此如果乙不更改版本, 甲下载一次如 B-1.0.jar 之后, maven 认为它已经有了正确的 B 的版本, 就不会再重新下载. 甲发现了这个问题, 对乙说 "你的更新我看不到, 你更新了么?" 乙说 "不可能! 我看看", 于是检查一下甲下载的 C-1.0.jar, 发现那是几天前的. 乙一拍脑袋, 说 "这简单, 我更新一下我的版本就好了, 我发布个 B-1.1.jar 上去, 你更新下依赖版本", 甲照做了, 似乎这么做是可行的.
这里有一个问题, 一次提交就更新一个版本, 这明显不是正确的管理办法, 此外, 乙得不停的通知甲更新对 B 的依赖版本, 累不累阿? 1.0, 或者说 1.1,2.0, 都代表了稳定, 这样随随便便的改版本, 能稳定么?
所以 Maven 有 SNAPSHOT 版本的概念, 它与 release 版本对应, 后者是指 1.0,1.1,2.0 这样稳定的发布版本.
现在乙可以将 B 的版本设置成 1.0-SNAPSHOT, 每次更改后, 都 mvn deploy 到 nexus 中, 每次 deploy,maven 都会将 SNAPSHOT 改成一个当前时间的 timestamp, 比如 B-1.0-SNAPSHOT.jar 到 nexus 中后, 会成为这个样子: B-1.0-20081017-020325-13.jar.Maven 在处理 A 中对于 B 的 SNAPSHOT 依赖时, 会根据这样的 timestamp 下载最新的 jar, 默认 Maven 每天 更新一次, 如果你想让 Maven 强制更新, 可以使用 - U 参数, 如: mvn clean install -U .
现在事情简化成了这个样子: 乙做更改, 然后 mvn deploy, 甲用最简单的 maven 命令就能得到最新的 B.
四, 从 1.0-SNAPSHOT 到 1.0 到 1.1-SNAPSHOT
SNAPSHOT 是快照的意思, 项目到一个阶段后, 就需要发布一个正式的版本(release 版本). 一次正式的发布需要这样一些工作:
在 trunk 中, 更新 pom 版本从 1.0-SNAPSHOT 到 1.0
对 1.0 打一个 SVN tag
针对 tag 进行 mvn deploy, 发布正式版本
更新 trunk 从 1.0 到 1.1-SNAPSHOT
你可以手工一步步的做这些事情, 无非就是一些 SVN 操作, 一些 pom 编辑, 还有一些 mvn 操作. 但是你应该明白, 手工做这些事情, 一来繁琐, 而来容易出错. 因此这里我介绍使用 maven 插件来自动化这一系列动作.
1. SCM
首先我们需要在 POM 中加入 scm 信息, 这样 Maven 才能够替你完成 SVN 操作, 这里我的配置如下:
- <scm>
- <connection>scm:SVN:http://192.168.1.100:8443/svn/myapp/trunk/</connection>
- <developerConnection>scm:SVN:https://192.168.1.100:8443/svn/myapp/trunk/</developerConnection>
- </scm>
需要注意的是, 很多 Windows 使用的 tortoiseSVN 客户端, 而没有 SVN 命令行客户端, 这会导致 Maven 所有 SVN 相关的工作失败, 因此, 你首先确保 SVN --version 能够运行.
注意:
如果你的项目中 pom 分多个模块, 就需要使用命令: release:prepare-with-pom 进行打包.
本地打包只需要运行一个命令: mvn release:prepare-with-pom, 该命令会执行操作并且将新打包的代码提交到 SVN.(参考 4. 开始工作)
在打包之前, 首先要配置依赖如下:
parent 父级模块的依赖(parent 项目的 pom.xml):
- <!-- scm 配置, 具体路径为待打包代码分支的根路径(trunk,branckes/v1.1.x,/tags/v1.1.5 等) -->
- <scm>
- <developerConnection>scm:SVN:SVN://SVN 主路径地址 / trunk/</developerConnection>
- </scm>
- <!-- maven-release-plugin 插件配置 -->
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-release-plugin</artifactId>
- <version>2.5.3</version>
- <configuration>
- <autoVersionSubmodules>true</autoVersionSubmodules>
- <tagNameFormat>v@{project.version}</tagNameFormat>
- <generateReleasePoms>false</generateReleasePoms>
- <arguments>-DskipTests</arguments>
- </configuration>
- </plugin>
- </plugins>
- </build>
console,service 等子模块的配置
- <!-- maven-release-plugin 插件配置 -->
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-release-plugin</artifactId>
- <version>2.5.3</version>
- </plugin>
- </plugins>
- </build>
2. 分发仓库
想要让 Maven 帮我们自动发布, 首先我们需要配置好分发仓库. 关于这一点, 见 Maven 最佳实践: Maven 仓库 https://juvenshun.iteye.com/blog/359256 -- 分发构件至远程仓库.
maven-release-plugin
紧接着, 我们需要配置 maven-release-plugin, 这个插件会帮助我们升级 pom 版本, 提交, 打 tag, 然后再升级版本, 再提交, 等等. 基本配置如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> <version>2.5.3</version> <configuration> <tagBase>https://192.168.1.100:8443/svn/myapp/tags/</tagBase> </configuration> </plugin>
GAV 我就不多解释了, 这里我们需要注意的是 configuration 元素下的 tagBase 元素, 它代表了我们 SVN 中的 tag 目录, 也就是说, maven-release-plugin 帮我们打 tag 的时候, 其基础目录是什么. 这里, 我填写了 SVN 仓库中的标准的 tags 目录.
3. 提交代码
接着, 确保你的所有代码都提交了, 如果你有未提交代码, release 插件会报错, 既然你要发布版本了, 就表示代码是稳定的, 所以要么要么把代码提交了, 要么把本地的更改抛弃了.
4. 开始工作
现在, 屏住呼吸, 执行:
mvn release:prepare
执行过程中, 你会遇到这样的提示:
What is the release version for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) 1.0: :
--"你想将 1.0-SNAPSHOT 发布为什么版本? 默认是 1.0." 我要的就是 1.0, 直接回车.
What is SCM release tag or label for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) myapp-1.0: :
--"发布的 tag 标签名称是什么? 默认为 myapp-1.0." 我还是要默认值, 直接回车.
What is the new development version for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) 1.1-SNAPSHOT: :
--"主干上新的版本是什么? 默认为 1.1-SNAPSHOT." 哈, release 插件会自动帮我更新版本到 1.1-SNAPSHOT, 很好, 直接回车.
然后屏幕刷阿刷, maven 在 build 我们的项目, 并进行了一些 SVN 操作, 你可以仔细查看下日志.
那么结果是什么呢? 你可以浏览下 SVN 仓库:
我们多了一个 tag:https://192.168.1.100:8443/svn/myapp/tags/myapp-1.0/, 这就是需要发布的版本 1.0.
再看看 trunk 中的 POM, 其版本自动升级成了 1.1-SNAPSHOT.
这不正是我们想要的么? 等等, 好像缺了点什么, 对了, 1.0 还没有发布到仓库中呢.
再一次屏住呼吸, 执行:
mvn release:perform
maven-release-plugin 会自动帮我们签出刚才打的 tag, 然后打包, 分发到远程 Maven 仓库中, 至此, 整个版本的升级, 打标签, 发布等工作全部完成. 我们可以在远程 Maven 仓库中看到正式发布的 1.0 版本.
这可是自动化的 , 正式的 版本发布!
五, Maven 的版本规则
前面我们提到了 SNAPSHOT 和 Release 版本的区别, 现在看一下, 为什么要有 1.0,1.1,1.1.1 这样的版本, 这里的规则是什么.
Maven 主要是这样定义版本规则的:
<主版本>.<次版本>.<增量版本>
比如说 1.2.3, 主版本是 1, 次版本是 2, 增量版本是 3.
主版本一般来说代表了项目的重大的架构变更, 比如说 Maven 1 和 Maven 2, 在架构上已经两样了, 将来的 Maven 3 和 Maven 2 也会有很大的变化. 次版本一般代表了一些功能的增加或变化, 但没有架构的变化, 比如说 Nexus 1.3 较之于 Nexus 1.2 来说, 增加了一系列新的或者改进的功能(仓库镜像支持, 改进的仓库管理界面等等), 但从大的架构上来说, 1.3 和 1.2 没什么区别. 至于增量版本, 一般是一些小的 bug fix, 不会有重大的功能变化.
一般来说, 在我们发布一次重要的版本之后, 随之会开发新的版本, 比如说, myapp-1.1 发布之后, 就着手开发 myapp-1.2 了. 由于 myapp-1.2 有新的主要功能的添加和变化, 在发布测试前, 它会变得不稳定, 而 myapp-1.1 是一个比较稳定的版本, 现在的问题是, 我们在 myapp-1.1 中发现了一些 bug(当然在 1.2 中也存在), 为了能够在段时间内修复 bug 并仍然发布稳定的版本, 我们就会用到分支 (branch), 我们基于 1.1 开启一个分支 1.1.1, 在这个分支中修复 bug, 并快速发布. 这既保证了版本的稳定, 也能够使 bug 得到快速修复, 也不同停止 1.2 的开发. 只是, 每次修复分支 1.1.1 中的 bug 后, 需要 merge 代码到 1.2(主干) 中.
上面讲的就是我们为什么要用增量版本.
六, 实战分支
目前我们 trunk 的版本是 1.1-SNAPSHOT, 其实按照前面解释的版本规则, 应该是 1.1.0-SNAPSHOT.
现在我们想要发布 1.1.0, 然后将主干升级为 1.2.0-SNAPSHOT, 同时开启一个 1.1.x 的分支, 用来修复 1.1.0 中的 bug.
首先, 在发布 1.1.0 之前, 我们创建 1.1.x 分支, 运行如下命令:
mvn release:branch -DbranchName=1.1.x -DupdateBranchVersions=true -DupdateWorkingCopyVersions=false
这是 maven-release-plugin 的 branch 目标, 我们指定 branch 的名称为 1.1.x, 表示这里会有版本 1.1.1, 1.1.2 等等. updateBranchVersions=true 的意思是在分支中更新版本, 而 updateWorkingCopyVersions=false 是指不更改当前工作目录 (这里是 trunk) 的版本.
在运行该命令后, 我们会遇到这样的提示:
What is the branch version for "Unnamed - org.myorg:myapp:jar:1.1-SNAPSHOT"? (org.myorg:myapp) 1.1-SNAPSHOT: :
--"分支中的版本号是多少? 默认为 1.1-SNAPSHOT" 这时我们想要的版本是 1.1.1-SNAPSHOT, 因此输入 1.1.1-SNAPSHOT, 回车, maven 继续执行直至结束.
接着, 我们浏览 SVN 仓库, 会看到这样的目录: https://192.168.1.100:8443/svn/myapp/branches/1.1.x/, 打开其中的 POM 文件, 其版本已经是 1.1.1-SNAPSHOT.
分支创建好了, 就可以使用 release:prepare 和 release:perform 为 1.1.0 打标签, 升级 trunk 至 1.2.0-SNAPSHOT, 然后分发 1.1.0.
至此, 一切 OK.
七, 小结
本文讲述了如何使用 Maven 结合 SVN 进行版本管理. 解释了 Maven 中 SNAPSHOT 版本的来由, 以及 Maven 管理版本的规则. 并结合 SCM 的 tag 和 branch 概念展示了如何使用 maven-release-plugin 发布版本, 以及创建分支. 本文涉及的内容比较多, 且略显复杂, 不过掌握版本管理的技巧对于项目的正规化管理来说十分重要. Maven 为我们提供了一些一套比较成熟的机制, 值得掌握.
参考: https://juvenshun.iteye.com/blog/376422
来源: https://www.cnblogs.com/huxiuqian/p/10281007.html