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

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

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

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

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

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

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

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

[EDIT]

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

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

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

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

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

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

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

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

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

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

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

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

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

[Редактировать 10 июля]

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

public final class AppVersion
{
   // SVN should fill this out with the latest tag when it checked out.

   private static final String APP_SVNURL_RAW = 
     "$HeadURL: svn+ssh://[email protected]/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();
  }
}

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

Ответ 1

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

Код 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>

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

Ответ 2

Ваш файл 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();

Ответ 3

  • Номера сборки должны быть связаны с сервером непрерывной интеграции, например hudson. Используйте разные задания для разных веток/команд/распределений.
  • Чтобы сохранить номер версии в окончательной сборке, я бы рекомендовал использовать maven для системы сборки. Он создаст файл .properties, заархивированный в финальный .jar/.war/.whatever-ar на META-INF/maven/<project group>/<project id>/pom.properties. Файл .properties будет содержать свойство версии.
  • Поскольку я рекомендую maven, я настоятельно рекомендую вам проверить плагин выпуска, чтобы подготовить выпуск в исходном репозитории и сохранить версии на синхронизации.

Ответ 4

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

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

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

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

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

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

Пост-сборка для релизных сборок, легко выполняемая с помощью подключаемого модуля Hudson: тега SVN с номером сборки.

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

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

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

Ответ 5

Я тоже использую Hudson, хотя гораздо более простой сценарий:

В моем Ant script есть цель, которая выглядит так:

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

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

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

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

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

Ответ 6

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

Ответ 7

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

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

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

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

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

Ответ 8

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

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

Ответ 9

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

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

У нас есть класс, посвященный чтению и предоставлению его графическим интерфейсам/журналам и т.д.

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