Java项目的构建和版本编号(ant,cvs,hudson)

问题

Java项目中系统构建编号和版本号管理的当前最佳实践是什么?特别:

  • 如何在分布式开发环境中系统地管理构建号
  • 如何在源/可用于运行时应用程序中维护版本号
  • 如何与源存储库正确集成
  • 如何更自动地管理版本号与存储库标签
  • 如何与持续构建基础架构集成

有很多工具可用,而ant(我们正在使用的构建系统)有一个任务可以维护构建号,但是不清楚如何使用CVS,svn或类似的多个并发开发人员来管理它。

[编辑]

下面出现了几个好的和有用的部分或具体的答案,所以我将总结其中的一些。听起来对我来说就是没有真正强大的"最佳实践",而是一系列重叠的想法。下面,找到我的摘要和一些人们可能会尝试回答的结果问题。 [新的stackoverflow ...如果我做错了,请提供评论。]

  • 如果你使用的是SVN,则需要对特定结账进行版本控制。构建编号可以利用它来创建标识特定结帐/修订的唯一构建号。 [我们因遗留原因而使用的CVS并没有提供这种程度的洞察力......使用标签进行人工干预可以帮助你实现目标。]
  • 如果你使用maven作为构建系统,则支持从SCM生成版本号,以及用于自动生成版本的发布模块。 [出于各种原因,我们不能使用maven,但这有助于那些能够做到的人。 [感谢marcelo-morales]]
  • 如果你使用ant作为构建系统,则以下任务描述可以帮助生成捕获构建信息的Java .properties文件,然后可以通过多种方式将其折叠到构建中。 [我们扩展了这个想法,包括哈德逊派生的信息,感谢marty-lamb]。
  • Ant和maven(以及哈德森和巡航控制)提供了将构建号码放入.properties文件或.txt / .html文件的简便方法。这种"安全"是否足以防止它被故意或意外地篡改?在构建时将它编译成"版本化"类是否更好?
  • 断言:应该在像哈德森这样的持续集成系统中定义/制定构建编号。 [感谢marcelo-morales]我们已经采纳了这个建议,但它确实破解了发布工程问题:发布如何发生?发布中是否有多个buildnumbers?来自不同版本的构建数量之间是否存在有意义的关系?
  • 问题:内部版本号背后的目标是什么?它用于质量保证吗?怎么样?是否主要由开发人员用于在开发期间消除多个构建之间的歧义,或更多用于QA以确定最终用户构建的内容?如果目标是可重复性,理论上这就是发布版本号应该提供的 - 为什么不呢? (请在下面作为你的答案的一部分回答这个问题,它将有助于说明你做出的建议/建议...)
  • 问题:手动构建中是否有构建号码的位置?这是如此有问题,以至于每个人都应该使用CI解决方案吗?
  • 问题:是否应该将建立的号码签入SCM?如果目标是可靠且明确地识别特定构建,如何应对可能崩溃/重启/等的各种连续或手动构建系统......
  • 问题:构建号码是否应该简短且甜蜜(即单调递增整数),以便易于存档到文件名中,易于在通信中引用等...或者它应该是长而且充满用户名,日期戳,机器名等?
  • 问题:请提供有关构建号分配如何适合你的大型自动发布过程的详细信息。是的,maven爱好者,我们知道这已经完成了,但并非我们所有人都喝醉了kool-aid ......

我真的想把这个充实到一个完整的答案,至少对于我们的cvs / ant / hudson设置的具体例子,所以有人可以根据这个问题构建一个完整的策略。我将标记为"答案"的任何人都可以为这个特定情况提供一个汤到坚果的描述(包括cvs标记方案,相关的CI配置项和释放过程,将构建号折叠到发布中,以便以编程方式如果你想询问/回答另一个特定的配置(例如,svn / maven /巡航控制),我将链接到这里的问题。 --JA

[编辑09年10月23日]我接受了最高投票的答案,因为我认为这是一个合理的解决方案,而其他几个答案也包括好主意。如果有人想要用marty-lamb来合成其中一些,我会考虑接受另一个。我对marty-lamb的唯一顾虑是它不会产生可靠的序列化构建number - 它取决于构建器系统的本地时钟,以提供明确的构建号,这不是很好。

[编辑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();
  }
}

如果这值得成为维基讨论,请留下评论。


#1 热门回答(63 赞)

对于我的几个项目,我捕获了subversion版本号,时间,运行构建的用户以及一些系统信息,将它们填充到包含在应用程序jar中的.properties文件中,并在运行时读取该jar。

蚂蚁代码如下所示:

<!-- software revision number -->
<property name="version" value="1.23"/>

<target name="buildinfo">
    <tstamp>
        <format property="builtat" pattern="MM/dd/yyyy hh:mm aa" timezone="America/New_York"/>
    </tstamp>        
    <exec executable="svnversion" outputproperty="svnversion"/>
    <exec executable="whoami" outputproperty="whoami"/>
    <exec executable="uname" outputproperty="buildsystem"><arg value="-a"/></exec>

    <propertyfile file="path/to/project.properties"
        comment="This file is automatically generated - DO NOT EDIT">        
        <entry key="buildtime" value="${builtat}"/>
        <entry key="build" value="${svnversion}"/>
        <entry key="builder" value="${whoami}"/>
        <entry key="version" value="${version}"/>
        <entry key="system" value="${buildsystem}"/>
    </propertyfile>
</target>

扩展它很简单,包括你可能想要添加的任何信息。


#2 热门回答(46 赞)

Yourbuild.xml

...
<property name="version" value="1.0"/>
...
<target name="jar" depends="compile">
    <buildnumber file="build.num"/>
    <manifest file="MANIFEST.MF">
        ...
        <attribute name="Main-Class" value="MyClass"/>
        <attribute name="Implementation-Version" value="${version}.${build.number}"/>
        ...
    </manifest>
</target>
...

你的java代码

String ver = MyClass.class.getPackage().getImplementationVersion();

#3 热门回答(6 赞)

  • 构建号应该与像哈德森这样的持续集成服务器相关联。为不同的分支/团队/分配使用不同的工作。
  • 为了在最终版本中保留版本号,我建议只使用maven进行构建系统。它将创建一个.properties文件,该文件存档到META-INF / maven / <项目组> / <项目ID> /pom.properties上的最终.jar / .war / .whatever-ar中。 .properties文件将包含version属性。
  • 由于我推荐maven,我建议你查看发布插件,以便在源存储库上准备发布并保持版本同步。