Java项目中系统构建编号和版本号管理的当前最佳实践是什么?特别:
-
如何在分布式开发环境中系统地管理构建号
-
如何在源/可用于运行时应用程序中维护版本号
-
如何正确地与源存储库集成
-
如何更自动地管理版本号与存储库标签
-
如何与持续构建基础架构集成
有很多工具可用,而ant(我们正在使用的构建系统)有一个维护构建号的任务,但是不清楚如何使用CVS,svn或类似的多个并发开发人员来管理它 .
[编辑]
下面出现了几个好的和有用的部分或具体的答案,所以我将总结其中的一些 . 听起来对我来说就像这里没有真正强大的“最佳实践”,而是一系列重叠的想法 . 下面,找到我的摘要和一些人们可能会尝试回答的结果问题 . [对stackoverflow来说是新的...如果我做错了,请提供评论 . ]
-
如果您使用的是SVN,则可以使用特定结帐的版本控制 . 构建编号可以利用它来创建标识特定结帐/修订的唯一构建号 . [我们因遗留原因而使用的CVS并没有提供这种程度的洞察力......使用标签进行人工干预可以帮助您实现目标 . ]
-
如果您使用maven作为构建系统,则支持从SCM生成版本号,以及用于自动生成版本的发布模块 . [出于各种原因,我们不能使用maven,但这有助于那些能够做到的人 . [感谢marcelo-morales]]
-
如果您使用ant作为构建系统,则以下任务描述可以帮助生成捕获构建信息的Java .properties文件,然后可以通过多种方式将其折叠到构建中 . [我们扩展了这个想法,包括哈德森派生的信息,谢谢marty-lamb] .
-
Ant和maven(以及hudson和巡航控制)提供了将构建号码放入.properties文件或.txt / .html文件的简便方法 . 这种“安全”是否足以防止它被故意或意外地篡改?在构建时将它编译成“版本化”类是否更好?
-
断言:应在hudson之类的持续集成系统中定义/制定构建编号 . [感谢marcelo-morales]我们已经采纳了这个建议,但它确实破解了发布工程问题:发布是如何发生的?发布中是否有多个buildnumbers?来自不同版本的构建数量之间是否存在有意义的关系?
-
问题:内部版本号背后的目标是什么?它用于QA吗?怎么样?它是否主要由开发人员用于在开发期间消除多个构建之间的歧义,或更多用于QA以确定最终用户构建的内容?如果目标是可重复性,理论上这就是发布版本号应该提供的 - 为什么不呢? (请在下面作为你的答案的一部分回答这个问题,它将有助于说明你做出的建议/建议...)
-
问题:手动构建中是否存在构建号码的位置?这是有问题的,以至于每个人都应该使用CI解决方案吗?
-
问题:是否应该将 Build 号码签入SCM?如果目标是可靠且明确地识别特定构建,那么如何应对可能崩溃/重启等的各种连续或手动构建系统......
-
问题:构建号码是否应该简短且甜蜜(即单调递增整数),以便易于存档文件名,易于在通信中引用等等......或者它应该是长而且充满用户名,日期戳,机器名等?
-
问题:请提供有关构建号分配如何适合您的大型自动发布流程的详细信息 . 是的,maven爱好者,我们知道这已经完成了,但并非我们所有人都喝醉了kool-aid ......
我真的很喜欢将其充实成一个完整的答案,至少对于我们的cvs / ant / hudson设置的具体例子,所以有人可以基于这个问题构建一个完整的策略 . 我将标记为“答案”的任何人都可以对这个特殊案例给出坚果的描述(包括cvs标记方案,相关CI配置项和释放过程,将构建号折叠到发行版中,以便以编程方式访问 . )如果您想要/回答其他特定配置(例如,svn / maven / cruise control)我将从这里链接到这个问题 . --JA
[编辑09年10月23日]我接受了最高投票的答案,因为我认为这是一个合理的解决方案,而其他几个答案也包括好主意 . 如果有人想采取一些措施来合成其中的一些,请考虑接受另一个 . 我对marty-lamb 's is that it doesn'唯一关注的是产生一个可靠的序列化内部版本号 - 它取决于构建器的本地时钟's system to provide unambiguous build numbers, which isn' t .
[编辑7月10日]
我们现在包括如下所示的类 . 这允许将版本号编译成最终的可执行文件 . 在记录数据,长期归档输出产品中发出不同形式的版本信息,并用于跟踪我们(有时是数年以后)对特定构建的输出产品的分析 .
public final class AppVersion
{
// SVN should fill this out with the latest tag when it's checked out.
private static final String APP_SVNURL_RAW =
"$HeadURL: svn+ssh://user@host/svnroot/app/trunk/src/AppVersion.java $";
private static final String APP_SVN_REVISION_RAW = "$Revision: 325 $";
private static final Pattern SVNBRANCH_PAT =
Pattern.compile("(branches|trunk|releases)\\/([\\w\\.\\-]+)\\/.*");
private static final String APP_SVNTAIL =
APP_SVNURL_RAW.replaceFirst(".*\\/svnroot\\/app\\/", "");
private static final String APP_BRANCHTAG;
private static final String APP_BRANCHTAG_NAME;
private static final String APP_SVNREVISION =
APP_SVN_REVISION_RAW.replaceAll("\\$Revision:\\s*","").replaceAll("\\s*\\$", "");
static {
Matcher m = SVNBRANCH_PAT.matcher(APP_SVNTAIL);
if (!m.matches()) {
APP_BRANCHTAG = "[Broken SVN Info]";
APP_BRANCHTAG_NAME = "[Broken SVN Info]";
} else {
APP_BRANCHTAG = m.group(1);
if (APP_BRANCHTAG.equals("trunk")) {
// this isn't necessary in this SO example, but it
// is since we don't call it trunk in the real case
APP_BRANCHTAG_NAME = "trunk";
} else {
APP_BRANCHTAG_NAME = m.group(2);
}
}
}
public static String tagOrBranchName()
{ return APP_BRANCHTAG_NAME; }
/** Answers a formatter String descriptor for the app version.
* @return version string */
public static String longStringVersion()
{ return "app "+tagOrBranchName()+" ("+
tagOrBranchName()+", svn revision="+svnRevision()+")"; }
public static String shortStringVersion()
{ return tagOrBranchName(); }
public static String svnVersion()
{ return APP_SVNURL_RAW; }
public static String svnRevision()
{ return APP_SVNREVISION; }
public static String svnBranchId()
{ return APP_BRANCHTAG + "/" + APP_BRANCHTAG_NAME; }
public static final String banner()
{
StringBuilder sb = new StringBuilder();
sb.append("\n----------------------------------------------------------------");
sb.append("\nApplication -- ");
sb.append(longStringVersion());
sb.append("\n----------------------------------------------------------------\n");
return sb.toString();
}
}
如果这值得成为维基讨论,请留下评论 .
9 回答
对于我的几个项目,我捕获了subversion版本号,时间,运行构建的用户以及一些系统信息,将它们填充到包含在应用程序jar中的.properties文件中,并在运行时读取该jar .
ant代码如下所示:
扩展它很简单,包括您可能想要添加的任何信息 .
你的build.xml
你的java代码
内部版本号应与hudson等持续集成服务器相关联 . 为不同的分支/团队/分配使用不同的工作 .
为了在最终版本中保留版本号,我建议只使用maven构建系统 . 它将创建一个.properties文件,该文件存档到
META-INF/maven/<project group>/<project id>/pom.properties
上的最终.jar / .war / .whatever-ar中 . .properties文件将包含version属性 .由于我推荐maven,我建议您查看release plugin以在源存储库上准备发布并保持版本同步 .
软件:
SVN
Ant
哈德森,持续整合
svntask,一个查找SVN修订版的Ant任务:http://code.google.com/p/svntask/
Hudson有三个构建/工作:连续,每晚和发布 .
对于连续/每晚构建:构建号是使用svntask找到的SVN修订版 .
对于发布版本/作业:内部版本号是Ant读取的版本号,来自Properties文件 . 属性文件也可以与发布一起分发,以便在运行时显示内部版本号 .
Ant构建脚本将构建号放在构建期间创建的jar / war文件的清单文件中 . 适用于所有构建 .
发布版本的构建后操作,使用Hudson插件轻松完成:使用内部版本号标记SVN .
优点:
对于jar / war的开发版本,开发人员可以从jar / war中找到SVN修订版,并在SVN中查找相应的代码
对于版本,SVN版本是与其中包含版本号的SVN标记对应的版本 .
希望这可以帮助 .
我也在使用Hudson,虽然这是一个更简单的场景:
我的Ant脚本中有一个目标,如下所示:
每当我的工作运行时,Hudson都会为我设置这些环境变量 .
在我的例子中,这个项目是一个webapp,我将这个
build-number.txt
文件包含在webapp的根文件夹中 - 我真的不在乎谁看到它 .完成此操作后,我们不会标记源代码控制,因为我们已经设置了Hudson作业,以便在构建成功时使用内部版本号/时间戳标记它 .
我的解决方案只涵盖了开发的增量构建数量,我们在尚未涵盖发布数量的项目中还没有达到足够的水平 .
您可能还想在http://code.google.com/p/codebistro/wiki/BuildNumber找到一个jar中查看BuildNumber Maven插件和Ant任务 . 我试图让它变得简单明了 . 它是一个非常小的jar文件,只依赖于安装的命令行Subversion .
这就是我解决这个问题的方法:
将源复制到构建目录
然后应用anttask "versioninfo"
编译修改后的源代码
这是存储版本信息的java文件:
这是anttask“versioninfo”:
这是我的2美分:
我的构建脚本每次构建应用程序时都会创建一个构建号(带有时间戳!) . 这创造了太多的数字,但从来没有太少 . 如果我对代码进行了更改,则内部版本号将至少更改一次 .
我在每个版本中对版本号进行版本控制(尽管不在中间版本) . 当我更新项目并获得一个新的内部版本号(因为其他人发布了一个版本号)时,我会覆盖我的本地版本并重新开始 . 这可以导致较低的内部版本号,这就是我加入时间戳的原因 .
发生发布时,内部版本号将作为单个提交中的最后一项提交,并显示消息“build 1547” . 在那之后,当它正式发布时,整个树都被标记了 . 这样,构建文件始终具有所有标记,并且标记和构建号之间存在简单的1:1映射 .
[编辑]我使用我的项目部署了一个version.html然后,我可以使用一个刮刀来简单地收集一个准确的 Map ,安装在哪里 . 如果您正在使用Tomcat或类似的,请将内部版本号和时间戳放在web.xml的
description
元素中 . 请记住:当您可以让计算机为您完成时,切勿记住任何内容 .我们通过CruiseControl运行我们的构建(在这里插入您最喜欢的构建管理器),并执行主构建和测试 .
然后,我们使用Ant和BuildNumber递增版本号,并使用此信息以及构建日期和其他元数据创建属性文件 .
我们有一个专门阅读这个并将其提供给GUI /日志等的课程 .
然后,我们将所有这些打包并构建可部署的构建编号和相应的构建 . 我们所有的服务器都会在启动时转储此元信息 . 我们可以返回CruiseControl日志并将内部版本号绑定到日期和签入 .