I'm an Infrastructure Specialist at
ThoughtWorks. In my role I
make sure that we are building our software so it can successfully be
deployed to production. In this series of blog posts I hope
to
pass on my top ten tips for using CruiseControl Enterprise
effectively. I'm writing these with the developers or systems
administrators in mind: the people who most often manage
CruiseControl. However, I hope that anybody who is interested
in
Continuous Integration will get something from these articles.
平均规模的Java项目会有很多依赖: 开源的工具和框架, 第三方的库, 你的项目或组织内部开发的库...等等等等.
我数了数我目前项目中依赖的(或者可能依赖的 :) ) jar 文件, 有 84 个. 更多的痛苦来自于你管理这些依赖的方式.
让你的软件能够编译或执行通常比你想象的要困难的多, 如果这些依赖不清楚的话. 加入一个新项目,
然后用一天的时间让你的代码从不清楚的代码或环境依赖中成功编译真是充满了痛苦.
让CruiseControl Enterprise和它的依赖 从 你自己的项目和项目依赖中清楚地分离出来, 是一个很好的主意. 最基本的例子, 可以让你的CruiseControl Enterprise安装像下面这样布局:
|-- cruise
| |-- 2.7.0
| |-- 2.7.1
|-- logs
| |-- blowfish
| |-- scorpion
|-- projects
| |-- blowfish
| |-- scorpion
这样升级 CruiseControl
Enterprise 应该只是几分钟的事. 如果你发现你需要往CCE本身加入额外的库, 通常这是一个警告信号,
意味着你的项目不能满足自我依赖. 仅有的例外是当你用自定义的 bootstrapper 或 publisher 等来扩展 CCE
自身的时候.
这里有两点尤其需要注意: 日志文件和 '.ser' 文件. 日志文件代表了你的项目的历史,
这就是我为什么一直努力把它们保存在与CruiseControl安装目录不同的目录层次中. 另外, 缺省情况下,
CruiseControl 还把项目的状态信息持久化到后缀名为 '.ser' 的文件中. 确保升级的时候你保留了它们.
如果你这么做了, 你的项目又不依赖于CCE, 那么就能很容易的升级CCE.
接下来, 考虑一下那些熔入到你的构建工具中的依赖. 有一个常用的模式是把依赖都放到你的 Apache Ant 的 'lib' 目录中. 这样做对诸如 Junit
之类一般不会被你的产品代码依赖的库没问题. 但如果你的产品代码或构建脚本依赖的库实际上贮存于你的构建工具内部时, 你就有麻烦了. 它今天可能还工作,
但明天就不一定了. 如果你升级了构建工具中你的代码依赖的那些库, 有可能你就构建不了以前的代码了. 这让事情变得很"有趣",
尤其是当你正试图通过升级依赖库解决一个产品中的Bug时. (升级后能构建最新的代码, 但不能构建以前的代码)
好消息是, 这很容易解决: 让你的项目包含自己的依赖. (让你的构建脚本显式的引用包含在项目内部的依赖, 而不是隐式的让构建工具来包含那些依赖). 比如说,
如果你的Ant脚本依赖于Oracle, 而你的项目使用的Ant包含了Oracle的驱动, 那么你的脚本看起来像这样:
<target name="drop_all_tables">
<sql driver="oracle.jdbc.driver.OracleDriver" userid="user"
password="donttell" url="jdbc:oracle:thin:@localhost:1521:orcl" delimiter=";">
<transaction src="${sql.dir}/drop_all_tables.sql" />
</sql>
</target>
在这个例子中, 对于Oracle驱动的依赖没有被明确的声明, Ant 只会从缺省classpath中寻找
"oracle.jdbc.driver.OracleDriver".
第一件要做的事就是把驱动所在的jar文件扔到project内部并设置一个classpath:
lib/
`-- ojdbc14.jar
<path id="buildtime">
<pathelement location="${lib.dir}/ojdbc14.jar"/>
</path>
<target name="drop_all_tables">
<sql driver="oracle.jdbc.driver.OracleDriver" userid="user"
password="donttell" url="jdbc:oracle:thin:@localhost:1521:orcl" delimiter=";" classpathref="buildtime">
<transaction src="${sql.dir}/drop_all_tables.sql" />
</sql>
</target>
注意'sql'元素中这个额外的 'classpathref' 属性. 这个有用的属性允许你引用脚本中在别处定义的路径, 减少了重复代码.
当你把一个依赖移到项目内部时, 要在可以删除的时候正式的删除这个依赖. 在我最近采用这种过程的一个项目中, 我很幸运的能够以我喜欢的方式进行定期发布.
我修复了trunk中的依赖, 并且几周后进行了重新检查. 以前那种依赖于项目外部某些东西的 release branches 没有被使用.
因此概括的说, 考虑一下你项目的依赖项, 包括那些只是凑巧被满足的依赖项. 如果你让那些依赖都很清楚直观, 我保证你会更少的被你的同事追问为什么他们不能编译!
如果你有许多工程有相同的依赖, 我希望在后续的文章中会解决一些问题.