首页 文章

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

提问于
浏览
131

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 回答

  • 63

    对于我的几个项目,我捕获了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>
    

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

  • 3

    你的build.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();
    
  • 46
    • 内部版本号应与hudson等持续集成服务器相关联 . 为不同的分支/团队/分配使用不同的工作 .

    • 为了在最终版本中保留版本号,我建议只使用maven构建系统 . 它将创建一个.properties文件,该文件存档到 META-INF/maven/<project group>/<project id>/pom.properties 上的最终.jar / .war / .whatever-ar中 . .properties文件将包含version属性 .

    • 由于我推荐maven,我建议您查看release plugin以在源存储库上准备发布并保持版本同步 .

  • 3

    软件:

    • 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标记对应的版本 .

    希望这可以帮助 .

  • 1

    我也在使用Hudson,虽然这是一个更简单的场景:

    我的Ant脚本中有一个目标,如下所示:

    <target name="build-number">
        <property environment="env" />
        <echo append="false" file="${build.dir}/build-number.txt">Build: ${env.BUILD_TAG}, Id: ${env.BUILD_ID}, URL: ${env.HUDSON_URL}</echo>
    </target>
    

    每当我的工作运行时,Hudson都会为我设置这些环境变量 .

    在我的例子中,这个项目是一个webapp,我将这个 build-number.txt 文件包含在webapp的根文件夹中 - 我真的不在乎谁看到它 .

    完成此操作后,我们不会标记源代码控制,因为我们已经设置了Hudson作业,以便在构建成功时使用内部版本号/时间戳标记它 .

    我的解决方案只涵盖了开发的增量构建数量,我们在尚未涵盖发布数量的项目中还没有达到足够的水平 .

  • 6

    您可能还想在http://code.google.com/p/codebistro/wiki/BuildNumber找到一个jar中查看BuildNumber Maven插件和Ant任务 . 我试图让它变得简单明了 . 它是一个非常小的jar文件,只依赖于安装的命令行Subversion .

  • 2

    这就是我解决这个问题的方法:

    • 将源复制到构建目录

    • 然后应用anttask "versioninfo"

    • 编译修改后的源代码

    这是存储版本信息的java文件:

    public class Settings {
    
        public static final String VERSION = "$VERSION$";
        public static final String DATE = "$DATE$";
    
    }
    

    这是anttask“versioninfo”:

    <!-- ================================= 
         target: versioninfo              
         ================================= -->
        <target name="versioninfo"
                depends="init"
                description="gets version info from svn"
        >
    
            <!-- 
            get svn info from the src folder 
            -->
            <typedef resource="org/tigris/subversion/svnant/svnantlib.xml"
                     classpathref="ant.classpath"
            />
            <svnSetting id="svn.setting"
                        javahl="false"
                        svnkit="true"
                        dateformatter="dd.MM.yyyy"
            />
            <svn refid="svn.setting">
                <info target="src" />
            </svn>
    
            <!-- 
            if repository is a taged version use "v <tagname>"
            else "rev <revisionnumber> (SVN)" as versionnumber
             -->
            <taskdef resource="net/sf/antcontrib/antcontrib.properties"
                     classpathref="ant.classpath"
            />
            <propertyregex property="version"
                           input="${svn.info.url}"
                           regexp=".*/tags/(.*)/${ant.project.name}/src"
                           select="v \1"
                           defaultvalue="rev ${svn.info.lastRev} (SVN)"
                           override="true"
            />
    
    
            <!-- 
            replace date and version in the versionfile ()
             -->
            <replace file="build/${versionfile}">
                <replacefilter token="$DATE$" value="${svn.info.lastDate}" />
                <replacefilter token="$VERSION$" value="${version}" />
            </replace>
    
        </target>
    
  • 4

    这是我的2美分:

    • 我的构建脚本每次构建应用程序时都会创建一个构建号(带有时间戳!) . 这创造了太多的数字,但从来没有太少 . 如果我对代码进行了更改,则内部版本号将至少更改一次 .

    • 我在每个版本中对版本号进行版本控制(尽管不在中间版本) . 当我更新项目并获得一个新的内部版本号(因为其他人发布了一个版本号)时,我会覆盖我的本地版本并重新开始 . 这可以导致较低的内部版本号,这就是我加入时间戳的原因 .

    • 发生发布时,内部版本号将作为单个提交中的最后一项提交,并显示消息“build 1547” . 在那之后,当它正式发布时,整个树都被标记了 . 这样,构建文件始终具有所有标记,并且标记和构建号之间存在简单的1:1映射 .

    [编辑]我使用我的项目部署了一个version.html然后,我可以使用一个刮刀来简单地收集一个准确的 Map ,安装在哪里 . 如果您正在使用Tomcat或类似的,请将内部版本号和时间戳放在web.xmldescription 元素中 . 请记住:当您可以让计算机为您完成时,切勿记住任何内容 .

  • 6

    我们通过CruiseControl运行我们的构建(在这里插入您最喜欢的构建管理器),并执行主构建和测试 .

    然后,我们使用Ant和BuildNumber递增版本号,并使用此信息以及构建日期和其他元数据创建属性文件 .

    我们有一个专门阅读这个并将其提供给GUI /日志等的课程 .

    然后,我们将所有这些打包并构建可部署的构建编号和相应的构建 . 我们所有的服务器都会在启动时转储此元信息 . 我们可以返回CruiseControl日志并将内部版本号绑定到日期和签入 .

相关问题