在持续交付中,自动化项目版本控制的Maven方式是什么?

76

我有一个web应用程序,我们会在每次功能准备好时部署到生产环境,有时一天可能会进行几次,有时发布之间可能会有几周时间。

目前,我们的项目没有增加版本号,一切都已经停留在版本号 0.0.1-SNAPSHOT超过一年了。我想知道Maven在web应用程序中进行持续交付的方式是什么。对于每个提交都增加版本号似乎有些过度,而像我们现在做的那样从不增加版本号也似乎是错误的。

这种Maven使用的推荐最佳实践是什么?

实际上,问题分为两个方面:

  • 在各自的pom.xml文件中提升项目版本号(可能会有很多)。
  • 更新所有依赖组件的版本号以使用各自的最新版本。
7个回答

57

我将此标记为正确答案,因为YouTube视频在准确回答这个问题方面非常出色。然而,我不得不暂停和倒带几次才能理解一些细节。我已经在我的答案中总结了视频中推荐的过程。 - ams
2
这个视频非常好,会对你有很大帮助。在视频的23分35秒处,我会强调Maven应该负责大部分短期运行的健全性验证,例如单元测试、覆盖率检查、集成测试等等...然而,持续交付并不止于此。对于较大的SOA型组织,将您的流水线分解为离散阶段实际上是非常好的实践,例如使用模拟端点进行功能测试、性能测试、大规模集成测试、UAT测试、合规性验证、静态代码分析、安全审查等等。 - KRK Owner
很棒的视频。我唯一的担忧是人们如何处理发布仓库中所有未正式发布的“发布候选版”?我们为我们的SNAPSHOT设置了清理计划,但发布工件将永远保留。我看到这在Maven存储库方面使用了大量磁盘空间。 - Patrick
1
@Patrick 发布候选版本并不等同于发布,除非经过批准。我有一个清理任务,会在它们超过两个月的时候删除它们。如果那时它们还没有被发布,那它们永远也不会被发布了 :-) - Mark O'Connor
如果您尚未建立CI/CD流程,您将如何升级版本?谢谢。 - pixel
显示剩余6条评论

46

根据Mark O'Connor的回答中链接的视频,该解决方案需要像git这样的DVCS和像Jenkins这样的CI服务器。

  • 在持续交付流程中不要使用快照版本构建,并且不要使用maven发布插件。
  • 诸如1.0-SNAPSHOT之类的快照版本将变成真实版本,例如1.0.buildNumber,其中buildNumber是Jenkins作业编号。

算法步骤:

  1. Jenkins克隆具有源代码的git存储库,并说源代码的版本为1.0-SNAPSHOT
  2. Jenkins创建名为1.0.JENKINS-JOB-NUMBER的git分支,以便将快照版本转换为真实版本1.0.124
  3. Jenkins调用maven版本插件,将pom.xml文件中的版本号从1.0-SNAPSHOT更改为1.0.JENKINS-JOB-NUMBER
  4. Jenkins调用mvn install
  5. 如果 mvn install 成功,则Jenkins将提交分支 1.0.JENKINS-JOB-NUMBER ,并创建一个带有适当标记的真实非快照版本以便以后复现。 如果 mvn install 失败,则Jenkins将仅删除新创建的分支并使构建失败。

我强烈建议观看Mark回答中链接的视频。


3
如何处理多模块Maven项目?您只需发布整个项目及其所有子模块的相同版本吗?如果我有一个依赖于B项目的A项目,并对B项目进行了提交,那么B项目将触发上述步骤,进而触发A项目的上述步骤,但是A项目如何知道B项目的版本号? - dukethrash
2
如果这是一个多模块项目,那么经验告诉我,使用单一版本是有意义的 - 这与版本插件保持一致。如果某些模块不经常更改,它们也可以作为单独的组件从构件存储库中拉取 - 例如,log4j、sl4fj... - KRK Owner
5
在我的情况下,这甚至不是一个多模块项目。我有A项目依赖于B项目。B项目有自己的Jenkins工作,并且可能会有生成发布版本的提交。然后A项目需要在pom文件中引用该发布版本的构建产物。您是否建议在B项目构建成功并且构建产物可用时手动更新A项目?B项目是一个核心项目,而A项目是一个Web应用程序,因此它们是耦合的,但是在使用以SNAPSHOT依赖项查看更改的IDE中,更新A项目对B项目的依赖关系需要很大的开销。 - dukethrash

