使用 git describe 和 Gradle 自动版本控制 Android 构建

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17097263/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 08:38:00  来源:igfitidea点击:

Automatic versioning of Android build using git describe with Gradle

androidgitgradleandroid-buildauto-versioning

提问by Mike Yount

I have searched extensively, but likely due to the newness of Android Studio and Gradle. I haven't found any description of how to do this. I want to do basically exactly what is described in this post, but with Android Studio, Gradle and Windows rather than Eclipse and Linux.

我进行了广泛的搜索,但可能是由于 Android Studio 和 Gradle 的新颖性。我还没有找到任何关于如何做到这一点的描述。我想基本上完全按照这篇文章中的描述去做,但是使用 Android Studio、Gradle 和 Windows 而不是 Eclipse 和 Linux。

采纳答案by Diego

A more proper and lean way to achieve the result which gained traction lately would be to use gradle git integrationvia Groovy JGit bindings. As it uses JGit it doesn't even require git to be installed to work.

实现最近获得关注的结果的一种更合适和精益的方法是通过Groovy JGit 绑定使用gradle git 集成。由于它使用 JGit,它甚至不需要安装 git 即可工作。

Here's a basic example showing a similar (but with some additional information in gitVersionName string) solution:

这是一个基本示例,显示了类似的(但在 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
  }
}
[...]

As you can see in Grgit API documentationthe describe operationprovides additional information other than most recent tag reachable in history:

正如您在Grgit API 文档所见describe 操作提供了除历史记录中可访问的最新标签之外的其他信息:

Find the most recent tag that is reachable from HEAD. If the tag points to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.

查找可从 HEAD 访问的最新标记。如果标记指向提交,则仅显示标记。否则,它将在标记对象之上附加提交的数量和最近提交的缩写对象名称作为标记名称的后缀。

Anyhow, it won't tell if the state is dirty or not. This information can be easily added by looking at the clean statusof the repo, and appending a string if it's not clean.

无论如何,它不会告诉状态是否脏。通过查看repo的干净状态,并在不干净时附加一个字符串,可以轻松添加此信息。

回答by moveaway00

Put the following in your build.gradle file for the project. There's no need to modify the manifest directly: Google provided the necessary hooks into their configuration.

将以下内容放入项目的 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()
    }
}

Note that if git is not installed on the machine, or there is some other error getting the version name/code, it will default to what is in your android manifest.

请注意,如果机器上未安装 git,或者在获取版本名称/代码时出现其他错误,它将默认为您的 android 清单中的内容。

回答by Léo Lam

After seeing moveaway00's answerand Avinash R's comment on that answer, I've ended up using this:

在看到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()
    }
}

I've edited moveaway00's code to also include Avinash R's comment: the version code is now the number of commits since master, as this is what the version code is supposed to be.

我已经编辑了 moveaway00 的代码,还包括 Avinash R 的评论:版本代码现在是自 以来的提交次数master,因为这是版本代码应该是的。

Note that I didn't need to specify the version code and the version name in the manifest, Gradle took care of it.

请注意,我不需要在清单中指定版本代码和版本名称,Gradle 会处理它。

回答by gladed

Yet another way:

还有一种方式:

https://github.com/gladed/gradle-android-git-versionis a new gradle plugin that calculates android-friendly version names and version codes automatically.

https://github.com/gladed/gradle-android-git-version是一个新的 gradle 插件,可以自动计算 android 友好的版本名称和版本代码。

It handles a lot of special cases that are not possible using the accepted solution:

它处理了许多使用公认的解决方案无法实现的特殊情况:

  • version tags for multiple projects in the same repo
  • expanded version codes like 1002003 for 1.2.3
  • gradle tasks for easily extracting version info for CI tools
  • etc.
  • 同一个 repo 中多个项目的版本标签
  • 1.2.3 的扩展版本代码,如 1002003
  • 用于轻松提取 CI 工具版本信息的 gradle 任务
  • 等等。

Disclaimer: I wrote it.

免责声明:我写的。

回答by jpotts18

Here is another solution that requires statements instead of functions to access the commandline. Warning: *nix only solution

这是另一种需要语句而不是函数来访问命令行的解决方案。警告:*nix 唯一解决方案

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

    }
}

回答by Sean

Another way, using Android Studio (Gradle):Check out this blog post: http://blog.android-develop.com/2014/09/automatic-versioning-and-increment.html

