Автоинкрементный код версии в приложении Android
Есть ли способ автоматически увеличивать код версии каждый раз, когда вы создаете приложение для Android в Eclipse?
Согласно http://developer.android.com/guide/publishing/versioning.html, вы должны вручную увеличить свой код версии в AndroidManifest.xml
,
Я понимаю, что перед каждой сборкой нужно запускать скрипт, который, например, анализирует файл AndroidManifest.xml, находит номер версии, увеличивает его и сохраняет файл до начала самой сборки. Тем не менее, я не мог узнать, как и если Eclipse поддерживает скрипты запуска до / после сборки.
Я нашел эту статью о настройке Ant Builder, но это не совсем об Android, и я боюсь, что это может испортить слишком много предопределенных шагов сборки для Android?
Должна быть общая проблема, как вы ее решили?
Что ж, это можно сделать вручную, но как только вы забудете выполнить эту работу, вы получите разные версии с одинаковым номером, и вся версия не имеет смысла.
14 ответов
Итак, я вижу это так:
В зависимости от статьи, которую вы представляете, используйте муравей для этой задачи (цели?).
- Манифест разбора (разбора XML)
- получить манифест старой версии и увеличить его / получить версию из репозитория
- сохранить новую версию в манифесте
- построить приложение для Android.
Но в моем случае я обычно заполняю это поле значением, основанным на ревизии тега, при развертывании или распространении приложения.
Я достиг этого. И вот как я это сделал для следующего парня (используя Eclipse):
1) Создайте внешний консольный исполняемый файл, который собирается записать код новой версии в AndroidManifest.xml: (мой находится на C#)
using System.IO;
using System.Text.RegularExpressions;
namespace AndroidAutoIncrementVersionCode
{
class Program
{
static void Main(string[] args)
{
try
{
string FILE = @"AndroidManifest.xml";
string text = File.ReadAllText(FILE);
Regex regex = new Regex(@"(?<A>android:versionCode="")(?<VER>\d+)(?<B>"")", RegexOptions.IgnoreCase);
Match match = regex.Match(text);
int verCode = int.Parse(match.Groups["VER"].Value) + 1;
string newText = regex.Replace(text, "${A}" + verCode + "${B}", 1);
File.WriteAllText(FILE, newText);
}
catch { }
}
}
}
в сторону: любой компилятор c-sharp может создать это приложение, вам не нужна Visual Studio или даже Windows
- если у вас его еще нет, установите.NET runtime ( Mono будет работать, ссылка) ( ссылка на MS.NET Framework 2.0, 2.0 самая маленькая загрузка, любая версия>= 2.0 в порядке)
- скопируйте этот код в
*.cs
файл (я назвал мой:AndroidAutoIncrementVersionCode.cs
) - откройте командную строку и перейдите туда, где вы сделали свой
*.cs
файл - создайте файл с помощью этой команды (в Windows аналогично Mono, но измените путь к компилятору):
c:\Windows\Microsoft.NET\Framework\v2.0.50727\csc AndroidAutoIncrementVersionCode.cs
(см. .NET или Mono для получения дополнительной информации) Поздравляю, вы только что создали приложение C# без каких-либо инструментов, оно должно было сгенерировать
AndroidAutoIncrementVersionCode.exe
в том же каталоге автоматически** пробег может отличаться, пути могут отличаться, покупка не требуется, недействительно там, где запрещено, я добавил это, потому что C# - это круто, и люди ошибочно думают, что у него есть блокировка MS, вы могли бы так же легко перевести это на другой язык (но Я не собираюсь делать это для вас;). кстати, любая версия любого.NET-компилятора будет работать, я адаптировал код для наименее общего знаменателя...*
конец в сторону
2) Запустите исполняемый файл во время процесса сборки: a) Перейдите в свойства проекта
б) В свойствах перейдите в "Строители" -> "Новые..."
в) Выберите "Программа"
г) На вкладке "Основные" выберите местоположение программы (я также установил рабочий каталог в качестве безопасного) и дайте ему имя, если хотите.
e) На вкладке "Обновить" выберите "Обновить ресурсы после завершения" и "Выбранный ресурс" - это обновит манифест после того, как мы его напишем.
f) На вкладке "Параметры сборки" вы можете отключить "Распределить консоль", так как у вас нет ввода и вывода, а затем выбрать только "Во время ручной сборки" и "Во время автоматической сборки", снимите флажок "После очистки", если установлен этот флажок. Затем выберите "Указать рабочий набор соответствующих ресурсов" и нажмите кнопку "Указать ресурсы...". В диалоговом окне "Редактировать рабочий набор" найдите файл "AndroidManifest.xml" и проверьте его, затем нажмите "Готово"
е) Теперь нажмите "ОК" в "Редактировать диалог настроек" и в свойствах вашего приложения выберите вновь созданного компоновщика и продолжайте нажимать "Вверх", пока он не окажется вверху списка, таким образом, выполняется автоматическое увеличение во-первых, и не вызывает случайных несинхронизированных состояний или перестроений. Когда созданный вами новый строитель окажется в верхней части списка, нажмите "ОК", и все готово.
Этот сценарий оболочки, подходящий для систем *nix, устанавливает текущую версию subversion для versionCode и последнего компонента versionName. Я использую Netbeans с NBAndroid и вызываю этот скрипт из целевой -pre-compile в custom_rules.xml.
Сохраните этот скрипт в файле с именем incVersion в том же каталоге, что и AndroidManifest.xml, сделайте его исполняемым: chmod +x incVersion
manf=AndroidManifest.xml
newverfull=`svnversion`
newvers=`echo $newverfull | sed 's/[^0-9].*$//'`
vers=`sed -n '/versionCode=/s/.*"\([0-9][0-9]*\)".*/\1/p' $manf`
vername=`sed -n '/versionName=/s/.*"\([^"]*\)".*/\1/p' $manf`
verbase=`echo $vername | sed 's/\(.*\.\)\([0-9][0-9]*\).*$/\1/'`
newvername=$verbase$newverfull
sed /versionCode=/s/'"'$vers'"'/'"'$newvers'"'/ $manf | sed /versionName=/s/'"'$vername'"'/'"'$newvername'"'/ >new$manf && cp new$manf $manf && rm -f new$manf
echo versionCode=$newvers versionName=$newvername
Создайте или отредактируйте custom_rules.xml и добавьте это:
<?xml version="1.0" encoding="UTF-8"?>
<project name="custom_rules">
<xmlproperty file="AndroidManifest.xml" prefix="mymanifest" collapseAttributes="true"/>
<target name="-pre-compile">
<exec executable="./incVersion" failonerror="true"/>
</target>
</project>
Так что, если моя текущая версия SVN - 82, я получаю это в AndroidManifest.xml:
android:versionCode="82"
android:versionName="2.1.82">
Когда я хочу выпустить новую версию, я, как правило, обновляю первые части versionName, но даже если я забуду, последняя часть versionName (описанная в моей статье "О деятельности") всегда будет сообщать мне, из какой версии svn она была собрана., Кроме того, если я не проверил изменения, номер ревизии будет 82M, а versionName будет что-то вроде 2.1.82M.
Преимущество по сравнению с простым увеличением номера версии каждый раз, когда выполняется сборка, заключается в том, что номер остается под контролем и может быть напрямую связан с конкретной версией SVN. Очень полезно при расследовании ошибок в других версиях.
FWIW, мне удалось обновить значение версии сборки в шести строках Python:
#!/bin/env python
import os
from xml.dom.minidom import parse
dom1 = parse("AndroidManifest.xml")
dom1.documentElement.setAttribute("android:versionName","%build.number%")
f = os.open("AndroidManifest.xml", os.O_RDWR)
os.write( f, dom1.toxml() )
Опираясь на ответ Чарльза, следующее увеличивает существующую версию сборки:
#!/usr/bin/python
from xml.dom.minidom import parse
dom1 = parse("AndroidManifest.xml")
oldVersion = dom1.documentElement.getAttribute("android:versionName")
versionNumbers = oldVersion.split('.')
versionNumbers[-1] = unicode(int(versionNumbers[-1]) + 1)
dom1.documentElement.setAttribute("android:versionName", u'.'.join(versionNumbers))
with open("AndroidManifest.xml", 'wb') as f:
for line in dom1.toxml("utf-8"):
f.write(line)
Receipe:
Для того чтобы атрибут android:versionCode элемента manifest в AndroidManifest.xml автоматически устанавливался на текущее время (от эпохи в секундах, полученное из оболочки Unix) при каждом запуске сборки, добавьте его в цель -pre-build в custom_rules.xml Файл Android.
<target name="-pre-build">
<exec executable="date" outputproperty="CURRENT_TIMESTAMP">
<arg value="+%s"/>
</exec>
<replaceregex file="AndroidMainfest.xml" match="android:versionCode=.*"
replace='android:versionCode="${CURRENT_TIMESTAMP}"' />
</target>
Подтверждение теста:
Получите атрибут versionCode сгенерированного apk-файла, используя следующую команду оболочки из каталога проекта Android:
$ANDROID_SDK/build-tools/20.0.0/aapt dump badging bin/<YourProjectName>.apk | grep versionCode
и сравните его с текущей датой, возвращенной командой shell: date +%s
Разница должна равняться периоду времени в секундах между двумя шагами подтверждения выше.
Преимущества этого подхода:
- Независимо от того, запущена ли сборка из командной строки или Eclipse, она будет обновлять versionCode.
- Код версии гарантированно будет уникальным и будет увеличиваться для каждой сборки
- VersionCode может быть реконструирован в приблизительное время сборки, если вам это нужно
- Приведенный выше скрипт заменяет любое текущее значение versionCode, даже 0, и не требует заполнителя макроса (например, -build_id-).
- Поскольку значение обновляется в файле AndroidManifest.xml, вы можете включить его в систему контроля версий, и оно сохранит фактическое значение, а не какой-либо макрос (например, -build_id-).
Основываясь на ответе Рокки, я немного улучшил этот скрипт на python, чтобы увеличить также versionCode, он работает для меня в Eclipse (интегрирован согласно ckozl) и Mac OSX
#!/usr/bin/python
from xml.dom.minidom import parse
dom1 = parse("AndroidManifest.xml")
oldVersion = dom1.documentElement.getAttribute("android:versionName")
oldVersionCode = dom1.documentElement.getAttribute("android:versionCode")
versionNumbers = oldVersion.split('.')
versionNumbers[-1] = unicode(int(versionNumbers[-1]) + 1)
dom1.documentElement.setAttribute("android:versionName", u'.'.join(versionNumbers))
dom1.documentElement.setAttribute("android:versionCode", str(int(oldVersionCode)+1))
with open("AndroidManifest.xml", 'wb') as f:
for line in dom1.toxml("utf-8"):
f.write(line)
также не забудьте chmod +x autoincrement.py
и убедитесь, что у вас есть правильный путь к Python в первой строке (в зависимости от вашей среды), как указал sulai
Я сделал нечто подобное, но написал это как приложение Desktop AIR вместо какого-то внешнего C# (не чувствовал установки другой системы сборки). Создайте это приложение Flex/ActionScript и измените путь к файлу, соберите его как отдельное настольное приложение. Он переписывает 1.2.3 часть вашего файла.
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="371" height="255" applicationComplete="Init();">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
public function Init():void
{
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
var myFile:File = new File("D:\\Dropbox\\Projects\\My App\\src\\Main-app.xml");
var fileStream:FileStream = new FileStream();
fileStream.open(myFile, FileMode.READ);
var fileContents:String = fileStream.readUTFBytes(fileStream.bytesAvailable);
var startIndex:Number = fileContents.indexOf("<versionNumber>");
var numberIndex:Number = startIndex + 15;
var endIndex:Number = fileContents.indexOf("</versionNumber>");
if (startIndex == -1 || endIndex == -1)
return;
var versionNumber:String = fileContents.substr(numberIndex, endIndex - numberIndex);
var versionArr:Array = versionNumber.split(".");
var newSub:Number = Number(versionArr[2]);
newSub++;
versionArr[2] = newSub.toString();
versionNumber = versionArr.join(".");
var newContents:String = fileContents.substr(0, startIndex) + "<versionNumber>" + versionNumber + "</versionNumber>" +
fileContents.substr(endIndex + 16);
fileStream.close();
fileStream = new FileStream();
fileStream.open(myFile, FileMode.WRITE);
fileStream.writeUTFBytes(newContents);
fileStream.close();
close();
}
]]>
</fx:Script>
<s:Label x="10" y="116" width="351" height="20" fontSize="17"
text="Updating My App Version Number" textAlign="center"/>
</s:WindowedApplication>
Я смог выработать собственное решение на основе предоставленной информации. На случай, если кому-то будет полезно, это мой bash-скрипт для обновления атрибутов versionCode и versionName при использовании GIT VCS в Linux.
Мой скрипт для редактирования файла AndroidManifest.xml выглядит так:
#/bin/bash
CODE=`git tag | grep -c ^v`
NAME=`git describe --dirty`
COMMITS=`echo ${NAME} | sed -e 's/v[0-9\.]*//'`
if [ "x${COMMITS}x" = "xx" ] ; then
VERSION="${NAME}"
else
BRANCH=" (`git branch | grep "^\*" | sed -e 's/^..//'`)"
VERSION="${NAME}${BRANCH}"
fi
cat AndroidManifest.template.xml \\
| sed -e "s/__CODE__/${CODE}/" \\
-e "s/__VERSION__/${VERSION}/" > AndroidManifest.xml
exit 0
Он анализирует файл шаблона (AndroidManifest.template.xml) и заменяет строки "__VERSION__" и "__CODE__" более подходящими значениями:
- "__CODE__" заменяется количеством тегов в репозитории Git, которое начинается с одной строчной буквы V и сопровождается последовательностью цифр и точек. Это похоже на большинство строк версии: "v0.5", "v1.1.4" и так далее.
- "__VERSION__" заменяется комбинацией выходных данных команды "git description" и, если не "чистой" сборки, ветви, на которой она была построена.
Под "чистой" сборкой я подразумеваю тот, где все компоненты находятся под контролем версий, а их последние коммиты помечены. "git description --dirty" сообщит номер версии на основе последнего доступного аннотированного тега в вашем последнем коммите в текущей ветке. Если после этого тега есть коммиты, сообщается количество этих коммитов, а также сокращенное имя объекта вашего последнего коммита. Опция "--dirty" добавит "-dirty" к вышеуказанной информации, если были изменены какие-либо файлы, которые находятся под управлением версией.
Таким образом, AndroidManifest.xml больше не должен находиться под контролем версий, и вам следует только отредактировать файл AndroidManifest.template.xml. Начало вашего файла AndroidManifest.template.xml выглядит примерно так:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.packagename"
android:versionCode="__CODE__"
android:versionName="__VERSION__" >
Надеюсь, это кому-нибудь пригодится
Вся заслуга принадлежит ckoz, но я написал свою собственную реализацию в C#. Я думаю, что это немного быстрее и не ест ошибок, потому что, если что-то идет не так, возможно, что-то неправильно настроено, и я должен знать об этом.
namespace AndroidVersionCodeAutoIncrement
{
using System.IO;
using System.Text.RegularExpressions;
public class Program
{
private static readonly Regex VersionCodeRegex = new Regex("android:versionCode=\"(?<version>.*)\"", RegexOptions.Compiled);
public static void Main()
{
using (var manifestFileStream = File.Open("AndroidManifest.xml", FileMode.Open, FileAccess.ReadWrite))
using (var streamReader = new StreamReader(manifestFileStream))
{
var manifestFileText = streamReader.ReadToEnd();
var firstMatch = VersionCodeRegex.Match(manifestFileText);
if (firstMatch.Success)
{
int versionCode;
var versionCodeValue = firstMatch.Groups["version"].Value;
if (int.TryParse(versionCodeValue, out versionCode))
{
manifestFileText = VersionCodeRegex.Replace(manifestFileText, "android:versionCode=\"" + (versionCode + 1) + "\"", 1);
using (var streamWriter = new StreamWriter(manifestFileStream))
{
manifestFileStream.Seek(0, SeekOrigin.Begin);
streamWriter.Write(manifestFileText);
manifestFileStream.SetLength(manifestFileText.Length);
}
}
}
}
}
}
}
Для тех, кто работает в OSX и хочет использовать Python, но не теряет форматирование XML, которое при синтаксическом анализе выполняется синтаксическим анализатором Python XML, приведем сценарий Python, который будет выполнять инкрементный просмотр на основе регулярного выражения, сохраняя форматирование:
#!/usr/bin/python
import re
f = open('AndroidManifest.xml', 'r+')
text = f.read()
result = re.search(r'(?P<groupA>android:versionName=")(?P<version>.*)(?P<groupB>")',text)
version = str(float(result.group("version")) + 0.01)
newVersionString = result.group("groupA") + version + result.group("groupB")
newText = re.sub(r'android:versionName=".*"', newVersionString, text);
f.seek(0)
f.write(newText)
f.truncate()
f.close()
Код был основан на ответе @ckozl, только что был сделан на python, поэтому вам не нужно создавать исполняемый файл для этого. Просто назовите скрипт autoincrement.py, поместите его в ту же папку, где находится файл manifest.xml, а затем выполните действия, которые ckozl описал выше!
Если вы используете Gradle, то вы можете versionName
а также versionCode
очень легко в build.gradle
, Вы можете использовать git commit count как растущее число для идентификации сборки.
Вы также можете использовать эту библиотеку: https://github.com/rockerhieu/Versionberg.
Вот версия Java для того, что это стоит. Также обработка нескольких манифестов.
String directory = "d:\\Android\\workspace\\";
String[] manifests = new String[]
{
"app-free\\AndroidManifest.xml",
"app-donate\\AndroidManifest.xml",
};
public static void main(String[] args)
{
new version_code().run();
}
public void run()
{
int I = manifests.length;
for(int i = 0; i < I; i++)
{
String path = directory + manifests[i];
String content = readFile(path);
Pattern versionPattern = Pattern.compile( "(.*android:versionCode=\")([0-9]+)(\".*)", Pattern.DOTALL );
Matcher m = versionPattern.matcher(content);
if (m.matches())
{
int code = Integer.parseInt( m.group(2) ) + 1;
System.out.println("Updating manifest " + path + " with versionCode=" + code);
String newContent = m.replaceFirst("$1" + code + "$3");
writeFile(path + ".original.txt", content);
writeFile(path, newContent);
}
else
{
System.out.println("No match to update manifest " + path);
}
}
}
Мне очень нравятся два решения. Первый зависит от Play Store, а другой - от Git.
Используя Play Store, вы можете увеличить код версии, посмотрев на самый высокий доступный код загруженной версии. Преимущество этого решения в том, что загрузка APK никогда не завершится ошибкой, поскольку код вашей версии всегда на единицу выше, чем код версии в Play Store. Обратной стороной является то, что распространять APK за пределами Play Store становится сложнее. Вы можете настроить это с помощью Gradle Play Publisher, следуя руководству по быстрому запуску и указав плагину автоматически разрешать коды версий:
plugins {
id 'com.android.application'
id 'com.github.triplet.play' version 'x.x.x'
}
android {
...
}
play {
serviceAccountCredentials = file("your-credentials.json")
resolutionStrategy = "auto"
}
Используя Git, вы можете увеличивать код версии в зависимости от того, сколько коммитов и тегов имеет ваш репозиторий. Преимущество здесь в том, что ваш результат воспроизводится и не зависит от чего-либо, кроме вашего репо. Обратной стороной является то, что вам нужно сделать новую фиксацию или тег, чтобы увеличить код версии. Вы можете настроить это, добавив плагин Version Master Gradle:
plugins {
id 'com.android.application'
id 'com.supercilex.gradle.versions' version 'x.x.x'
}
android {
...
}
Если вы хотите обновить AndroidManifest.xml для использования определенного номера версии, возможно, из системы сборки, то вы можете использовать проект, который я только что отправил на GitHub: https://github.com/bluebirdtech/AndroidManifestVersioner
Это базовое приложение командной строки.NET, использование:
AndroidManifestVersioner <path> <versionCode> <versionName>.
Спасибо другим авторам за их код.