Automatic versioning of Android build using git describe with Gradle

后端 未结 10 1904
醉酒成梦
醉酒成梦 2020-11-30 18:18

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

相关标签:
10条回答
  • 2020-11-30 18:58

    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'
            }
    
        }
    }
    
    0 讨论(0)
  • 2020-11-30 19:10

    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()
        ...
      }
      ...
    }
    
    0 讨论(0)
  • 2020-11-30 19:11

    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.

    0 讨论(0)
  • 2020-11-30 19:14

    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.

    0 讨论(0)
  • 2020-11-30 19:16

    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.

    0 讨论(0)
  • 2020-11-30 19:16

    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:

    0 讨论(0)
提交回复
热议问题