草庐IT

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

coder 2023-04-23 原文

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

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

    关于java - Java 项目的构建和版本编号(ant、cvs、hudson),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/690419/

    有关java - Java 项目的构建和版本编号(ant、cvs、hudson)的更多相关文章

    1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

      如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

    2. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

      我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

    3. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

      我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

    4. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    5. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

      我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

    6. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

      如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

    7. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

      我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

    8. Ruby 从大范围中获取第 n 个项目 - 2

      假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

    9. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    10. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    随机推荐