Автоматическое управление версиями Android-проекта из git description с помощью Android Studio/Gradle
Я много искал, но, вероятно, из-за новизны Android Studio и Gradle, я не нашел описания того, как это сделать. Я хочу сделать в основном именно то, что описано в этом посте, но с Android Studio, Gradle и Windows, а не Eclipse и Linux.
9 ответов
Более правильным и скудным способом достижения результата, который в последнее время набирает обороты, было бы использование интеграции gradle git через привязки Groovy JGit. Поскольку он использует JGit, он даже не требует установки git для работы.
Вот базовый пример, демонстрирующий аналогичное (но с дополнительной информацией в строке gitVersionName) решение:
buildscript {
dependencies {
classpath 'org.ajoberstar:grgit:1.4.+'
}
}
ext {
git = org.ajoberstar.grgit.Grgit.open()
gitVersionCode = git.tag.list().size()
gitVersionName = "${git.describe()}"
}
android {
defaultConfig {
versionCode gitVersionCode
versionName gitVersionName
}
}
[...]
Как вы можете видеть в документации Grgit API, операция description предоставляет дополнительную информацию, кроме самого последнего доступного тега в истории:
Найдите самый последний тег, доступный из HEAD. Если тег указывает на фиксацию, отображается только тег. В противном случае он добавляет суффикс имени тега к числу дополнительных коммитов поверх помеченного объекта и сокращенному имени объекта самого последнего коммита.
Во всяком случае, он не скажет, грязное состояние или нет. Эту информацию можно легко добавить, посмотрев на чистое состояние репо и добавив строку, если она не чистая.
Поместите следующее в свой файл build.gradle для проекта. Нет необходимости изменять манифест напрямую: Google предоставил необходимые хуки в их конфигурацию.
def getVersionCode = { ->
try {
def code = new ByteArrayOutputStream()
exec {
commandLine 'git', 'tag', '--list'
standardOutput = code
}
return code.toString().split("\n").size()
}
catch (ignored) {
return -1;
}
}
def getVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags', '--dirty'
standardOutput = stdout
}
return stdout.toString().trim()
}
catch (ignored) {
return null;
}
}
android {
defaultConfig {
versionCode getVersionCode()
versionName getVersionName()
}
}
Обратите внимание, что если git не установлен на машине или есть какая-то другая ошибка при получении имени / кода версии, по умолчанию будет указано то, что есть в вашем манифесте Android.
Увидев ответ на Moveaway00 и комментарий Avinash R к этому ответу, я решил использовать это:
apply plugin: 'android'
def getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', '--first-parent', '--count', 'master'
standardOutput = stdout
}
return Integer.parseInt(stdout.toString().trim())
}
catch (ignored) {
return -1;
}
}
def getVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags', '--dirty'
standardOutput = stdout
}
return stdout.toString().trim()
}
catch (ignored) {
return null;
}
}
android {
defaultConfig {
versionCode getVersionCode()
versionName getVersionName()
}
}
Я отредактировал код moveaway00, включив в него комментарий Avinash R: код версии - это количество коммитов с тех пор master
, так как это то, что код версии должен быть.
Обратите внимание, что мне не нужно было указывать код версии и название версии в манифесте, Gradle позаботился об этом.
Еще один способ:
https://github.com/gladed/gradle-android-git-version - это новый плагин Gradle, который автоматически вычисляет названия версий и коды версий для Android.
Он обрабатывает множество особых случаев, которые невозможны при использовании принятого решения:
- теги версий для нескольких проектов в одном репо
- расширенная версия кодов, таких как 1002003 для 1.2.3
- простые задачи для легкого извлечения информации о версии для инструментов CI
- и т.п.
Отказ от ответственности: я написал это.
Вот еще одно решение, которое требует операторов вместо функций для доступа к командной строке. Предупреждение: *nix only solution
def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
// Auto-incrementing commit count based on counting commits to master (Build #543)
def commitCount = Integer.parseInt('git rev-list master --count'.execute([], project.rootDir).text.trim())
// I want to use git tags as my version names (1.2.2)
def gitCurrentTag = 'git describe --tags --abbrev=0'.execute([], project.rootDir).text.trim()
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.some.app"
minSdkVersion 16
targetSdkVersion 22
versionCode commitCount
versionName gitCurrentTag
buildConfigField "String", "GIT_SHA", "\"${gitSha}\""
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Другой способ, используя Android Studio (Gradle): ознакомьтесь с этой записью в блоге: http://blog.android-develop.com/2014/09/automatic-versioning-and-increment.html
Вот реализация из блога:
android {
defaultConfig {
...
// Fetch the version according to git latest tag and "how far are we from last tag"
def longVersionName = "git -C ${rootDir} describe --tags --long".execute().text.trim()
def (fullVersionTag, versionBuild, gitSha) = longVersionName.tokenize('-')
def(versionMajor, versionMinor, versionPatch) = fullVersionTag.tokenize('.')
// Set the version name
versionName "$versionMajor.$versionMinor.$versionPatch($versionBuild)"
// Turn the version name into a version code
versionCode versionMajor.toInteger() * 100000 +
versionMinor.toInteger() * 10000 +
versionPatch.toInteger() * 1000 +
versionBuild.toInteger()
// Friendly print the version output to the Gradle console
printf("\n--------" + "VERSION DATA--------" + "\n" + "- CODE: " + versionCode + "\n" +
"- NAME: " + versionName + "\n----------------------------\n")
...
}
}
Если это поможет, я создал пример скрипта Gradle, который использует теги Git и описание Git для достижения этой цели. Вот код (вы также можете найти его здесь).
1) Сначала создайте файл versioning.gradle, содержащий:
import java.text.SimpleDateFormat
/**
* This Gradle script relies on Git tags to generate versions for your Android app
*
* - The Android version NAME is specified in the tag name and it's 3 digits long (example of a valid tag name: "v1.23.45")
* If the tag name is not in a valid format, then the version name will be 0.0.0 and you should fix the tag.
*
* - The Android version CODE is calculated based on the version name (like this: (major * 1000000) + (minor * 10000) + (patch * 100))
*
* - The 4 digits version name is not "public" and the forth number represents the number of commits from the last tag (example: "1.23.45.178")
*
*/
ext {
getGitSha = {
return 'git rev-parse --short HEAD'.execute().text.trim()
}
getBuildTime = {
def df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
df.setTimeZone(TimeZone.getTimeZone("UTC"))
return df.format(new Date())
}
/**
* Git describe returns the following: [GIT_TAG_NAME]-[BUILD_NUMBER]-[GIT_SHA]
*/
getAndroidGitDescribe = {
return "git -C ${rootDir} describe --tags --long".execute().text.trim()
}
/**
* Returns the current Git branch name
*/
getGitBranch = {
return "git rev-parse --abbrev-ref HEAD".execute().text.trim()
}
/**
* Returns the full version name in the format: MM.mm.pp.ccc
*
* The version name is retrieved from the tag name which must be in the format: vMM.mm.pp, example: "v1.23.45"
*/
getFullVersionName = {
def versionName = "0.0.0.0"
def (tag, buildNumber, gitSha) = getAndroidGitDescribe().tokenize('-')
if (tag && tag.startsWith("v")) {
def version = tag.substring(1)
if (version.tokenize('.').size() == 3) {
versionName = version + '.' + buildNumber
}
}
return versionName
}
/**
* Returns the Android version name
*
* Format "X.Y.Z", without commit number
*/
getAndroidVersionName = {
def fullVersionName = getFullVersionName()
return fullVersionName.substring(0, fullVersionName.lastIndexOf('.'))
}
/**
* Returns the Android version code, deducted from the version name
*
* Integer value calculated from the version name
*/
getAndroidVersionCode = {
def (major, minor, patch) = getAndroidVersionName().tokenize('.')
(major, minor, patch) = [major, minor, patch].collect{it.toInteger()}
return (major * 1000000) + (minor * 10000) + (patch * 100)
}
/**
* Return a pretty-printable string containing a summary of the version info
*/
getVersionInfo = {
return "\nVERSION INFO:\n\tFull version name: " + getFullVersionName() +
"\n\tAndroid version name: " + getAndroidVersionName() +
"\n\tAndroid version code: " + getAndroidVersionCode() +
"\n\tAndroid Git branch: " + getGitBranch() +
"\n\tAndroid Git describe: " + getAndroidGitDescribe() +
"\n\tGit SHA: " + getGitSha() +
"\n\tBuild Time: " + getBuildTime() + "\n"
}
// Print version info at build time
println(getVersionInfo());
}
2) Затем отредактируйте ваше приложение /build.gradle, чтобы использовать его следующим образом:
import groovy.json.StringEscapeUtils;
apply plugin: 'com.android.application' // << Apply the plugin
android {
configurations {
// ...
}
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 17
targetSdkVersion 22
applicationId "app.example.com"
versionCode getAndroidVersionCode() // << Use the plugin!
versionName getAndroidVersionName() // << Use the plugin!
// Build config constants
buildConfigField "String", "GIT_SHA", "\"${getGitSha()}\""
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
buildConfigField "String", "FULL_VERSION_NAME", "\"${getVersionName()}\""
buildConfigField "String", "VERSION_DESCRIPTION", "\"${StringEscapeUtils.escapeJava(getVersionInfo())}\""
}
signingConfigs {
config {
keyAlias 'MyKeyAlias'
keyPassword 'MyKeyPassword'
storeFile file('my_key_store.keystore')
storePassword 'MyKeyStorePassword'
}
}
buildTypes {
debug {
minifyEnabled false
debuggable true
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
debuggable false
}
}
productFlavors {
// ...
}
dependencies {
// ...
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
/**
* Save a build.info file
*/
task saveBuildInfo {
def buildInfo = getVersionInfo()
def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0]
assetsDir.mkdirs()
def buildInfoFile = new File(assetsDir, 'build.info')
buildInfoFile.write(buildInfo)
}
gradle.projectsEvaluated {
assemble.dependsOn(saveBuildInfo)
}
Наиболее важной частью является применение плагина
apply plugin: 'com.android.application'
А затем используйте его для имени и кода версии Android
versionCode getAndroidVersionCode()
versionName getAndroidVersionName()
Основываясь на ответе Лео Лама и моих предыдущих исследованиях того же решения для ant, я разработал чисто кроссплатформенное решение с использованием jgit:
Файл: git-version.gradle
buildscript {
dependencies {
//noinspection GradleDynamicVersion
classpath "org.eclipse.jgit:org.eclipse.jgit:4.1.1.+"
}
repositories {
jcenter()
}
}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import static org.eclipse.jgit.lib.Constants.MASTER
def git = Git.wrap(new FileRepositoryBuilder()
.readEnvironment()
.findGitDir()
.build())
ext.readVersionCode = {
def repo = git.getRepository()
def walk = new RevWalk(repo)
walk.withCloseable {
def head = walk.parseCommit(repo.getRef(MASTER).getObjectId())
def count = 0
while (head != null) {
count++
def parents = head.getParents()
if (parents != null && parents.length > 0) {
head = walk.parseCommit(parents[0])
} else {
head = null
}
}
walk.dispose()
println("using version name: $count")
return count
}
}
ext.readVersionName = {
def tag = git.describe().setLong(false).call()
def clean = git.status().call().isClean()
def version = tag + (clean ? '' : '-dirty')
println("using version code: $version")
return version
}
Использование будет:
apply from: 'git-version.gradle'
android {
...
defaultConfig {
...
versionCode readVersionCode()
versionName readVersionName()
...
}
...
}
Определите простую функцию в файле Gradle:
def getVersion(){
def out = new ByteArrayOutputStream();
exec {
executable = 'git'
args = ['describe', '--tags']
standardOutput = out
}
return out.toString().replace('\n','')
}
Используй это:
project.version = getVersion()
Это слегка измененная версия ответа Диего, которая отвечает моему желанию иметь название версии в следующем стиле:
{последний тег} - {короткий хэш текущего коммита} - {метка времени текущего коммита}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.ajoberstar.grgit:grgit-core:3.1.1'
}
}
/**
* Version name will be in following format:
*
* "{latest tag}-{short hash of current commit}-{timestamp of current commit}"
*
* Example: 1.5.3-ecae0d4-1560426381
*/
ext {
git = org.ajoberstar.grgit.Grgit.open(currentDir: projectDir)
listOfTags = git.tag.list()
noTags = listOfTags.isEmpty()
head = git.head()
if (noTags) {
gitVersionCode = 0
gitVersionName = "no-tag-${head.abbreviatedId}-${head.time}"
} else {
lastTaggedCommit = listOfTags.last().commit
tagName = git.describe(commit: lastTaggedCommit)
gitVersionCode = listOfTags.size()
gitVersionName = "$tagName-${head.abbreviatedId}-${head.time}"
}
}
task printVersion() {
println("Version Name: $gitVersionName")
println("Version Code: $gitVersionCode")
}
Предполагая, что вы также указали versionNameSuffix
в app
модуля build.gradle
следующим образом:
android {
...
productFlavors {
staging {
versionCode gitVersionCode
versionName gitVersionName
versionNameSuffix '-STAGING'
...
}
// ... other flavors here
}
}
Тогда это будет название версии: