Archive

Posts Tagged ‘持续集成’

Travis CI,翩翩而至的CI云

March 6th, 2012 8 comments

在开源社区的持续集成领域,当Jenkins/Hudson一统天下的时候,天边突然有个家伙踩着云朵飞了过来,风格非常小清新,而且表现得和最近大红大紫的Github非常亲昵。Ruby on Rails、Ruby和Node.js等项目已经被他吸引,或者说大部分有身份的Ruby开源项目都成了他的粉丝,事实上投靠他的项目已经超过7,500个,他每天执行超过4,000次的构建,他支持Ruby、Java、Python、Perl……好吧,其实是除了C/C++/C#之外的所有主流语言。

他是Travis CI,为开源社区提供的云端持续集成服务。

 

 

DSL方式的配置

我还记得07年接触CruiseControl的时候,所有配置都得通过后台的XML文件完成,修改完了再重启服务,如此反复,久而久之,烦不胜烦。然后08年接触到Hudson,修改配置有比较友好的Web界面了,自然一见倾心。现在来看,travis-ci似乎回到了CruiseControl的原始时期?其实不是这样,下面是一个典型的travis-ci的构建配置:

language: java
script: mvn -q clean install
# whitelist
branches:
  only:
    - master
notifications:
  email:
    - cukes-devs@googlegroups.com
  irc:
    - “irc.freenode.org#cucumber”

首先,和XML相比,非常小清新,现在好像大家都喜欢这么搞,比如Gradle较之于Maven,这么简洁性感的配置,无疑是非常有吸引力的。其次,这里的配置和CI服务其实是没有关系的,因此对于项目开发人员来说,我只关心怎么构建我的项目,而不用关心CI服务器如何配置。

这个DSL模型是非常简单的,它主要只包含了四个部分:

  1. 语言,比如java还是ruby,进一步的,还能指定具体版本(可以是多个),比如rvm 1.9.3。
  2. 生命周期,关于安装依赖的 before_install、install、after_install,关于具体构建的 before_script、script、after_script。
  3. 构建脚本,针对具体生命周期的一步,调用用户定义的脚本执行构建,例如上例中的 script: mvn -q clean install
  4. 通知,告诉用户CI成功或者失败,支持email和irc等等。

值得一提的是,Travis CI提供了工具travis-lint帮助你验证DSL的语法。

另外,由于构建配置是存储在版本控制仓库中的文本,因此天生就继承了版本控制带来的各种好处,例如可以回溯历史、不怕丢失、一有更改大家都能看到。

与Github集成

Travis CI只支持Github,但是集成得非常平滑,你可以直接用Github账号登陆,看到自己的Git仓库,然后点个按钮开启集成并授权Travis CI访问你的仓库即可,之后默认每次的push都会触发集成。

Travis ci github

换句话说,默认Travis CI不支持你自己的Git仓库,更不用说Subversion什么的了。但是,Travis CI是开源的,因此技术上来说支持其他形式的SCM不是什么困难的事情。

企业级CI

InfoQ的这篇报道我们可以看到,Travis CI已经试图将这一成功的开源项目在企业层面复制,名字也想好了:Travis Pro。

在我看来,这是Travis CI大有可为的一个领域,首先,云CI可以解放开发人员的生产力,正如我们在前面看到的,开发人员一方面不需要考虑CI背后的基础设置,另一方面又能够很轻松地对自己的构建任务实现可控。传统的做法,一种是开发人员自己维护CI服务器,包括服务器本身和集成任务的配置;一种是交给配置管理员,那开发人员想要修改集成任务配置的时候,就需要和配置管理员协调。

其次,这种将CI作为服务按需提供给开发人员的方式,可以节省企业的成本,从这篇关于Travis CI环境的文档我们可以看到,Travis CI在执行每次构建之前才开启虚拟机,执行完之后立刻关闭,这可以让硬件资源得到充分的利用。

当然,真正把Travis CI部署到企业环境中还是有许多工作要做的,这需要有人熟悉它的工作原理;可能要进一步开发以集成企业内部的各种环境,例如常见的LDAP;Travis CI目前对Ruby支持非常好,但对于Java就一般,还不支持C#。

其他亮点

Travis CI还有很多非常Cool的特性,包括:

小结

Travis CI有没有革命性的新技术?看起来是没有,不过它用到了很多当前最流行的技术和工具,比如DSL、Github、RESTful、Chef等等,而且达到了非常好的效果,这自然吸引了大量Geek,这也受益于开源社区所独有的特别开放的氛围。

关于Travis Ci和Jenkins/Hudson的关系,在我看来,短时间Jenkins/Hudson的主流地位并不会受到挑战,这主要是因为Jenkins的生态圈目前更为成熟,对Java的支持更好,另外对大多数中小企业来说,部署Jenkisn/Hudson容易得多。但长期来看,Travis CI这种云的方式必然会在大型的企业中占据主流的地位。

本文只是从用户角度简单分析了Travis CI,关于Travis CI的架构和实现,希望有机会再学习分享,目前,感兴趣的读者可以参考这篇架构概览

延伸阅读

 


原创文章,转载请注明出处, 本文地址: http://www.juvenxu.com/2012/03/06/travis-ci/

使用email-ext替换Jenkins(Hudson)的默认邮件通知

May 18th, 2011 2 comments

注:本文由Jdonee投递

简述

众所周知,Jenkins(Hudson)默认提供了一个邮件通知,能在构建失败、构建不稳定等状态后发送邮件。但是它本身有很多局限性,比如它的邮件通知无法提供详细的邮件内容、无法定义发送邮件的格式、无法定义灵活的邮件接收配置等等。在这样的情况下,我们找到了Jenkins Email Extension Plugin。该插件能允许你自定义邮件通知的方方面面,比如在发送邮件时你可以自定义发送给谁,发送具体什么内容等等。本文不会告诉你如何安装该插件,关于插件的安装请参考这里

功能概要

该插件扩展了Hudson构建默认的邮件通知功能,并赋予你更多更灵活的控制。它能在如下三块区域来自定义:

  • Triggers -指定发送一封邮件通知应有的前置条件。
  • Content - 指定每封触发邮件的标题和正文的内容。
  • Recipients -.指定一封邮件触发后发送给谁来接收(邮件)。

配置

它主要包含两个部分:基本配置和项目配置。

公共配置

当然,在一个项目中应用email-ext插件之前,您必须做一些公共的配置。现在先跳转到Hudson的“系统配置”页面,如下图:

找到标题为“Extended E-mail Notification”的片段,你就能配置一些公共的email-ext属性。这些属性必须匹配你SMTP邮件服务器的设置。这一节不仅能配置成Hudson原有邮件通知的镜像(虽然有很多配置是一样的,但这是个不同的扩展点),而且还增加了一些额外的功能。输入框中名为 Default Subject 和 Default Content 的项允许你在公共级别配置邮件的内容。这样做的话,可以使您为所有的项目按您的需求做更好的、更简单的配置。如下图。

根据帮助文档,我们可以了解到一些公共属性,下面我解释一下常用的属性。
属性详解:

  1. Override Global Settings
    如果不选,该插件将使用默认的E-mail Notification通知选项。反之,您可以通过指定不同于( 默认选项)的设置来进行覆盖。
  2. Default Content Type
    指定构建后发送邮件内容的类型,有Text和HTML两种.
  3. Use List-ID Email Header
    为所有的邮件设置一个List-ID的邮件信头,这样你就可以在邮件客户端使用过滤。它也能阻止邮件发件人大部分的自动回复(诸如离开办公室、休假等等)。你可以使用你习惯的任何名称或者ID号,但是他们必须符合如下其中一种格式(真实的ID必须要包含在<和>标记里):
    <ci-notifications.company.org>
    Build Notifications <ci-notifications.company.org>
    “Build Notifications” <ci-notifications.company.org>
    关于更详细的List-ID说明请参阅RFC-2919.
  4. Default Subject
    自定义邮件通知的默认主题名称。该插件能在邮件的主题字段中替换一些令牌,这样你就可以从构建中包含指定的输出信息。
  5. Default Content
    自定义邮件通知的默认内容主体。该插件能在邮件的内容主体中替换一些令牌,这样你就可以从构建中包含指定的输出信息。
  6. Content Token Reference[公共配置]
    所有的参数都是可选的,每个令牌的参数可以如下表示,字符串类型使用name=“value”,而布尔型和数字型使用name=value。如果{和}标记里面没有参数,则不会被解析。
    示例:$TOKEN, ${TOKEN}, ${TOKEN, count=100}, ${ENV, var=”PATH”}

可用令牌

  • ${BUILD_LOG, maxLines, escapeHtml} -显示最终构建日志。
    • maxLines – 显示该日志最多显示的行数,默认250行。
    • escapeHtml -如果为true,格式化HTML。默认false。
  • ${BUILD_LOG_REGEX, regex, linesBefore, linesAfter, maxMatches, showTruncatedLines, substText, escapeHtml, matchedLineHtmlStyle} -按正则表达式匹配显示构建日志的行数。
    • 匹配符合该正则表达式的行数。参阅java.util.regex.Pattern,默认“(?i)\b(error|exception|fatal|fail(ed|ure)|un(defined|resolved))\b”。
    • linesBefore -包含在匹配行之前的行编号。行数会与当前的另一个行匹配或者linesAfter重叠,默认0
    • linesAfter -包含在匹配行之后的行编号。行数会与当前的另一个行匹配或者linesBefore重叠,默认0
    • maxMatches -匹配的最大数量,如果为0,则包含所有匹配。默认为0。
    • showTruncatedLines -如果为true,包含[...truncated ### lines...]行。默认为true。
    • substText -如果非空,把这部分文字插入该邮件,而不是整行。默认为空。
    • escapeHtml -如果为true,格式化HTML。默认false。
    • matchedLineHtmlStyle -如果非空,输出HTML。匹配的行数将变为<b style=”your-style-value”> html escaped matched line </b>格式。默认为空。
  • ${BUILD_NUMBER} -显示当前构建的编号。
  • ${BUILD_STATUS} -显示当前构建的状态(失败、成功等等)
  • ${BUILD_URL} -显示当前构建的URL地址。
  • ${CHANGES, showPaths, format, pathFormat} -显示上一次构建之后的变化。
    • showPaths – 如果为 true,显示提交修改后的地址。默认false。
    • format – 遍历提交信息,一个包含%X的字符串,其中%a表示作者,%d表示日期,%m表示消息,%p表示路径,%r表示版本。注意,并不是所有的版本系统都支持%d和%r。如果指定showPaths将被忽略。默认“[%a] %m\n”。
    • pathFormat -一个包含“%p”的字符串,用来标示怎么打印字符串。
  • ${CHANGES_SINCE_LAST_SUCCESS, reverse, format, showPaths, changesFormat, pathFormat} -显示上一次成功构建之后的变化。
    • reverse -在顶部标示新近的构建。默认false。
    • format -遍历构建信息,一个包含%X的字符串,其中%c为所有的改变,%n为构建编号。默认”Changes for Build #%n\n%c\n”。
    • showPaths, changesFormat, pathFormat – 分别定义如${CHANGES}的showPathsformatpathFormat参数。
  • ${CHANGES_SINCE_LAST_UNSTABLE, reverse, format, showPaths, changesFormat, pathFormat} -显示显示上一次不稳固或者成功的构建之后的变化。
    • reverse -在顶部标示新近的构建。默认false。
    • format -遍历构建信息,一个包含%X的字符串,其中%c为所有的改变,%n为构建编号。默认”Changes for Build #%n\n%c\n”。
    • showPaths, changesFormat, pathFormat -分别定义如${CHANGES}的showPathsformatpathFormat参数。
  • ${ENV, var} – 显示一个环境变量。
    • var – 显示该环境变量的名称。如果为空,显示所有,默认为空。
  • ${FAILED_TESTS} -如果有失败的测试,显示这些失败的单元测试信息。
  • ${JENKINS_URL} -显示Jenkins服务器的地址。(你能在“系统配置”页改变它)。
  • ${HUDSON_URL} -不推荐,请使用$JENKINS_URL
  • ${PROJECT_NAME} -显示项目的名称。
  • ${PROJECT_URL} -显示项目的URL。
  • ${SVN_REVISION} -显示SVN的版本号。
  • ${CAUSE} -显示谁、通过什么渠道触发这次构建。
  • ${JELLY_SCRIPT, template} -从一个Jelly脚本模板中自定义消息内容。有两种模板可供配置:HTML和TEXT。你可以在$JENKINS_HOME/email-templates下自定义替换它。当使用自动义模板时,”template”参数的名称不包含“.jelly”。
    • template -模板名称,默认”html”。
  • ${FILE, path} -包含一个指定文件的内容
    • path -文件路径,注意,是工作区目录的相对路径。
  • ${TEST_COUNTS, var} -显示测试的数量。
    • var – 默认“total”。
      • total -所有测试的数量。
      • fail -失败测试的数量。
      • skip -跳过测试的数量。

项目配置

要想在一个项目中使用email-ext插件,你首先必须在项目配置页激活它。在”Post-build Actions”选项中勾选”Editable Email Notification”标签。

项目基本配置

当插件激活后你就能编辑如下三个字段:

  • Global Recipient List -这是一个以逗号(或者空格)分隔的可接受邮件的邮箱地址列表。允许您为每封邮件指定单独的列表。
  • Default Subject -允许你配置令牌,这样就可以在项目中更容易地配置所有邮件的主题。
  • Default Content -跟 Default Subject的作用一样,但是是把主题替换内容主体。
项目高级配置

要查看插件的高级配置,请点击”Advanced”按钮。该选项允许您各种类型的邮件触发器指定接收者。默认情况下,唯一使用的触发器配置是”Failure”触发器。要增加更多的触发器,选择“Add a Trigger”旁边下拉列表中的类型,它会增加到控件上面的列表中。一旦你增加了一个触发器,你就可以对它做一些选择。如果你点击一个触发器旁边的”?”号,它将告诉你你在什么前置条件中来触发邮件发送。如下图。

  • Send to Recipient List -如果勾选,邮件将发送到”Global Recipient List”中的所有邮件地址。
  • Send to Committers -该邮件会发给上次构建时检查过代码的人员,该插件会基于提交者的ID和追加Jenkins配置页面的(default email suffix)默认邮件后缀来生成一个邮件地址。譬如,上次提交代码的人是”first.last”, 默认的电子邮件后缀为“@somewhere.com”,那么电子邮件将被发送到“first.last@ somewhere.com”。
  • Send To Requester -如果勾选,邮件将发送给构建触发者。
  • Include Culprits -如果勾选,而且 “Send To Committers”勾选,邮件将包含最后成功构建的提交者。
  • More Configuration -通过单击”+(expand)”链接您能为每个邮件触发器作更多单独的设置。
    • Recipient List -这是一个以逗号(或者空格)分隔的可接受邮件的邮箱地址列表。如果触发就发送邮件到该列表。该列表会追加在”Global Recipient List”里。
    • Subject – 指定选择邮件的主题。注意:高级选项中的邮件触发器类型可覆盖对它的配置。
    • Content -指定选择邮件的内容主体。注意:高级选项中的邮件触发器类型可覆盖对它的配置。
  • Remove -通过单击指定触发器当前行的”Delete”按钮,你可以删除该触发器。
项目邮件令牌

email-ext插件使用令牌来允许动态数据插入到邮件的主题和内容主体中。令牌是一个以$(美元符号)开始,并以空格结束的字符串。当一个邮件触发时,主题和内容主体字段的所有令牌都会通过真实的值动态地替换。同样,令牌中的“值”能包含其它的令牌,那将被替换成真实的内容。比如, $DEFAULT_SUBJECT令牌能通过从公共配置页面的Default Subject字段中的文本(或者其它令牌)替换。同理, $PROJECT_DEFAULT_SUBJECT令牌也能通过项目配置页面的Default Subject 字段中值替换。

一旦你的项目中激活email-ext插件,它会使用默认值设置邮件的内容字段。项目配置页的默认主题和主体内容字段分别对应的是DEFAULT_SUBJECT和DEFAULT_CONTENT,因此它会自动地使用全局的配置。同理,每个触发器中的内容分别对应的是$PROJECT_DEFAULT_SUBJECT 和 $PROJECT_DEFAULT_CONTENT,所以它也会自动地使用项目的配置。由于令牌中的“值”能包含其它的令牌,这样一来,您就能为令牌快速地创建不同的切入点:全局级别(所有项目),专属级别(单一项目),通用级别(两者之间)。

如果你要查看所有可用的令牌,你可以点击项目配置页的Content Token Reference的?号获取详细的信息。

根据帮助文档,我们可以了解到一些公共属性,下面我解释一下常用的属性。
属性详解:

1、触发器类型

注意:所有的触发器都只能配置一次。

Failure:即时发送构建失败的邮件。如果”Still Failing”触发器已配置,而上一次构建的状态是”Failure”,那么”Still Failing”触发器将发送一封邮件来替代(它)。
Unstable:即时发送构建不稳固的邮件。如果”Still Unstable”触发器已配置,而上一次构建的状态是”Unstable”,那么”Still Unstable”触发器将发送一封邮件来替代(它)。
Still Failing:如果两次或两次以上连续构建的状态为”Failure”,发送该邮件。
Success:如果构建的状态为”Successful”发送邮件。如果”Fixed”已配置,而上次构建的状态为“Failure”或“Unstable”,那么”Fixed”触发器将发送一封邮件来替代(它)。
Fixed:当构建状态从“Failure”或“Unstable”变为”Successful”时发送邮件。
Still Unstable:如果两次或两次以上连续构建的状态为” Unstable “,发送该邮件。
Before Build:当构建开始时发送邮件。

2、Content Token Reference[项目配置]

注意:这里只解释系统配置页面中缺少的令牌。

  • ${DEFAULT_SUBJECT} -这是Jenkins系统配置页面默认配置的邮件主题
  • ${DEFAULT_CONTENT} -这是Jenkins系统配置页面默认配置的邮件内容主体
  • ${PROJECT_DEFAULT_SUBJECT} – 这是项目的默认邮件主题。高级配置中使用该令牌的结果要优先于Default Subject字段。警告:不要在Default Subject 或者Default Content中使用该令牌,它会产生一个未知的结果。
  • ${PROJECT_DEFAULT_CONTENT} -这是项目的默认邮件内容主体。高级配置中使用该令牌的结果要优先于Default Content字段。警告:不要在Default Subject 或者Default Content中使用该令牌,它会产生一个未知的结果。

Jelly 脚本

从Jenkins(Hudson)2.9版本开始我们可以使用Jelly脚本。Jelly脚本跟Hudson的API挂钩,能获得你想要的任何信息,所以它很强大。插件有两个打包后的Jelly脚本,当然你也可以自定义(脚本)。

关于插件中默认的两个Jelly脚本:一个用来设计HTML格式邮件,另一个则是定义TEXT格式邮件。通过上面的截图看到它们的样子。你能通过使用模板参数指定插件调用哪一个脚本。它们的使用方法如下:

  • 文本格式: ${JELLY_SCRIPT,template=”text”}
  • HTML格式:  ${JELLY_SCRIPT,template=”html”}

你也能编写属于自己的Jelly脚本。Jelly脚本能跟Hudson的API(包括hudson.model.AbstractBuildhudson.model.AbstractProject)挂钩,因而特别强大。如果你打算这么做,你可以先参考现有的htmltext脚本一探究竟。

值得注意的是,拥有Hudson管理员权限是使用自定义Jelly脚本(该脚本没有跟email-ext打包)的前提。脚本的生成步骤本身其实相对简单:

  1. 创建Jelly脚本。脚本的名称应该是<名称>.jelly。名称以.jelly结尾是很重要的。
  2. 让你的Hudson管理员把脚本存放在HUDSON_HOME\email-templates文件夹里。
  3. 使用Jelly令牌,让template匹配你的脚本名称(不要包含后缀)。比如,脚本的名称为foobar.jelly,则邮件内容中应该是${JELLY_SCRIPT,template=”foobar”}。

下面两个图就是就是使用Jelly脚本生成的邮件(最新版Email-ext新增html_gamil模板,它跟html模板类似,所以这里不再显示它的截图):

总结

以上就是我介绍的Email-ext插件,由于自己的局限,对于它的使用没有更深的了解。参考资料[2]中还有关于它的扩展,你也可以自行扩充它的功能。如果您有关于该插件以及Jenkins使用的更多更好的感受,我期待与您一起分享。

参考资料

[1] 《Maven实战》第11章11.9邮件反馈。

[2] https://wiki.jenkins-ci.org/display/JENKINS/Email-ext+plugin


原创文章,转载请注明出处, 本文地址: http://www.juvenxu.com/2011/05/18/hudson-email-ext/

Categories: Hudson Tags: , ,

InfoQ Maven专栏(四)——基于Maven的持续集成实践

February 4th, 2011 5 comments

Martin的《持续集成》

相信很多读者和我一样,最早接触到持续集成的概念是来自Martin的著名文章《持续集成》,该文最早发布于2000年9月,之后在2006年进行了一次修订,它清晰地解释了持续集成的概念,并总结了10条实践,它们分别为:

  • 只维护一个源码仓库
  • 自动化构建
  • 让构建自行测试
  • 每人每天向主干提交代码
  • 每次提交都应在持续集成机器上构建主干
  • 保持快速的构建
  • 在模拟生产环境中测试
  • 让每个人都能轻易获得最新的可执行文件
  • 每个人都能看到进度
  • 自动化部署

原始文章距今已10年有余,这在软件行业中算是很长的时间了,但我们都能看到Martin总结的这些实践依旧闪耀着光芒,依旧有很多团队在努力实践它们并得到了丰厚的回报,当然也有很多团队因为各种原因拒绝实践持续集成从而无法体会到个中好处。

从这10条实践中我们能找到很多流行开源工具的影子,例如版本控制工具cvs、svn、git,自动化构建工具Maven、Ant,自动化测试框架JUnit、TestNG,以及持续集成服务器CruiseControl和Hudson等等。其实不论你是否实践持续集成,单独使用这其中的很多工具都能发挥极大的价值,持续集成的一大意义在于它引入了一个有效的流程,能让这些工具有机融合,并相互促进。 关于持续集成还有一本获得Jolt大奖的图书,名为《持续集成——软件质量改进和风险降低之道》。但无论是Martin的文章,还是这本图书,都没有阐述使用Maven作为自动化构建工具实施持续集成的细节。本文旨在介绍一些基于Maven实施持续集成的实践,希望这些经验能从具体处帮助到读者。

架设私有Maven仓库

Martin的文章并没有涉及到依赖管理的内容,但在Java的世界中,依赖管理是开发人员不得不面对的问题。无论是外部的开源类库依赖,还是项目内部的模块间依赖,都需要有效地管理。可以说依赖管理是持续集成核心的内容之一。Maven通过其依赖管理机制和随处可用的中央仓库有效地解决了这个问题,用户只需要在POM中声明项目所需要的依赖,Maven就能在构建的时候自动从仓库解析依赖。

不过仅仅这样是不够的,我们知道,持续集成的最大好处在于降低风险,简单地来说就是尽早暴露问题,能让开发人员及早发现并修复,从而降低修复成本。可是,如果每个人都从中央仓库重复下载依赖,这是非常耗时的,集成的反馈周期肯定会延长。我已经无数次听到有人抱怨“Maven在下载整个Internet!”。构建要快!持续集成反馈要快!Maven你不能拖慢这个流程。

幸运的是开源世界有很好的解决方案,只要使用Maven仓库管理器软件如Nexus建立一个私有的Maven仓库,问题就能迎刃而解。原理很简单,这个位于局域网内的Maven仓库能够代理所有外部仓库,从而避免所有人从Internet重复下载依赖文件。这样Maven解析依赖的时候仅限于局域网,构建速度就大大地加快了。例如大家都需要使用junit-4.8.2.jar,当第一个人向私有仓库请求的时候,私有仓库从中央库下载并缓存下来,假设耗时10s,之后其他人需要junit-4.8.2.jar的时候,私有仓库直接使用缓存的文件,这个耗时可能就是1s。如果有100个开发人员使用该文件,那节省的时间就是 100 * 10 – ( 10 + 99 * 1 ) = 891s ,实际情况中依赖的数量可能会是成百上千,那节省的时间就变得非常的可观。

也许有人会说,我也完全可以将项目依赖加入到版本控制中,这一点甚至在《卓有成效的程序员》中都被明确提及,在该书第5章的”DRY版本控制”一节中,Neal Ford有这么一段话:“所有用来构建项目的东西都应该被放入版本控制,包括二进制文件(类库,框架,JAR文件,构建脚本等等)”。作者进一步解释了其目的,这么做能够保证项目不受外部因素影响(如依赖版本变化,甚至丢失),保证构建的稳定,作者也同时提及了一般版本控制工具处理二进制文件的性能问题。抛开这条结论性的实践,仔细考虑其目的,我们就能发现,私有Maven仓库同样能保证构建的稳定,而且能避免版本控制工具处理二进制文件而造成的潜在性能问题。所以,我斗胆说一句,Neal Ford所提的这条实践OUT了!

私有Maven的仓库的意义还不仅限于此,结合自动化部署和Maven的SNAPSHOT机制,它能大大促进项目集成的效率。

在模块化的开发环境中,大家各司其职,专注于自己所负责的模块,持续集成的规则是,在往版本控制提交代码前,需要先保证本地构建没有问题,那一般的做法就是更新所有模块的代码并构建。可是,真的需要构建那些其实你并不怎么关心的模块么?且不谈一旦构建他人代码时出错,你往往会不知所措,这种做法同时也增加了本地构建的时间。

Maven有SNAPSHOT版本的概念,其目的就是让你能够构建一个临时的版本,供团队他人使用,这样他们就不必在代码的层次关心自己的依赖。于是私有Maven仓库就充当了一个中介的作用,而持续集成服务器就多了一个职责,每次它成功构建一个模块,都应该将该模块的SNAPSHOT版本发布到Maven仓库中。现在,大家就不用去构建别人的代码了,Maven能自动帮你从私有仓库解析下载依赖的最新SNAPSHOT(使用mvn命令的-U参数强制更新)。注意,除了持续集成服务器外,任何其他人都不应该发布SNAPSHOT版本到Maven仓库,因为只有持续集成服务器的环境是可信任的,你能在本地成功执行mvn clean install并不代表持续集成服务器上该命令能成功,由于每个人的本地环境各有差异,因此集成的成功与否应当以持续集成服务器为准,而只有集成成功后,SNAPSHOT才可以被部署到私有仓库供他人使用。

鉴于上述的原因分析,我认为在基于Maven的持续集成环境中,再怎么强调私有Maven仓库的重要性都是不为过的。

正确的集成命令

在持续集成服务器上使用怎样的 mvn 命令集成项目,这个问题乍一看答案很显然,不就是 mvn clean install 么?事实上比较好的集成命令会稍微复杂些,下面是一些总结:

  • 不要忘了clean: clean能够保证上一次构建的输出不会影响到本次构建。
  • 使用deploy而不是install: 构建的SNAPSHOT输出应当被自动部署到私有Maven仓库供他人使用,这一点在前面已经详细论述。
  • 使用-U参数: 该参数能强制让Maven检查所有SNAPSHOT依赖更新,确保集成基于最新的状态,如果没有该参数,Maven默认以天为单位检查更新,而持续集成的频率应该比这高很多。
  • 使用-e参数:如果构建出现异常,该参数能让Maven打印完整的stack trace,以方便分析错误原因。
  • 使用-Dmaven.repo.local参数:如果持续集成服务器有很多任务,每个任务都会使用本地仓库,下载依赖至本地仓库,为了避免这种多线程使用本地仓库可能会引起的冲突,可以使用-Dmaven.repo.local=/home/juven/ci/foo-repo/这样的参数为每个任务分配本地仓库。
  • 使用-B参数:该参数表示让Maven使用批处理模式构建项目,能够避免一些需要人工参与交互而造成的挂起状态。

综上,持续集成服务器上的集成命令应该为 mvn clean deploy -B -e -U -Dmaven.repo.local=xxx 。此外,定期清理持续集成服务器的本地Maven仓库也是个很好的习惯,这样可以避免浪费磁盘资源,几乎所有的持续集成服务器软件都支持本地的脚本任务,你可以写一行简单的shell或bat脚本,然后配置以天为单位自动清理仓库。需要注意的是,这么做的前提是你有私有Maven仓库,否则每次都从Internet下载所有依赖会是一场噩梦。

用好Profile

如果不需要考虑各种不同的环境, 而且你的自动测试(包括集成测试)跑得飞快,那你就不用为项目建立多个集成任务。但实际的情况是,集成的时候可能要考虑各种环境,例如开发环境、测试环境、产品环境。而当项目越来越大,测试越来越多,控制构建时间在一个可接受的范围内(例如10分钟)变得越来越不现实。《持续集成——软件质量改进和风险降低之道》中介绍了一种名为分阶段构建(staged build)的解决方案,例如你可以将构建分为两个部分,第一部分包括了编译和单元测试等能够快速结束的任务,第二个部分包括集成测试等耗时较长的任务,只有第一部分成功完成后,才触发第二部分集成。这么做的意义在于让持续集成的反馈尽可能的快。

Maven的Profile机制能够很好的支持分阶段构建。例如,借助Maven Surefire Plugin,你可以统一单元测试命名为**UT,统一集成测试命名为**IT,然后配置Maven Surefire Plugin默认只运行单元测试,然后再编写一个名为integrationTest的Profile,在其中配置Maven Surefire Plugin运行集成测试。然后再以此为基础分阶段构建项目,第一个构建为 mvn clean install -B -e -U ,第二个构建任务为 mvn clean deploy -B -e -U -PintegrationTest 。前一个构建成功后再触发第二个构建,然后才部署至Maven仓库。值得一提的是,Maven Surefire Plugin能够很好支持JUnit 3、JUnit 4和TestNG,你可以按照最适合自己的方式来划分单元测试和集成测试。

另一个常见的分阶段构建案例是生成Maven站点,使用 mvn clean site 生成站点往往比较耗时且耗资源,这样的任务对应的持续集成中的持续审查阶段,该阶段往往不需要很高的集成频率。你会希望每10分钟就检查源代码变更并编译测试,但很少有人会希望每10分钟让系统生成一次测试覆盖率报告、CheckStyle报告等内容,因此合理的做法是使用一个较低的频率,例如每天,这样可以避免无谓的资源消耗,更重要的是,这样不会拖慢本该很快的编译和单元测试等反馈内容。

还有一些情况是系统需要基于不同环境进行集成,这时候就需要用到Maven的属性机制、资源过滤、以及前面提到的Profile。篇幅原因,这里不再展开。

小结

持续集成是敏捷最重要的实践之一,但如何在基于Maven的环境下实践持续集成却鲜有文章详述,本文介绍了一些该主题的最佳实践,包括架设私有仓库、使用正确的集成命令、利用Profile等技术处理分阶段构建等等。本文旨在让广大Maven用户认识到这些实践的存在及重要性,并没有详细解释一些诸如Nexus安装配置、Maven Surefire Plugin配置、或者说Profile配置使用方面的细节,如果你希望看到更细节的介绍,可以参考我的《Maven实战》一书。除了上面的内容之外,该书还详细解释了如何使用Hudson(也许该改称Jenkins了)这一最流行的开源持续集成服务器。当然,如果你有关于Maven和持续集成方面的经验,也请不吝分享。

本文已经首发于InfoQ中文站,版权所有,原文为Maven实战(四)——基于Maven的持续集成实践如需转载,请务必附带本声明,谢谢。
InfoQ中文站是一个面向中高端技术人员的在线独立社区,为Java、.NET、Ruby、SOA、敏捷、架构等领域提供及时而有深度的资讯、高端技术大会如QCon 、线下技术交流活动QClub、免费迷你书下载如架构师》等。


原创文章,转载请注明出处, 本文地址: http://www.juvenxu.com/2011/02/04/infoq-maven-ci-best-practices/