繁体   English   中英

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

[英]Build and Version Numbering for Java Projects (ant, cvs, hudson)

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

  • 如何在分布式开发环境中系统地管理构建号

  • 如何在源/可用于运行时应用程序中维护版本号

  • 如何与源存储库正确集成

  • 如何更自动地管理版本号与存储库标签

  • 如何与持续构建基础架构集成

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

[编辑]

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

  • 如果您使用的是SVN,则可以使用特定结帐的版本控制。 构建编号可以利用它来创建标识特定结帐/修订的唯一构建号。 [我们因遗留原因而使用的CVS并没有提供这种程度的洞察力......使用标签进行人工干预可以帮助您实现目标。]

  • 如果您使用maven作为构建系统,则支持从SCM生成版本号,以及用于自动生成版本的发布模块。 [出于各种原因,我们不能使用maven,但这有助于那些能够做到的人。 [感谢marcelo-morales ]]

  • 如果您使用ant作为构建系统,则以下任务描述可以帮助生成捕获构建信息的Java .properties文件,然后可以通过多种方式将其折叠到构建中。 [我们扩展了这个想法,包括哈德逊派生的信息,感谢marty-lamb ]。

  • Ant和maven(以及hudson和巡航控制)提供了将构建号放入.properties文件或.txt / .html文件的简便方法。 这种“安全”是否足以防止它被故意或意外地篡改? 在构建时将它编译成“版本化”类是否更好?

  • 断言:应该在像哈德森这样的持续集成系统中定义/制定构建编号。 [感谢marcelo-morales ]我们已经采纳了这个建议,但它确实破解了发布工程问题:发布是如何发生的? 发布中是否有多个buildnumbers? 来自不同版本的构建数量之间是否存在有意义的关系?

  • 问题:内部版本编号背后的目标是什么? 它用于QA吗? 怎么样? 它是否主要由开发人员用于在开发期间消除多个构建之间的歧义,或更多用于QA以确定最终用户构建的内容? 如果目标是可重复性,理论上这就是发布版本号应该提供的 - 为什么不呢? (请在下面作为你的答案的一部分回答这个问题,它将有助于说明你做出的建议/建议...)

  • 问题:手动构建中是否存在构建号码的位置? 这是有问题的,以至于每个人都应该使用CI解决方案吗?

  • 问题:是否应将建立号码签入SCM? 如果目标是可靠且明确地识别特定构建,那么如何应对可能崩溃/重启等的各种连续或手动构建系统......

  • 问题:构建号码是否应该简短且甜蜜(即单调增加整数),以便易于存档到文件名中,易于在通信中引用等等......或者它应该是多长的用户名,日期戳,机器名等?

  • 问题:请提供有关如何将构建号分配适合您的大型自动发布流程的详细信息。 是的,maven爱好者,我们知道这已经完成了,但并非我们所有人都喝醉了kool-aid ......

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

[编辑09年10月23日]我接受了最高投票的答案,因为我认为这是一个合理的解决方案,而其他几个答案也包括好主意。 如果有人想要用marty-lamb 's合成其中一些,我会考虑接受另一个。 我对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();
  }
}

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

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

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

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

软件:

  • SVN
  • 蚂蚁
  • 哈德森,持续整合
  • 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脚本中有一个目标,如下所示:

<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作业设置为在构建成功时使用内部版本号/时间戳标记它。

我的解决方案仅涵盖开发的增量构建数量,我们还没有在我们覆盖版本号的项目中得到足够的。

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

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

  • 源被复制到构建目录
  • 然后应用反任务“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>

这是我的2美分:

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM