Нумерация сборок и версий для проектов Java (ant, cvs, hudson)

Каковы современные рекомендации по систематической нумерации сборок и управлению номерами версий в проектах Java? В частности:

  • Как систематически управлять номерами сборки в распределенной среде разработки

  • Как сохранить номера версий в исходном / доступном для приложения времени выполнения

  • Как правильно интегрироваться с исходным хранилищем

  • Как более автоматически управлять номерами версий по сравнению с тегами репозитория

  • Как интегрироваться с инфраструктурой непрерывного построения

Существует довольно много доступных инструментов, и у ant (используемой нами системы сборки) есть задача, которая будет поддерживать номер сборки, но неясно, как управлять этим с несколькими параллельными разработчиками, использующими CVS, SVN или аналогичные. ,

[РЕДАКТИРОВАТЬ]

Ниже приведены несколько хороших и полезных частичных или конкретных ответов, поэтому я кратко изложу некоторые из них. Для меня это звучит так, будто на самом деле нет сильной «лучшей практики», скорее, это набор совпадающих идей. Ниже, найдите мои резюме и некоторые итоговые вопросы, на которые люди могут попытаться ответить как продолжение. [Новое в stackoverflow ... пожалуйста, предоставьте комментарии, если я делаю это неправильно.]

  • Если вы используете SVN, версия проверки конкретной проверки приходит вместе с поездкой. Нумерация сборок может использовать это для создания уникального номера сборки, который идентифицирует конкретную проверку / ревизию. [CVS, который мы используем по унаследованным причинам, не обеспечивает такого уровня понимания ... ручное вмешательство с тегами поможет вам в этом.]

  • Если вы используете maven в качестве системы сборки, есть поддержка для создания номера версии из SCM, а также модуль выпуска для автоматического создания выпусков. [Мы не можем использовать Maven по разным причинам, но это помогает тем, кто может. [Благодаря Марсело-Моралесу ]]

  • Если вы используете ant в качестве своей системы сборки, следующее описание задачи может помочь создать файл .properties Java, содержащий информацию о сборке, которая затем может быть встроена в вашу сборку несколькими способами. [Мы расширили эту идею, чтобы включить информацию, полученную из Хадсона, спасибо marty-lamb ].

  • Ant и maven (и hudson и круиз-контроль) предоставляют простые средства для получения номеров сборок в файл .properties или в файл .txt / .html. Достаточно ли это «безопасно», чтобы предотвратить его умышленное или случайное вмешательство? Лучше ли скомпилировать его в класс управления версиями во время сборки?

  • Утверждение: нумерация сборки должна быть определена / принята в системе непрерывной интеграции, такой как Hudson . [Благодаря marcelo-morales ] Мы приняли это предложение, но оно действительно раскрывает вопрос разработки релиза: как происходит релиз? Есть ли в релизе несколько сборочных номеров? Есть ли значимые отношения между номерами сборки из разных версий?

  • Вопрос: Какая цель стоит за номером сборки? Это используется для QA? Как? Используется ли он главным образом разработчиками для устранения неоднозначности между несколькими сборками во время разработки или для QA, чтобы определить, какую сборку получил конечный пользователь? Если целью является воспроизводимость, теоретически это то, что должен предоставить номер версии релиза - почему бы и нет? (ответьте, пожалуйста, как часть ваших ответов ниже, это поможет осветить выбор, который вы сделали / предложили ...)

  • Вопрос: есть ли место для номеров сборок в сборках вручную? Это так проблематично, что КАЖДЫЙ должен использовать решение CI?

  • Вопрос: Должны ли номера сборок регистрироваться в SCM? Если целью является надежное и однозначное определение конкретной сборки, как справиться с множеством систем непрерывной или ручной сборки, которые могут привести к сбою / перезапуску и т. Д.

  • Вопрос: Должен ли номер сборки быть коротким и приятным (т. Е. Монотонно увеличивающимся целым числом), чтобы его можно было легко вводить в имена файлов для архивирования, легко ссылаться при общении и т. Д. ... или он должен быть длинным и полным имен пользователей даты, названия машин и т.д?

  • Вопрос: Пожалуйста, предоставьте подробную информацию о том, как назначение номеров сборки вписывается в ваш более крупный процесс автоматического выпуска. Да, любители мавена, мы знаем, что это сделано и сделано, но не все из нас уже выпили kool-aid ...

Я действительно хотел бы изложить это в полном ответе, по крайней мере, для конкретного примера нашей установки cvs / ant / hudson, чтобы кто-то мог построить полную стратегию на основе этого вопроса. Я отмечу как «Ответ» любого, кто может дать подробное описание для этого конкретного случая (включая схему тегирования cvs, соответствующие элементы конфигурации CI и процедуру выпуска, которая сворачивает номер сборки в выпуск таким образом, чтобы он программно доступно.) Если вы хотите спросить / ответить для другой конкретной конфигурации (скажем, svn / maven / круиз-контроль), я сошлюсь на вопрос здесь. --JA

[РЕДАКТИРОВАТЬ 23 октября 09] Я принял ответ с наибольшим количеством голосов, потому что я думаю, что это разумное решение, в то время как некоторые другие ответы также включают хорошие идеи. Если кто-то захочет попробовать синтезировать некоторые из них с мучениками , я подумаю о принятии другого. Единственное беспокойство, которое я испытываю к marty-lamb's, заключается в том, что он не производит надежно сериализованный номер сборки - он зависит от локальных часов в системе сборщика, чтобы обеспечить однозначные номера сборки, что не очень хорошо.

[Изменить 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();
  }
}

Оставьте комментарии, если это заслуживает того, чтобы стать вики-обсуждением.

27.03.2009 16:18:51
Для будущих читателей обратите внимание, что номер ревизии в предложенном вами коде относится к файлу, а не к глобальной ревизии репозитория. Для получения дополнительной информации см .: subversion.apache.org/faq.html#version-value-in-source
maayank 16.01.2011 16:12:46
Мне интересно, есть ли у кого подобные простые подходы при использовании gradleи / или git?
bnjmn 9.01.2014 19:48:13
9 ОТВЕТОВ
РЕШЕНИЕ

Для нескольких моих проектов я записываю номер ревизии subversion, время, пользователя, который запускал сборку, и некоторую системную информацию, помещаю их в файл .properties, который включается в jar приложения, и читаю этот jar во время выполнения.

Код муравья выглядит так:

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

Это просто расширить, чтобы включить любую информацию, которую вы можете добавить.

63
26.09.2012 15:10:41
Для кросс-платформенной версии используйте это вместо свойства whoami, указанного выше: <entry key = "builder" value = "$ {user.name}" />
Ed Brannin 21.05.2010 21:18:00
-1 за наличие зависимого от платформы решения. Хороший способ поместить все доступные свойства ant в файл: `<имя свойства =" antprops.file "location =" $ {build.temp.project.dir} /used_ant.properties "/> <echoproperties destfile =" $ {antprops.file} "/> <! - сортировать файл ТОЛЬКО для ant 1.7.0 и новее !!! -> <concat> <union> <sort> <tokens> <file file =" $ {antprops .file} "/> <linetokenizer includedelims =" true "/> </ tokens> </ sort> </ union> </ concat>`
raudi 5.03.2013 12:00:01
Вы совершаете снова после этого? Или это сделано до того, как вы внесете изменения?
Charles Wood 22.01.2014 16:34:41
Как это сделать для репозитория git?
TechCrunch 3.12.2014 18:29:09

Мы запускаем нашу сборку через CruiseControl (вставьте ваш любимый менеджер сборки здесь) и выполняем основную сборку и тесты.

Затем мы увеличиваем номер версии с помощью Ant и BuildNumber и создаем файл свойств с этой информацией, а также датой сборки и другими метаданными.

У нас есть класс, предназначенный для чтения этого и предоставления его GUI / журналам и т. Д.

Затем мы упаковываем все это и собираем развертываемый пакет, связывающий номер сборки и соответствующую сборку. Все наши серверы выдают эту мета-информацию при запуске. Мы можем вернуться к журналам CruiseControl и привязать номер сборки к дате и регистрации.

1
23.01.2011 01:31:49
Совет: Если вы используете CC и BuildNumber от Ant, вы можете использовать PropertyFileLabelIncrementer, чтобы синхронизировать метки CC с номером сборки.
Jeffrey Fredrick 10.04.2009 01:21:02

Вот мои 2 цента:

  • Мой скрипт сборки создает номер сборки (с отметкой времени!) Каждый раз, когда я собираю приложение. Это создает слишком много чисел, но никогда не слишком мало. Если у меня есть изменения в коде, номер сборки изменится хотя бы один раз.

  • Я версии номер сборки с каждым выпуском (хотя и не промежуточный). Когда я обновляю проект и получаю новый номер сборки (потому что кто-то сделал релиз), я перезаписываю свою локальную версию и начинаю заново. Это может привести к меньшему номеру сборки, поэтому я включил метку времени.

  • Когда происходит выпуск, номер сборки фиксируется как последний элемент в одном коммите с сообщением «сборка 1547». После этого, когда это официальный релиз, все дерево помечается. Таким образом, в файле сборки всегда есть все теги, и между тегами и номерами сборки существует простое отображение 1: 1.

[РЕДАКТИРОВАТЬ] Я развертываю version.html со своими проектами, а затем я могу использовать скребок, чтобы просто собрать точную карту того, что и где установлено. Если вы используете Tomcat или аналогичный, укажите номер сборки и метку времени в descriptionэлементе web.xml . Помните: никогда не запоминайте что-либо, когда компьютер может сделать это за вас.

2
29.03.2009 14:34:20
Да ... это то, что у нас тоже. И это неудобно, когда нужно помнить, какая невероятно долгая сборка была развернута здесь или там. Слишком много цифр - тоже проблема ...
Varkhan 27.03.2009 16:51:20
@ Вархан: почему? Просто поместите версию где-нибудь, где скребок HTML может собрать ее для вас ...
Aaron Digulla 29.03.2009 14:35:12
  • Номера сборки должны быть связаны с сервером непрерывной интеграции, таким как Hudson . Используйте разные задания для разных филиалов / команд / дистрибутивов.
  • Чтобы сохранить номер версии в окончательной сборке, я бы порекомендовал просто использовать maven для системы сборки. Он создаст файл .properties, заархивированный в финальную версию .jar / .war / .whwhat-ar on META-INF/maven/<project group>/<project id>/pom.properties. Файл .properties будет содержать свойство version.
  • Поскольку я рекомендую Maven, я настоятельно рекомендую вам проверить плагин релиза, чтобы подготовить релиз в исходном хранилище и синхронизировать версии.
6
27.03.2009 16:51:43

Програмное обеспечение:

  • SVN
  • Муравей
  • Гудзон, для непрерывной интеграции
  • svntask, задача Ant для поиска версии SVN: http://code.google.com/p/svntask/

У Хадсона есть три билда / задания: Непрерывный, Ночной и Выпуск.

Для непрерывной / ночной сборки: номер сборки - это ревизия SVN, найденная с помощью svntask.

Для сборки / задания по выпуску: Номер сборки - это номер выпуска, читаемый Ant из файла свойств. Файл свойств также может распространяться вместе с выпуском для отображения номера сборки во время выполнения.

Сценарий сборки Ant помещает номер сборки в файл манифеста файлов jar / war, которые создаются во время сборки. Относится ко всем билдам.

Действие после сборки для сборок Release, легко выполняемое с помощью плагина Hudson: тег SVN с номером сборки.

Льготы:

  • Для dev-версии jar / war разработчик может найти SVN-версию из jar / war и найти соответствующий код в SVN.
  • Для выпуска версия SVN - это версия, соответствующая тегу SVN, в котором указан номер выпуска.

Надеюсь это поможет.

6
29.03.2009 13:45:06
Есть ли у вас Release build / job проверить файл свойств номера сборки / выпуска обратно в SVN?
matt b 3.04.2009 18:59:06
Для непрерывных сборок номер сборки - это номер редакции SVN. Так что нечего регистрировать. Для сборок релиза это проверенный файл, который используется для получения номера сборки. Как правило, инженер по выпуску или кто-либо еще хочет обновить этот файл задолго до выпуска.
Raleigh 14.05.2009 16:47:20

Я также использую 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>

Хадсон устанавливает эти переменные среды для меня всякий раз, когда выполняется моя работа.

В моем случае этот проект является веб-приложением, и я включаю этот build-number.txtфайл в корневую папку веб-приложения - мне все равно, кто его видит.

Мы не помечаем управление исходным кодом, когда это сделано, потому что у нас уже настроено задание Hudson, чтобы пометить его номером сборки / отметкой времени, когда сборка будет успешной.

Мое решение распространяется только на добавочные номера сборки для разработки, мы еще не продвинулись достаточно далеко в проекте, где мы пока рассматриваем номера версий.

4
30.03.2009 18:29:07
дополнительно (по крайней мере, в текущих версиях jenkins) вы можете проверить свойства: `env.SVN_URL_1 env.SVN_REVISION_1 env.SVN_URL_2 env.SVN_REVISION_2 и т. д.
raudi 5.03.2013 13:22:01

Ваш 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
18.02.2016 12:22:36
+1 за использование свойства, которое Java уже поддерживает с таким методом.
Ed Brannin 21.05.2010 21:33:22
-1 за наличие номера сборки в build.xml, а не в отдельном файле свойств
raudi 5.03.2013 12:01:07

Вы также можете взглянуть на плагин BuildNumber Maven и задачу Ant в одном банке, который можно найти по адресу http://code.google.com/p/codebistro/wiki/BuildNumber . Я пытался сделать это простым и понятным. Это очень маленький JAR-файл, который зависит только от установленной командной строки Subversion.

3
22.01.2011 23:33:43

Вот как я решил это:

  • исходники копируются в каталог сборки
  • тогда применяется anttask "versioninfo"
  • скомпилировать измененные источники

Вот файл Java, в котором хранится информация о версии:

public class Settings {

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

}

А вот и Анттаск "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>
3
19.11.2011 20:09:17