28

从Maven 3.2.1开始,内置支持连续交付友好版本:https://issues.apache.org/jira/browse/MNG-5576 您可以在版本中使用3个预定义变量:

${changelist}
${revision}
${sha1}

所以你基本上要做的是:

  1. 将版本设置为例如 1.0.0-${revision}。(你可以使用 mvn versions:set 在多模块项目中快速且正确地执行此操作。)
  2. 为本地开发设置属性 <revision>SNAPSHOT</revision>
  3. 在 CI 环境中运行 mvn clean install -Drevision=${BUILD_NUMBER} 或类似的命令,甚至可以是 mvn clean verify -Drevision=${BUILD_NUMBER}

你可以使用例如https://wiki.jenkins-ci.org/display/JENKINS/Version+Number+Plugin来生成有趣的构建编号。

一旦你发现构建稳定(例如通过验收测试),你就可以将版本推送到 Nexus 或其他仓库。任何不稳定的构建都将被删除。


这个要处理起来容易多了。没有版本更改+提交来使其工作...我只需将我的设置为<version>1.0${revision}</version>,并使用属性<revision>-SNAPSHOT</revision>,然后在CI构建期间使用mvn clean deploy -Drevision=".$TRAVIS_BUILD_ID"。它会产生_补丁_版本,这些版本是_不寻常的_,但始终在增加,因此它们符合semver... - Lucas

10

有一些关于如何处理Maven版本号和持续交付(CD)的好的讨论和建议(我会在我的回答后添加它们)。

首先,我对SNAPSHOT版本的看法。在Maven中,SNAPSHOT表示该版本目前正在开发中,是特定版本的SNAPSHOT后缀之前的版本。因此,像Nexus或maven-release-plugin这样的工具对SNAPSHOT有特殊处理。对于Nexus,它们存储在单独的存储库中,并且允许使用相同的SNAPSHOT发布版本更新多个构件。因此,在项目中不建议使用SNAPSHOT依赖项,尤其是在CD世界中,因为构建不再可靠。

SNAPSHOT作为项目版本将成为问题,当其他项目使用您的项目时,就会出现上述问题。

对我来说,SNAPSHOT的另一个问题是它不太可追踪或可重现。当我在生产环境中看到版本0.0.1-SNAPSHOT时,我需要进行一些搜索,以找出它是从哪个修订版本构建的。当我在文件系统上找到这个软件的版本时,我需要查看pom.properties或MANIFEST文件,以确定这是否为旧垃圾还是最新版本。

为了避免手动更改版本号(特别是当您每天构建多个构建时),让构建服务器为您更改编号。因此,对于开发,我会选择一个

<major>.<minor>-SNAPSHOT

版本号通常以SNAPSHOT结尾,但在构建新版本时,构建服务器可以将SNAPSHOT替换为更独特和可追踪的内容。

例如:

<major>.<minor>-b<buildNumber>
<major>.<minor>-r<scmNumber>

因此,主版本号和次版本号可用于营销问题或仅显示达到了新的重要里程碑,并且可以在任何时候手动更改。而buildNumber(来自您的持续集成服务器的数字)或scmNumber(SUbversion或GIT的修订版)使每个发布都是独特且可追踪的。当使用buildNumber或Subversion修订版时,项目版本甚至是可排序的(不适用于GIT编号)。使用buildNumber或scmNumber也很容易看出此版本中有哪些更改。
另一个例子是stackoverflow的版本控制。
<year>.<month>.<day>.<buildNumber>

以下是缺失的链接:


这些链接涉及IT技术,需要注意的是保留HTML标记,同时让内容更加通俗易懂。