另一种方法,使用 Android Studio (Gradle):查看这篇博文:http: //blog.android-develop.com/2014/09/automatic-versioning-and-increment.html

Here's the implementation from the blog:

这是博客中的实现:

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")
...
}

}

}

回答by Soloist

If it can be of any help, I've set up an example Gradle script that uses Git tags and Git describe to achieve this. Here's the code (you can also find it here).

如果它有任何帮助,我已经设置了一个示例 Gradle 脚本,它使用 Git 标签和 Git 描述来实现这一点。这是代码(您也可以在此处找到)。

1) First create a versioning.gradlefile containing:

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) Then edit your app/build.gradleto use it like this:

2) 然后编辑您的app/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)
}

The most important part is to apply the plugin

最重要的部分是应用插件

apply plugin: 'com.android.application'

And then use it for the android version name and code

然后将其用于android版本名称和代码

versionCode getAndroidVersionCode()
versionName getAndroidVersionName()

回答by Avinash R

Based on Léo Lam's answerand my earlier explorationson the same solution for ant, I have devised a purely cross-platformsolution using jgit:

基于Léo Lam 的回答以及我之前对 ant 相同解决方案的探索,我设计了一个使用 jgit的纯跨平台解决方案:

(original source)

(原始来源

File: git-version.gradle

文件: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
}

The usage will be:

用法将是:

apply from: 'git-version.gradle'

android {
  ...
  defaultConfig {
    ...
    versionCode readVersionCode()
    versionName readVersionName()
    ...
  }
  ...
}

回答by S.D.

Define simple function in gradle file:

在 gradle 文件中定义简单的函数:

def getVersion(){
    def out = new ByteArrayOutputStream();
    exec {
        executable = 'git'
        args = ['describe', '--tags']
        standardOutput = out
    }
    return out.toString().replace('\n','')
}

Use it:

用它:

project.version = getVersion()

回答by azizbekian

This is a slightly changed version of Diego's answer, which fulfils my desire to have version name in following style:

这是Diego's answer 的一个略有变化的版本,它满足了我希望具有以下样式的版本名称的愿望:

{latest tag} - {short hash of current commit} - {time of current commit}

{最新标签} - {当前提交的短哈希} - {当前提交的时间}

    import java.text.SimpleDateFormat

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'org.ajoberstar.grgit:grgit-core:3.1.1'
        }
    }

    /**
     * Version name will be in following format:
     *
     * "{latest release tag}-{short commit hash of current commit}-{time of current commit}"
     *
     * Example: 1.6.0-5ae9b86-2019-07-04-13:20
     */
    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 {
            tagNames = listOfTags.collect { git.describe(commit: it.commit, tags: true) }
            mostRecentVersion = mostRecentVersion(tagNames)

            def date = new SimpleDateFormat('yyyy-MM-dd-HH:mm').format(new Date(head.time * 1000))
            gitVersionCode = listOfTags.size()
            gitVersionName = "$mostRecentVersion-${head.abbreviatedId}-${date}"
        }
    }

    /**
     * Shamelessly stolen from <a href="https://stackoverflow.com/a/7723766/">StackOverflow</a>.
     */
    static String mostRecentVersion(List versions) {
        def sorted = versions.sort(false) { a, b ->
            List verA = a.tokenize('.')
            List verB = b.tokenize('.')

            def commonIndices = Math.min(verA.size(), verB.size())

            for (int i = 0; i < commonIndices; ++i) {
                def numA = verA[i].toInteger()
                def numB = verB[i].toInteger()

                if (numA != numB) {
                    return numA <=> numB
                }
            }
            // If we got this far then all the common indices are identical, so whichever version is longer must be more recent
            verA.size() <=> verB.size()
        }

        // println "Sorted versions: $sorted"
        sorted[-1]
    }

    task printVersion() {
        println("Version Code: $gitVersionCode")
        println("Version Name: $gitVersionName")
    }

Assuming you have also specified versionNameSuffixin appmodule's build.gradlefollowing way:

假设您还versionNameSuffixapp模块的build.gradle以下方式指定:

    android {
        ...
        productFlavors {
            debug {
                versionCode gitVersionCode
                versionName gitVersionName
                versionNameSuffix '-DEBUG'
                ...
            }
            // ... other flavors here
        }
    }

Then this will be the version name:

然后这将是版本名称:

Version name as can be seen by user in settings

用户在设置中可以看到的版本名称