Генерация версии android приложения из ревизии subversion и git

    Когда пользователи сталкиваются с проблемами — всегда хочется точно знать какой именно версией ПО они пользуются. При использовании системы контроля версий и автоматической нумерации версий ПО, такую информацию можно предоставить пользователям, а в случае необходимости просто попросить продиктовать строку.

    О том, как можно пронумеровать свой android проект написано здесь и здесь. В обоих статьях рассмотрен пример получения версии проекта с помощью 'svn info', причём в первой статье автор жалуется на отсутствие SvnAnt, а во второй статье автор замечает проблему, связанную с использованием 'svn info'. Проблема связана с тем, что 'svn info' выдаёт неточные сведения о ревизии рабочей копии.

    Далее рассмотрен пример решения этой проблемы достаточно простым способом.
    UPD: добавлен скрипт для git.

    Проблема


    Суть в том, что команда 'svn info' выдаёт last commit revision элементов указанного каталога.
    Например:
    $ svn info
    Revision: 32

    При этом:
    $ svn info ./src/ru/bsrgin/myproject/MyActivity.java
    Revision: 45
    $ svn -r 32 -v log
    Changed paths:
        D /some-folder
    $ svn -r 45 -v log
    Changed paths:
        M /src/ru/bsrgin/myproject/MyActivity.java

    Авторы обоих упомянутых статей интерпретируют вывод утилиты как обычный property-файл.
    Т.е. наличие строк вида Revision: 32 позволяет им интерпретировать данные как параметры в Build And скрипте. Соответственно задача сильно усложняется, если выполнять 'svn -R info' и искать наиболее позднюю ревизию в выходном файле.

    Запрос же 'svn -r HEAD info' выдаёт номер ревизии на сервере, а не в рабочей копии, что тоже неверно, т.к. не выполняется главное условие — генерация актуальной версии ПО. Аргументы BASE, COMMITTED и PREV также не дают ответ на вопрос — из файлов какой версии собран проект?

    Решение


    Я уже готов был отказаться от метода получения версии ПО, описанного в статье, но вовремя вспомнил про ещё одну утилитку 'svnversion'. Формат вывода данных этой утилитой такой:
    4123:4168     mixed revision working copy
    4168M         modified working copy
    4123S         switched working copy
    4123P         partial working copy, from a sparse checkout
    4123:4168MS   mixed revision, modified, switched working copy

    Т.е. если в моей рабочей копии запустить 'svnversion', то появится такой результат:
    $ svnversion
    32:46

    А если ещё и модифицировать какой-нибудь файл, то:
    $ svnversion
    32:46M

    Собственно говоря, утилита выдаёт полезную информацию, которую хотелось бы включить в версию приложения, но не подходит формат вывода данных. Пришлось немного разобраться с синтаксисом Build Ant скриптов…

    Ниже даны инструкции как добавить в свой проект версию рабочей копии subversion или git.

    Последовательность действий


    Создаём файл svn-revision.build.xml в корне проекта. Вставляем в него следующее содержимое:
    <project default="svn-revision">
        <target name="svn-revision">
            <!--
            Выполняем `svnversion -n` для того, чтобы получить текущую ревизию рабочей копии.
            Возможные результаты:
            4123:4168     mixed revision working copy
            4168M         modified working copy
            4123S         switched working copy
            4123P         partial working copy, from a sparse checkout
            4123:4168MS   mixed revision, modified, switched working copy
            -->
            <exec executable="svnversion" output="svnversion.output">
                <arg line="-n"/>
            </exec>
            <loadresource property="svnversion.Revision">
                <file file="svnversion.output"/>
            </loadresource>
            <echo>Revision: ${svnversion.Revision}</echo>
            <!--
            Сохраняем номер ревизии в Manifest файл в конец текстового фрагмента параметра VersionName
            -->
            <replaceregexp file="AndroidManifest.xml"
                match='android:versionName="([^".]+\.[^".]+)(\.[^"]*)?"'
                replace='android:versionName="\1.${svnversion.Revision}"'
            />
            <!--
            Удяляем временные файлы
            -->
            <delete file="svnversion.output"/>
        </target>
    </project>

    Подразумевается, что AndroidManifest.xml находится в том же самом каталоге, что и svn-revision.build.xml. Если это не так, то модифицируйте 22 строчку. Также подразумевается, что версия приложения выглядит как 1.2 или 1.2.3. Если нет, то модифицируйте 23 строчку.

    Далее создаём файл .externalToolBuilders/AddSvnRevisionToVersion.launch и добавляем в него следующие строчки.
    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
      <booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
      <booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
      <booleanAttribute key="org.eclipse.debug.core.ATTR_REFRESH_RECURSIVE" value="false"/>
      <stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${project}"/>
      <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
        <listEntry value="${project_loc}/svn-revision.build.xml"/>
      </listAttribute>
      <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
        <listEntry value="1"/>
      </listAttribute>
      <booleanAttribute key="org.eclipse.debug.core.capture_output" value="false"/>
      <booleanAttribute key="org.eclipse.debug.ui.ATTR_CONSOLE_OUTPUT_ON" value="false"/>
      <booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
      <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
      <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
      <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="ANDROID-APP"/>
      <stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${project_loc}/svn-revision.build.xml"/>
      <stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,"/>
      <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
    </launchConfiguration>

    Вместо ANDROID-APP вставляем название своего проекта (смотрим значение тэга <name> в файле .project). Далее модифицируем файл .project — после тэга <buildCommand> добавляем приведённый ниже скрипт:
    <buildCommand>
        <name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
        <triggers>full,incremental,</triggers>
        <arguments>
            <dictionary>
                <key>LaunchConfigHandle</key>
                <value><project>/.externalToolBuilders/AddSvnRevisionToVersion.launch</value>
            </dictionary>
        </arguments>
    </buildCommand>

    В результате первым task-ом будет добавлен процесс, который модифицирует AndroidManifest.xml файл. Файл будет обновляться всякий раз, когда запускается build, также процесс будет запускаться всякий раз, когда собирается release пакет. Если сборка release пакета автоматизирована или вы не пользуетесь Eclipse, то надо настроить ваш сборщик на выполнение svn-revision.build.xml.

    Git


    Для получения аналогичного результата при использовании SCV git, заменяем файл svn-revision.build.xml на приведённый ниже git-revision.build.xml.
    <project default="git-revision">
        <target name="git-revision">
            <!--
            Выполняем 'git describe' для того, чтобы
            получить текущую ревизию рабочей копии.
            См. http://habrahabr.ru/blogs/android_development/132017/#comment_4385225
            -->
            <exec executable="git" output="gitdescribe.output">
                <arg line="describe --always --dirty=+ --abbrev=5"/>
            </exec>
            <loadresource property="git.Revision">
                <file file="gitdescribe.output"/>
            </loadresource>
            <echo>Revision: ${git.Revision}</echo>
            <!--
            Сохраняем номер ревизии в Manifest файл в конец текстового фрагмента
            параметра VersionName
            -->
            <replaceregexp file="AndroidManifest.xml"
                match='android:versionName="([^".]+\.[^".]+)(\.[^"]*)?"'
                replace='android:versionName="\1.${git.Revision}"'
            />
            <!--
            Удяляем временные файлы
            -->
            <delete file="gitdescribe.output"/>
        </target>
    </project>

    Соответственно не забываем поправить файлы AddSvnRevisionToVersion.launch и .project.

    Получение versionName программным способом


    Теперь для того, чтобы получить версию ПО, можно воспользоваться таким методом:
    public static getApplicationVersion()
    {
        try {
            return getInstance().getApplicationContext().getPackageManager()
                .getPackageInfo(getPackageName(), 0).versionName;
        }
        catch (NameNotFoundException e) {
            return "App not installed!";
        }
    }

    В результате в своём проекте я получил строчку 1.0.32:46M (subversion) и 1.0.58c57+ (git).
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 13
    • –2
      Гораздно удобнее пользоваться svn:keywords в т.ч. $Revision:$ к примеру в strings.xml и выводить вы логе или прямо на активити.
      • +1
        Тогда вы возможно не подозреваете о проблеме, и вот почему…
        Создаём два подопытных каталога, в каждых добавляем по файлу.
        $ md 1
        $ md 2
        $ echo "first file">1\1.txt
        $ echo "$Rev$ and $LastChangedRevision$">2\2.txt
        $ svn add 1
        A 1
        A 1\1.txt
        $ svn add 2
        A 2
        A 2\2.txt

        Добавляем svn:keywords Rev и LastChangedRevision во второй файл, коммитим.
        $ svn propset svn:keywords "Rev LastChangedRevision" 2\2.txt
        property 'svn:keywords' set on '2\2.txt'
        $ svn --message "first commit" commit
        Adding 1
        Adding 1\1.txt
        Adding 2
        Adding 2\2.txt
        Transmitting file data ..
        Committed revision 1.

        Проверяем содержимое обоих файлов — видим, что второй теперь содержит номер ревизии.
        $ type 1\1.txt
        "first file"
        $ type 2\2.txt
        "$Rev: 1 $ and $LastChangedRevision: 1 $"

        Добавляем во второй файл строку, фиксируем, снова замечаем изменения.
        $ echo one more message>>2\2.txt
        $ svn --message "second commit" commit
        Sending 2\2.txt
        Transmitting file data .
        Committed revision 2.
        $ type 2\2.txt
        "$Rev: 2 $ and $LastChangedRevision: 2 $"
        one more message

        Смотрим что нам выдают svn info и svnversion.
        $ svn info
        Revision: 0
        $ svnversion
        0:2

        Теперь меняем первый файл, а второй не трогаем.
        $ echo third invisible commit>>1\1.txt
        $ svn --message "third commit" commit
        Sending 1\1.txt
        Transmitting file data .
        Committed revision 3.

        Смотрим что нам выдают svn info и svnversion.
        $ svn info
        Revision: 0
        $ svnversion
        0:3

        А теперь убеждаемся в том, что второй файл остался нетронутым.
        $ type 2\2.txt
        "$Rev: 2 $ and $LastChangedRevision: 2 $"
        one more message

        Это потому, что он не менялся. Он всё ещё хранит номер 2 ревизии, тогда как версия рабочей копии уже 3.
        • 0
          Даже учитывая такую особенность, номер ревизии стоит добавлять в strings.xml
          • 0
            Версию можно сохранить и в strings.xml, а не AndroidManifest.xml, для этого достаточно отредактировать svn-revision.build.xml. Только не надо пытаться сделать это с помощью svn:keywords.
            Что касается использования bash-скриптов, то можно придумать ещё способы получения версий. Я например предпочитаю пользоваться subwcrev.
            Буду очень признателен, если пришлёте Build Ant команды для git, чтобы я вставил их в статью. Сам этого сделать не могу — везде использую subversion.
            • 0
              Я ant не использую, у меня git hooks для этой цели.
      • +1
        А вот, кстати, мой скрипт для добавления ревизии в strings.xml для git.
        github.com/beshkenadze/androidhooks
        • +1
          А для mercurial можно что-нибудь придумать?
          • 0
            Имхо вместо versionName лучше использовать versionCode (он ближе по сути), плюс его не надо реплейсить регэкспом, достаточно просто определить нужную переменную.

            А вообще это задача для билд-сервера всё-таки, например, в TeamCity можно сделать таким способом: blog.thevery.info/2011/06/updating-android-version-number-during.html
            • 0
              Я кстати немного не так делал. Я делал svn log с выводом в формате xml, а затем по xpath тупо хавал последний элемент. Таким образом получал и юзера, сделавшего последний коммит, и собственно ревизию. А саму версию получал слиянием ветки (она в репозе в виде папок вида 1.2.3) и собственно ревизии.

              Кстати, использую эту технику не для Android, а для формирования версионного ресурса у c++ программ под винду.
              • +1
                Я вот так с git делаю: чтобы получить номер последнего изменения, запускаю git describe --always. А ещё запускаю git status --porcelain и смотрю, есть ли в выдаче строки, не начинающиеся с ??.

                Всё это делает python-скрипт, который вызывается из Eclipse как externalToolBuilder.
                • +1
                  Я вот так с git делаю: чтобы получить номер последнего изменения, запускаю git describe --always.

                  Аналогично. Кроме того, у git describe есть ещё интересные ключики:
                  • --tags — добавлять название последнего тэга, если есть
                  • --dirty=+ — помечать грязную рабочую копию плюсиком
                  • --abbrev=5 — урезать хэш до 5 символов
                  • 0
                    Спасибо, обновил статью
                  • +3
                    Кстати, раз уж речь зашла об альтернативных VCS, в Mercurial для получения номера текущей ревизии годится команда
                    hg heads --template {rev}

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.