Java项目中系统内部版本号和版本号管理的当前最佳实践是什么?特别:
如何在分布式开发环境中系统地管理内部版本号
如何在源代码中维护版本号/可用于运行时应用程序
如何与源存储库正确集成
如何更自动地管理版本号和存储库标签
如何与持续构建基础架构集成
有很多可用的工具,而ant(我们正在使用的构建系统)的任务将维护一个构建号,但是目前尚不清楚如何使用CVS,svn或类似的工具与多个并发开发人员一起管理该数目。 。
[编辑]
下面出现了几个很好的有用的部分或特定答案,因此我将总结其中的一些。在我看来,这并不是真正的“最佳实践”,而是一些相互重叠的想法。在下面,找到我的摘要以及随之而来的人们可能会尝试回答的一些问题。[stackoverflow的新功能...如果我做错了,请提供评论。]
如果您使用的是SVN,则特定的结帐版本会随之增加。内部编号可以利用此编号来创建唯一的内部编号,以标识特定的结帐/修订。[出于遗留原因,我们使用的CVS并不能提供如此深入的了解...使用标签进行手动干预会使您有所作为。]
如果将maven用作构建系统,则支持从SCM生成版本号,以及用于自动生成发行版的发行版模块。[出于多种原因,我们不能使用Maven,但这可以帮助那些可以使用Maven的人。[感谢marcelo-morales ]]
如果您使用ant作为构建系统,则以下任务描述可以帮助生成捕获构建信息的Java .properties文件,然后可以通过多种方式将其折叠到构建中。[感谢marty-lamb,我们将这个想法扩展到包括来自哈德逊的信息]。
Ant和Maven(以及Hudson和Cruise Control)提供了将内部版本号获取到.properties文件或.txt / .html文件的简便方法。这个“安全”足以防止它被有意或无意地篡改吗?在构建时将其编译为“版本”类更好吗?
断言:应在诸如hudson的连续集成系统中定义/制定内部版本号。[感谢marcelo-morales ]我们采纳了这个建议,但是它确实解决了发布工程问题:发布是如何发生的?版本中是否有多个内部版本号?不同发行版的内部版本号之间是否存在有意义的关系?
问题:内部编号背后的目标是什么?是否用于质量检查?怎么样?是开发人员主要使用它在开发过程中消除多个版本之间的歧义,还是让质量检查人员确定最终用户获得了什么版本?如果目标是可复制性,那么从理论上讲,这就是发行版本号应提供的内容-为什么不提供?(请在下面的回答中作为一部分回答,这将有助于阐明您所做的/建议的选择...)
问题:手动构建中是否存在内部版本号?这是否有问题,以至每个人都应该使用CI解决方案?
问题:是否应将内部版本号签入SCM?如果目标是可靠,明确地确定特定的构建,则如何应对可能会崩溃/重新启动等的各种连续或手动构建系统。
问题:内部版本号短而甜美(即,单调递增的整数),以便它易于粘贴到文件名中进行存档,易于在通信中引用等……还是应该长而又充满用户名,日期戳,机器名称等?
问题:请提供有关内部编号分配如何适合较大的自动发布过程的详细信息。是的,Maven爱好者,我们知道这已经完成了,但是并不是我们所有人都已经喝了苦艾酒。
我真的很想将其充实为一个完整的答案,至少对于我们的cvs / ant / hudson设置的具体示例而言,这样,有人可以根据此问题制定出完整的策略。我将标记为“答案”的任何人都可以为此特定情况提供详尽的描述(包括cvs标记方案,相关的CI配置项以及将内部版本号折叠到版本中的发布过程,以便以编程方式进行如果您要询问/回答其他特定配置(例如svn / maven / cruise控件),我将在此处链接到该问题。--JA
[编辑2009年10月23日]我接受了投票最多的答案,因为我认为这是一个合理的解决方案,而其他几个答案也都包含了好主意。如果有人想破解使用marty-lamb合成其中的某些内容,我将考虑接受另一种。我对marty-lamb的唯一担忧是,它不会生成可靠的序列化内部版本号-它依赖于构建者系统上的本地时钟来提供明确的内部版本号,这并不好。
[编辑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();
}
}
如果这值得成为Wiki讨论,请发表评论。
gradle
和/或时有类似的简单方法git
?