Automatic versioning of Android build using git describe with Gradle

只愿长相守 提交于 2019-11-26 10:13:00

问题


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.


回答1:


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

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

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 documentation the describe operation provides additional information other than most recent tag reachable in history:

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.

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




回答2:


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.

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.




回答3:


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

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.

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




回答4:


Yet another way:

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

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.

Disclaimer: I wrote it.




回答5:


Here is another solution that requires statements instead of functions to access the commandline. Warning: *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'
        }

    }
}



回答6:


Another way, using Android Studio (Gradle): Check out this blog post: 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")
...
}

}




回答7:


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

1) First create a versioning.gradle file containing:

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.gradle to use it like this:

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

versionCode getAndroidVersionCode()
versionName getAndroidVersionName()



回答8:


Based on Léo Lam's answer and my earlier explorations on the same solution for ant, I have devised a purely cross-platform solution using jgit:

(original source)

File: 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()
    ...
  }
  ...
}



回答9:


Define simple function in gradle file:

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

Use it:

project.version = getVersion()



回答10:


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

{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 versionNameSuffix in app module's build.gradle following way:

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

Then this will be the version name:



来源:https://stackoverflow.com/questions/17097263/automatic-versioning-of-android-build-using-git-describe-with-gradle

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!