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

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

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

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

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

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

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

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

[EDIT]

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

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

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

  • Если вы используете АНТ в качестве вашей системы сборки следующее описание задачи может помочь создать Java .файл свойств захватывает информацию о сборке, которую затем можно сложить в сборку несколькими способами. [Мы расширили эту идею, чтобы включить информацию, полученную от Хадсона, спасибо Марти-агнец].

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

  • утверждение: нумерация сборки должна быть определена/введена в систему непрерывной интеграции, например Гудзон. [Спасибо Марсело Моралес] мы сделали это предложение, но оно действительно открывает инженерный вопрос выпуска: Как происходит выпуск? Есть ли несколько buildnumbers в выпуске? Есть ли серьезные отношения между buildnumbers с разных релизов?

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

  • вопрос: есть ли место для номеров сборки в ручных сборках? Это настолько проблематично, что все должны использовать решение CI?

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

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

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

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

[править 23 окт 09] Я принял ответ с высшим голосованием, потому что я думаю, что это разумное решение, в то время как несколько других ответы также включают хорошие идеи. Если кто-то хочет попробовать синтезировать некоторые из них с помощью Марти-агнецs, Я подумаю о принятии другого. Единственное, что меня беспокоит в marty-lamb, это то, что он не производит надежно сериализованный номер сборки-это зависит от локальных часов в системе строителя, чтобы обеспечить однозначные номера сборки, что не здорово.

[Править 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.

9 ответов


для нескольких моих проектов я фиксирую номер версии subversion, время, пользователя, который запустил сборку, и некоторую системную информацию, вставляю их в a .файл свойств, который включается в 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>

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


код построить.в 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 для системы сборки. Это создаст а .файл свойств архивируется в финал .сосуд./война./какая разница-ar на META-INF/maven/<project group>/<project id>/pom.properties. Этот.свойства файла будет содержать свойство version.
  • Так как я рекомендуя maven, я бы настоятельно рекомендовал вам проверить плагин освободить чтобы подготовить выпуск в исходном репозитории и синхронизировать версии.

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

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

Hudson имеет три сборки / задания: непрерывный, ночной и выпуск.

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

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

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

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

преимущества:

  • для версии dev jar / war, разработчик может найти редакцию SVN из jar / war и посмотреть соответствующий код в 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 настройте, чтобы пометить его с номером сборки / меткой времени, когда сборка будет успешной.

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


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


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

  • источники копируются в каталог сборки
  • затем применяется анттаска "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 "
                       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 цента:

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

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

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

[EDIT]я развертываю версию.html с мои проекты, а затем я могу использовать скребок, чтобы просто собрать точную карту, что установлено где. Если вы используете Tomcat или аналогичный, поместите номер сборки и метку времени в description элемент web.в XML. Помните: никогда ничего не запоминайте, когда компьютер может сделать это за вас.


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

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

У нас есть класс, посвященный чтению этого и предоставлению его GUIs / logs и т. д.

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