1
管道中的版本控制第一个链接已经损坏。 - Aravind Yarram
似乎已被删除。我没有找到替代链接或者在网页缓存中的内容 :(. - mszalbach
这个答案更多地涉及版本名称格式及其含义。已经有足够好的正式规范 称为“语义化版本” - 只需添加 SNAPSHOT 表示“尚未发布”的版本。而版本的格式在 Maven 中并不是真正的问题,而且还有一些限制。问题在于使这个过程变得轻松,并最好是自动的(至少在使用类似 X.Y.Z 的手动语义版本进行发布之前)。更新多个相互依赖的 pom.xml 文件的部分问题所在。 - uvsmtid

7

不要这样做!

<Major>.<minor>-<build>

会咬你一口的是Maven会将连字符后面的任何内容视为词汇上的,这意味着版本1在词法上比版本10高。

如果你正在向Maven请求最新版本的某个东西,那么上述问题就会出现。

解决方法是在构建号之前使用小数点代替连字符。

请务必这样做!

<Major>.<minor>.<build>

在本地使用SNAPSHOT版本是可以的,但在构建过程中最好使用正式版本。

mvn versions:set -DnewVersion=${major}.${minor}.${build.number}

有一些方法可以从pom中获取主/次版本号,例如使用help:evaluate并将其导出到环境变量中,然后再调用versions:set。这种方法有点不规范,但是我(以及我的团队成员)曾经为了简化此过程而苦思冥想,当时Maven还不够成熟来处理这个问题。我相信Maven 2.3.1可能已经在某种程度上解决了这个问题,因此这些信息可能已经不再相关。
对于一群开发人员来说,在同一个主要.次要版本上发布是可以的,但是需要注意,次要更改不会破坏API,而主要版本更改则会有一些破坏性的API更改或功能/行为的弃用。
从持续交付的角度来看,每个构建都有可能被发布,因此每次提交都应该创建一个构建。

7
根据http://mojo.codehaus.org/versions-maven-plugin/version-rules.html的描述以及我的测试,这是不正确的。Maven的默认版本控制方案是“<major>.<minor>.<incremental>-<buildNumber|qualifier>”(大部分可选)。只要破折号后面的部分是数字,它就不会被按字典序进行比较。我使用“mvn versions:use-latest-versions”和例如“1.4-11”正确地被检测为新于“1.4-2”。 - Philipp Paland
你测试这个使用的是哪个版本的Maven? 另外,你是否尝试安装1.4.21版本,并进行测试以了解Maven版本范围解析的工作原理?如果你有发现,那将会很有趣,因为我在2.2.1版本中遇到了这个问题,并通过查看Maven源代码进行了验证。此外,我认为这些规则实际上只适用于版本插件,而我所说的是依赖关系解析。 - KRK Owner
请参考 https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm#MAVEN401。 @philipp是正确的。在${major}.${minor}.${build.number}和${major}.${minor}.${build.number}-${patchNumber}中使用递增编号是安全的,但在${major}.${minor}.${build.number}.${patchNumber}中不安全。 - iMysak

2

在我们的网页应用程序中,我们目前使用以下版本控制模式:

<jenkins build num>-<git-short-hash>

例如:247-262e37b9。

这种模式的好处在于它可以给你一个始终独一无二并且可追溯到产生它的Jenkins构建和Git修订的版本。

在 Maven 3.2.1+ 中,他们终于杀死了使用 ${property} 作为版本的警告,因此这使得构建变得非常容易。只需要将所有的 poms 更改为使用 <version>${revision}</version> 并使用 -Drevision=whatever 进行构建即可。唯一的问题是,在您发布的 poms 中,实际的 pom 文件中版本仍将保持为 ${revision},这可能会导致各种奇怪的问题。为解决此问题,我编写了一个简单的 Maven 插件(https://github.com/jeffskj/cd-versions-maven-plugin),该插件在文件中进行变量替换。


该插件似乎是更新pom.xml文件版本本身的解决方案。然而,下一个问题是更新所有参与发布的依赖组件中的版本,以便它们实际上使用彼此的最新版本。在多模块/反应堆构建中(更新外部和不仅限于内部组件),我遇到了**自动更新不可靠的问题。因此,短期解决方案是保持SHAPSHOT您对如何解决这个问题有什么评论吗?** - uvsmtid
你基本上不应该使用快照版本。在构建之前更新依赖的版本。 - Jeff S
保持所有模块版本相同。在根模块的dependencyManagement部分中将模块设置为${project.version}。然后简单地运行mvn versions:set -DnewVersion=whatever就可以改变反应堆顺序中包括父级在内的版本。 - Piotr Gwiazda

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接