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

在 Java 项目中,系统构建编号和版本号管理的当前最佳实践是什么?具体来说:

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

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

    [编辑]

    下面出现了几个好的和有用的部分或具体的答案,所以我将总结其中的一些。在我看来,这方面并没有真正强有力的“最佳实践”,而是一系列重叠的想法。在下面,找到我的总结和一些由此产生的问题,人们可能会在后续行动中尝试回答这些问题。 [stackoverflow 的新手...如果我做错了,请提供评论。]
  • 如果您使用的是 SVN,则需要对特定结帐进行版本控制。内部版本编号可以利用这一点来创建一个唯一的内部版本编号,用于标识特定的 check out /修订版本。 [出于遗留原因,我们使用 CVS 并没有提供这种级别的洞察力……使用标签进行手动干预可以让您在此过程中有所作为。]
  • 如果您使用 maven 作为构建系统,则支持从 SCM 生成版本号,以及用于自动生成版本的发布模块。 [由于各种原因,我们不能使用 maven,但这有助于那些可以使用的人。 [感谢 marcelo-morales ]]
  • 如果您正在使用 ant作为您的构建系统,以下任务描述可以帮助生成捕获构建信息的 Java .properties 文件,然后可以通过多种方式将其折叠到您的构建中。 [我们扩展了这个想法以包含来自 hudson 的信息,谢谢 marty-lamb ]。
  • Ant 和 maven(以及 hudson 和 Cruise Control)提供了将内部版本号放入 .properties 文件或 .txt/.html 文件的简单方法。这是否“安全”足以防止它被有意或无意地篡改?在构建时将其编译为“版本控制”类是否更好?
  • 断言:构建编号应该在持续集成系统中定义/制定,比如 hudson . [感谢 marcelo-morales ] 我们采纳了这个建议,但它确实解决了发布工程问题:发布是如何发生的?一个版本中是否有多个内部版本号?不同版本的内部版本号之间是否存在有意义的关系?
  • 问题:内部版本号背后的目标是什么?是否用于 QA?怎么样?它主要由开发人员用于在开发过程中消除多个构建之间的歧义,还是更多地用于 QA 以确定最终用户获得了什么构建?如果目标是可重复性,理论上这就是发布版本号应该提供的——为什么不呢? (请将此作为以下答案的一部分进行回答,这将有助于阐明您做出/建议的选择...)
  • 问题:在手动构建中是否有构建编号的位置?这是否有问题以至于每个人都应该使用 CI 解决方案?
  • 问题:是否应该将内部版本号 checkin SCM?如果目标是可靠且明确地识别特定构建,如何应对可能崩溃/重启/等的各种连续或手动构建系统...
  • 问题:内部版本号是否应该短小精悍(即单调递增的整数),以便轻松地将文件名粘贴到存档中,易于在通信中引用等……还是应该长而充满用户名,日期戳、机器名称等?
  • 问题:请提供有关内部版本号的分配如何适合更大的自动化发布流程的详细信息。是的,maven 爱好者,我们知道这已经完成了,但并不是我们所有人都喝过 kool-aid...

  • 我真的很想将其充实为一个完整的答案,至少对于我们的 cvs/ant/hudson 设置的具体示例,因此有人可以基于此问题构建完整的策略。我将标记为“答案”的任何人都可以对这种特殊情况进行全面的描述(包括 cvs 标记方案、相关的 CI 配置项以及将内部版本号折叠到版本中的发布程序,以便以编程方式可访问。)如果您想询问/回答其他特定配置(例如 svn/maven/cruise control),我将从这里链接到该问题。 --JA

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

    [编辑 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 讨论,请发表评论。

    最佳答案

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

    Ant 代码如下所示:

    <!-- 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>
    

    扩展它以包含您可能想要添加的任何信息很简单。

    https://stackoverflow.com/questions/690419/

    相关文章:

    java - 如何在 Maven 中读取外部属性文件

    configuration - 在 Xcode 中添加构建配置

    android - 错误 :Execution failed for task ':app:comp

    iphone - 重新签署 IPA (iPhone)

    java - 构建与编译 (Java)

    build - gmake 和 make 有什么区别?

    Xcode - 但是......我们的文件在哪里?

    visual-studio - 发生代码更改时,Visual Studio 2010 不会在运行前构

    java - 使用 gradle 的多项目测试依赖项

    javascript - `npm build` 没有在 package.json 中构建 't r