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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/690419/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:58:46  来源:igfitidea点击:

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

javabuildbuild-processversioning

提问by andersoj

What are current best-practices for systematic build numbering and version number management in Java projects? Specifically:

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

  • How to manage build numbers systematically in a distributed development environment

  • How to maintain version numbers in source / available to the runtime application

  • How to properly integrate with source repository

  • How to more automatically manage version numbers vs. repository tags

  • How to integrate with continuous build infrastructure

  • 如何在分布式开发环境中系统地管理内部版本号

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

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

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

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

There are quite a number of tools available, and ant (the build system we're using) has a task that will maintain a build number, but it's not clear how to manage this with multiple, concurrent developers using CVS, svn, or similar.

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

[EDIT]

[编辑]

Several good and helpful partial or specific answers have appeared below, so I'll summarize a few of them. It sounds to me like there is not really a strong "best practice" on this, rather a collection of overlapping ideas. Below, find my summaries and some resulting questions that folks might try to answer as follow-ups. [New to stackoverflow... please provide comments if I'm doing this wrong.]

下面出现了几个好的和有用的部分或具体的答案,所以我将总结其中的一些。在我看来,这方面并没有真正强有力的“最佳实践”,而是一系列重叠的想法。在下面,找到我的总结和一些由此产生的问题,人们可能会在后续行动中尝试回答这些问题。[stackoverflow 的新手...如果我做错了,请提供评论。]

  • If you are using SVN, versioning of a specific checkout comes along for the ride. Build numbering can exploit this to create a unique build number that identifies the specific checkout/revision. [CVS, which we are using for legacy reasons, doesn't provide quite this level of insight... manual intervention with tags gets you part way there.]

  • If you are using maven as your build system, there is support for producing a version number from the SCM, as well as a release module for automatically producing releases. [We can't use maven, for a variety of reasons, but this helps those who can. [Thanks to marcelo-morales]]

  • If you are using antas your build system, the following task description can help produce a Java .properties file capturing build information, which can then be folded into your build in a number of ways. [We expanded on this idea to include hudson-derived information, thanks marty-lamb].

  • Ant and maven (and hudson and cruise control) provide easy means for getting build numbers into a .properties file, or into a .txt/.html file. Is this "safe" enough to keep it from being tampered with intentionally or accidentally? Is it better to compile it into a "versioning" class at build time?

  • Assertion: Build numbering should be defined/enacted in a continuous integration system like hudson. [Thanks to marcelo-morales] We have taken this suggestion, but it does crack open the release engineering question: How does a release happen? Are there multiple buildnumbers in a release? Is there a meaningful relationship between buildnumbers from differing releases?

  • Question: What is the objective behind a build number? Is it used for QA? How? Is it used primarily by developers to disambiguate between multiple builds during development, or more for QA to determine what build an end-user got? If the goal is reproducibility, in theory this is what a release version number should provide -- why doesn't it? (please answer this as a part of your answers below, it will help illuminate the choices you have made/suggested...)

  • Question: Is there a place for build numbers in manual builds? Is this so problematic that EVERYONE should be using a CI solution?

  • Question: Should build numbers be checked in to the SCM? If the goal is reliably and unambiguously identifying a particular build, how to cope with a variety of continuous or manual build systems that may crash/restart/etc...

  • Question: Should a build number be short and sweet (i.e., monotonically increasing integer) so that it's easy to stick into file names for archival, easy to refer to in communication, etc... or should it be long and full of usernames, datestamps, machine names, etc?

  • Question: Please provide details about how the assignment of build numbers fits into your larger automated release process. Yes, maven lovers, we know this is done and done, but not all of us have drunk the kool-aid quite yet...

  • 如果您使用的是 SVN,则需要对特定结帐进行版本控制。内部版本编号可以利用这一点来创建一个唯一的内部版本编号,用于标识特定的检出/修订版本。[我们出于遗留原因使用的 CVS 并没有提供这种级别的洞察力……使用标签进行手动干预可以帮助您实现这一目标。]

  • 如果您使用 maven 作为构建系统,则支持从 SCM 生成版本号,以及用于自动生成版本的发布模块。[由于各种原因,我们不能使用 maven,但这有助于那些可以使用的人。[感谢马塞洛-莫拉莱斯]]

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

  • Ant 和 maven(以及 hudson 和 Cruise Control)提供了将内部版本号放入 .properties 文件或 .txt/.html 文件的简单方法。这是否“安全”足以防止它被有意或无意地篡改?在构建时将其编译为“版本控制”类是否更好?

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

  • 问题:内部版本号背后的目标是什么?是否用于 QA?如何?它主要由开发人员用于在开发过程中消除多个构建之间的歧义,还是更多地用于 QA 以确定最终用户获得了什么构建?如果目标是可重复性,理论上这就是发布版本号应该提供的——为什么不呢?(请将此作为以下答案的一部分进行回答,这将有助于阐明您做出/建议的选择......)

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

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

  • 问题:内部版本号是否应该短小精悍(即单调递增的整数),以便轻松地将文件名粘贴到存档中,易于在通信中引用等……还是应该长而充满用户名,日期戳、机器名称等?

  • 问题:请提供有关内部版本号的分配如何适合更大的自动化发布流程的详细信息。是的,maven 爱好者,我们知道这已经完成了,但并不是我们所有人都喝过 kool-aid...

I'd really like to flesh this out into a complete answer, at least for the concrete example of our cvs/ant/hudson setup, so someone could build a complete strategy based on this question. I'll mark as "The Answer" anyone who can give a soup-to-nuts description for this particular case (including cvs tagging scheme, relevant CI config items, and release procedure that folds the build number into the release such that it's programmatically accessible.) If you want to ask/answer for another particular configuration (say, svn/maven/cruise control) I'll link to the question from here. --JA

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

[EDIT 23 Oct 09] I accepted the top-voted answer because I think it's a reasonable solution, while several of the other answers also include good ideas. If someone wants to take a crack at synthesizing some of these with marty-lamb's, I'll consider accepting a different one. The only concern I have with marty-lamb's is that it doesn't produce a reliably serialized build number -- it depends on a local clock at the builder's system to provide unambiguous build numbers, which isn't great.

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

[Edit Jul 10]

[编辑 7 月 10 日]

We now include a class like the below. This allows the version numbers to be compiled into the final executable. Different forms of the version info are emitted in logging data, long-term archived output products, and used to trace our (sometimes years-later) analysis of output products to a specific build.

我们现在包括一个如下所示的类。这允许将版本号编译成最终的可执行文件。不同形式的版本信息在日志数据、长期存档的输出产品中发出,并用于跟踪我们(有时是几年后)对特定构建的输出产品分析。

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

Leave comments if this deserves to become a wiki discussion.

如果这值得成为 wiki 讨论,请发表评论。

采纳答案by Marty Lamb

For several of my projects I capture the subversion revision number, time, user who ran the build, and some system information, stuff them into a .properties file that gets included in the application jar, and read that jar at runtime.

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

The ant code looks like this:

蚂蚁代码如下所示:

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

It's simple to extend this to include whatever information you might want to add.

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

回答by Brian Agnew

We run our build via CruiseControl (insert your favourite build manager here), and perform the main build and tests.

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

We then increment the version number using Ant and BuildNumberand create a property file with this info plus the date of build and other metadata.

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

We have a class dedicated to reading this and providing it to GUIs/logs etc.

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

We then package all of this up and build a deployable tying together the build number and the corresponding build. All our servers dump this meta info on start up. We can go back through the CruiseControl logs and tie the build number to the date and checkins.

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

回答by Aaron Digulla

Here is my 2 cents:

这是我的 2 美分:

  • My build script creates a build number (with timestamp!) each time I build the app. This creates too many numbers but never too few. If I have a change in the code, the build number will change at least once.

  • I version the build number with every release (though not inbetween). When I update the project and I get a new build number (because someone else did a release), I overwrite my local version and start over. This can lead to a lower build number which is why I've included the timestamp.

  • When a release happens, the build number is committed as the last item in a single commit with the message "build 1547". After that, when it's an official release, the whole tree is tagged. This way, the build file always has all tags and there is a simple 1:1 mapping between tags and build numbers.

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

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

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

[EDIT] I deploy a version.html with my projects and then, I can use a scraper to simply collect an accurate map what is installed where. If you're using Tomcat or similar, put the build number and timestamp in the descriptionelement of web.xml. Remember: Never memorize anything when you can have a computer do it for you.

[编辑] 我在我的项目中部署了一个 version.html,然后,我可以使用刮刀来简单地收集安装位置的准确地图。如果您使用 Tomcat 或类似产品,请将内部版本号和时间戳放在web.xmldescription元素中。记住:当您可以让计算机为您做任何事情时,永远不要记住任何事情。

回答by H Marcelo Morales

  • Build numbers should be associated with a continuous integration server like hudson. Use different jobs for different branches/teams/distributions.
  • To keep the version number in the final build, I would recommend just using mavenfor build system. It will create a .properties file archived into the final .jar/.war/.whatever-ar on META-INF/maven/<project group>/<project id>/pom.properties. The .properties file will contain the version property.
  • Since I am recommending maven, I would urge you to check out the release pluginto prepare the release on source repository and keep the versions on sync.
  • 内部版本号应该与像hudson这样的持续集成服务器相关联。为不同的分支/团队/分布使用不同的工作。
  • 为了在最终构建中保留版本号,我建议只使用maven作为构建系统。它将创建一个存档到最终 .jar/.war/.whatever-ar on 的 .properties 文件META-INF/maven/<project group>/<project id>/pom.properties。.properties 文件将包含版本属性。
  • 由于我推荐 maven,我建议您查看发布插件以准备源存储库上的发布并保持版本同步。

回答by Raleigh

Software:

软件:

  • SVN
  • Ant
  • Hudson, for continuous integration
  • svntask, an Ant task to find SVN revision: http://code.google.com/p/svntask/
  • SVN
  • 蚂蚁
  • Hudson,用于持续集成
  • svntask,一个寻找 SVN 版本的 Ant 任务:http://code.google.com/p/svntask/

Hudson has three builds/jobs: Continuous, Nightly and Release.

Hudson 有三个构建/作业:Continuous、Nightly 和 Release。

For a Continuous/Nightly build: Build number is the SVN revision, found using svntask.

对于连续/夜间构建:构建号是使用 svntask 找到的 SVN 修订版。

For a Release build/job: Build number is the Release number, read by Ant, from a Properties file. The properties file can also be distributed with the release for displaying the build number at runtime.

对于发布构建/作业:构建编号是由 Ant 从属性文件读取的发布编号。属性文件也可以随发行版一起分发,用于在运行时显示内部版本号。

The Ant build script puts the build number in the manifest file of jar/war files that are created during the build. Applies to all builds.

Ant 构建脚本将构建号放在构建期间创建的 jar/war 文件的清单文件中。适用于所有构建。

Post-build action for Release builds, done easily using a Hudson plug-in: tag SVN with the build number.

发布构建的构建后操作,使用 Hudson 插件轻松完成:使用构建号标记 SVN。

Benefits:

好处:

  • For a dev version of a jar/war, the developer can find the SVN revision from the jar/war and look up the corresponding code in SVN
  • For a release, the SVN revision is the one corresponding to the SVN tag that has the release number in it.
  • 对于jar/war 的dev 版本,开发者可以从jar/war 中找到SVN 版本,并在SVN 中查找对应的代码
  • 对于发行版,SVN 修订版是与其中包含发行版编号的 SVN 标签相对应的修订版。

Hope this helps.

希望这可以帮助。

回答by matt b

I'm using Hudson also, although a far more simpler scenario:

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

My Ant script has a target in it that looks like:

我的 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 sets these environment variables for me whenever my job runs.

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

In my case, this project is a webapp and I'm including this build-number.txtfile in the root folder of the webapp - I don't really care who sees it.

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

We don't tag source control when this is done because we already have our Hudson job set up to tag it with the build number/timestamp when the build is successful.

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

My solution only covers the incremental build numbers for development, we haven't gotten far enough in the project where we are covering release numbers yet.

我的解决方案仅涵盖用于开发的增量构建编号,我们在涵盖版本编号的项目中还远远不够。

回答by user146146

Your build.xml

你的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>
...

Your java code

你的Java代码

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

回答by Sasha O

You may also want to take a look at BuildNumber Maven plugin and Ant task in one jar found at http://code.google.com/p/codebistro/wiki/BuildNumber. I tried to make it simple and straightforward. It is a very small jar file that only depends on command line Subversion installed.

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

回答by flederohr

This is how i resolved this:

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

  • the sources are copied to the build directory
  • then the anttask "versioninfo" is applied
  • compile the modified sources
  • 源被复制到构建目录
  • 然后应用anttask“versioninfo”
  • 编译修改后的源代码

Here is the java file storing the version info:

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

public class Settings {

    public static final String VERSION = "$VERSION$";
    public static final String DATE = "$DATE$";

}

And here is the anttask "versioninfo":

这是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 "
                       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>