Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Gradlin': Plugging it in for Build Success

Gradlin': Plugging it in for Build Success

Building a Gradle plugin for versioning an Android application. We start with a simple premise -- keeping track of versions for an Android application. We build up from this task at an application level, into multi-project build logic, and finally encapsulate the build logic into a Gradle plugin for versioning.

Presented at DroidCon UK 2014, Oct 31, 2014
http://uk.droidcon.com/2014/sessions/4890/

NeiL saitug

October 31, 2014
Tweet

More Decks by NeiL saitug

Other Decks in Programming

Transcript

  1. Gradlin'
    Pluggin'(it(in(for(Build(Success
    Lisa%Neigut%DroidCon(UK%Oct%31

    View Slide

  2. Hello,&I’m$Lisa
    I"work"on"the"Android"team"at"Etsy

    View Slide

  3. Versioning
    From%Project%to%Plugin

    View Slide

  4. build.gradle
    android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"
    defaultConfig {
    applicationId "neigut.lisa.gradlepractice"
    minSdkVersion 15
    targetSdkVersion 21
    versionCode 1
    versionName "1.0"
    }

    }

    View Slide

  5. build.gradle

    android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"
    defaultConfig {
    applicationId "neigut.lisa.gradlepractice"
    minSdkVersion 15
    targetSdkVersion 21
    versionCode lookupVersionCode()
    versionName lookupVersionName()
    }

    }

    View Slide

  6. build.gradle
    def lookupVersionCode() {
    return 1
    }
    def lookupVersionName() {
    return “1.0”
    }

    View Slide

  7. build.gradle
    project.ext.set("versionCode", 1);
    project.ext.set("versionName", “1.0”);
    def lookupVersionCode() {
    return project.versionCode;
    }
    def lookupVersionName() {
    return project.versionName;
    }

    View Slide

  8. Wri$ng'a'Task'to'Bump'
    Versions

    View Slide

  9. build.gradle
    project.tasks.create('bumpVersion') {
    doLast {
    project.versionCode += 1;
    project.versionName = String.valueOf(project.versionCode + ".0");
    project.android.applicationVariants.all { variant ->
    variant.mergedFlavor.versionName project.versionName
    variant.mergedFlavor.versionCode project.versionCode
    }
    }
    }

    View Slide

  10. build.gradle
    project.android.applicationVariants.all { variant ->
    variant.mergedFlavor.versionName project.versionName
    variant.mergedFlavor.versionCode project.versionCode
    }

    View Slide

  11. Caveats
    • applicationVariants"is"only"available"for"
    com.android.application"projects.
    • All"product"flavors"will"be"assigned"the"same"
    versionName"and"versionCode

    View Slide

  12. ./gradlew*tasks
    Other tasks
    -----------
    bumpVersion

    View Slide

  13. $"./gradlew"bumpVersion"assembleDebug
    AndroidManifest.xml

    package="neigut.lisa.gradlepractice"
    android:versionCode=“2”
    android:versionName=“2.0” >

    View Slide

  14. build.gradle
    project.ext.set("versionCode", 1);
    project.ext.set("versionName", “1.0”);
    android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"
    defaultConfig {
    applicationId "neigut.lisa.gradlepractice"
    minSdkVersion 15
    targetSdkVersion 21
    versionCode project.versionCode
    versionName project.versionName
    }

    }
    project.tasks.create('bumpVersion') {
    doLast {
    project.versionCode += 1;
    project.versionName = String.valueOf(project.versionCode + ".0");
    project.android.applicationVariants.all { variant ->
    variant.mergedFlavor.versionName project.versionName
    variant.mergedFlavor.versionCode project.versionCode
    }
    }
    }

    View Slide

  15. build.gradle+(old)
    defaultConfig {
    versionCode lookupVersionCode()
    versionName lookupVersionName()
    }
    build.gradle+(new)
    defaultConfig {
    ...
    versionCode project.versionCode
    versionName project.versionName
    }

    View Slide

  16. Saving'the'State

    View Slide

  17. AndroidApp
    -\build.gradle
    -\app
    --\src
    --\build.gradle

    View Slide

  18. AndroidApp
    -\build.gradle
    -\app
    --\src
    --\build.gradle
    --\versions.gradle

    View Slide

  19. versions.gradle
    majorVersion=2
    minorVersion=0
    bugFixVersion=0
    alphaVersion=0

    View Slide

  20. versions.gradle
    majorVersion=2
    minorVersion=0
    bugFixVersion=1
    alphaVersion=0

    View Slide

  21. SellOnEtsy
    with%in'person:%2.0.0.0
    SellOnEtsy
    !with!in'person!bug'fixes:!2.0.1.0

    View Slide

  22. Load%the%version

    View Slide

  23. build.gradle
    def String VERSION_FILE_NAME = "versions.gradle”

    View Slide

  24. build.gradle
    def String VERSION_FILE_NAME = "versions.gradle”
    project.ext.set(“majorVersion”, 0);
    project.ext.set(“minorVersion”, 0);
    project.ext.set(“bugFixVersion”, 0);
    project.ext.set(“alphaVersion”, 0);

    View Slide

  25. build.gradle
    def String VERSION_FILE_NAME = "versions.gradle”
    project.ext.set(“majorVersion”, 0);
    project.ext.set(“minorVersion”, 0);
    project.ext.set(“bugFixVersion”, 0);
    project.ext.set(“alphaVersion”, 0);
    def loadVersions() {
    def versionsFile = new File(project.projectDir, VERSION_FILE_NAME)
    versionsFile.eachLine() { line ->
    if (line.contains('=')) {
    def (key, value) = line.split('=').collect { it.trim() }
    if (“majorVersion”.equals(key)) {
    project.majorVersion = Integer.parseInt(value)
    } else if (“minorVersion”.equals(key)) {
    project.minorVersion = Integer.parseInt(value)
    } else if (“bugFixVersion”.equals(key)) {
    project.bugFixVersion = Integer.parseInt(value)
    } else if (“alphaVersion”.equals(key)) {
    project.alphaVersion = Integer.parseInt(value)
    }
    }
    }
    }

    View Slide

  26. Increment(and(Save(to(Disk

    View Slide

  27. TODO:
    • Create'4'tasks'(one'for'each'version'‘type’)
    • Each'task'will'increment'the'appropriate'project'
    property
    • Then'write'out'new'values'to'versions.gradle'
    file

    View Slide

  28. build.gradle
    def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion']
    ...
    VERSIONS.each { targetName ->
    project.tasks.create(name: "bump$targetName") {
    doLast{
    project.ext[targetName] += 1
    // Write to versions.gradle file
    // Update applicationVariants stanza

    View Slide

  29. build.gradle
    def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion']
    project.afterEvaluate {
    VERSIONS.each { targetName ->
    project.tasks.create(name: "bump$targetName") {
    doLast{
    project.ext[targetName] += 1
    new File(project.projectDir, VERSIONS_FILE_NAME).withWriter { out ->
    out.write(
    """majorVersion=${project.majorVersion}
    minorVersion=${project.minorVersion}
    bugFixVersion=${project.bugFixVersion}
    alphaVersion=${project.alphaVersion}
    """
    // Update applicationVariants stanza

    View Slide

  30. ./gradlew*tasks
    bumpmajorVersion
    bumpminorVersion
    bumpbugFixVersion
    bumpalphaVersion

    View Slide

  31. $"./gradlew"bumpmajorVersion
    versions.gradle
    majorVersion=3
    minorVersion=0
    bugFixVersion=0
    alphaVersion=0

    View Slide

  32. $"./gradlew"bumpalphaVersion
    versions.gradle
    majorVersion=3
    minorVersion=0
    bugFixVersion=0
    alphaVersion=1

    View Slide

  33. AndroidManifest.xml
    android:versionName=“3.0.0.1” >

    View Slide

  34. build.gradle
    def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion']
    project.afterEvaluate {
    VERSIONS.each { targetName ->
    project.tasks.create(name: "bump$targetName") {
    doLast{
    project.ext[targetName] += 1
    new File(project.projectDir, VERSIONS_FILE_NAME).withWriter { out ->
    out.write(
    """majorVersion=${project.majorVersion}
    minorVersion=${project.minorVersion}
    bugFixVersion=${project.bugFixVersion}
    alphaVersion=${project.alphaVersion}
    """
    // Update applicationVariants stanza

    View Slide

  35. build.gradle
    project.afterEvaluate {
    ...
    }

    View Slide

  36. Project(Hooks
    project.beforeEvaluate { project ->
    }
    project.afterEvaluate { project ->
    }

    View Slide

  37. Project(Hooks
    project.beforeEvaluate { project ->
    // Set up the project properties
    // Load the versions from disk
    }
    project.afterEvaluate { project ->
    // Logic to create bump version tasks goes here.
    }

    View Slide

  38. Versioning:
    1. Stateful*versions
    2. Gradle*tasks*to*manipulate*them
    3. Jenkins*can*manage*app*versions*easily
    4. Can*be*checked*into*Git

    View Slide

  39. Wouldn’t)be)great)if)we)had)another)
    app?

    View Slide

  40. Op#ons
    1. Copypasta*

    View Slide

  41. Op#ons
    1. Copypasta
    2. Leverage0the0root0build0of0a0mul9:project0build0to0
    share0logic

    View Slide

  42. Op#ons
    1. Copypasta
    2. Leverage0the0root0build0of0a0mul9:project0build0to0
    share0logic
    3. Write0a0re:usable0Gradle0plugin

    View Slide

  43. Op#ons
    1. Copy'pasta
    2. Leverage'the'root'build'of'a'mul23project'build'to'
    share'logic
    3. Write'a're1usable'Gradle'plugin

    View Slide

  44. Mul$%Project-Builds

    View Slide

  45. AndroidApp
    -\build.gradle
    -\app
    --\src
    --\build.gradle
    --\versions.gradle

    View Slide

  46. AndroidApp
    -\build.gradle
    -\app
    --\src
    --\build.gradle
    --\versions.gradle
    -\appTwo
    --\src
    --\build.gradle
    --\versions.gradle

    View Slide

  47. AndroidApp
    -\build.gradle (root)
    -\app
    --\src
    --\build.gradle (app project)
    --\versions.gradle
    -\appTwo
    --\src
    --\build.gradle (appTwo project)
    --\versions.gradle

    View Slide

  48. AndroidApp
    -\build.gradle (root)
    -\settings.gradle
    -\app
    --\src
    --\build.gradle (app project)
    --\versions.gradle
    -\appTwo
    --\src
    --\build.gradle (appTwo project)
    --\versions.gradle

    View Slide

  49. se#ngs.gradle
    include ':app', ':appTwo’

    View Slide

  50. Versioning)Steps:
    • Build&Tasks
    • Set%up%project%proper,es
    • Load%the%versions%file
    • Set%the%Variant%Version%

    View Slide

  51. Root$Project$Hooks
    • allprojects,{}
    • subprojects,{}
    • project(‘:app’),{}

    View Slide

  52. build.gradle+(—\app)
    afterEvaluate {
    ...
    }
    build.gradle+(root)
    subprojects {
    project.afterEvaluate {
    ...
    }
    }

    View Slide

  53. build.gradle+(root)
    def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion']
    subprojects {
    project.afterEvaluate {
    // Logic to create bump version tasks goes here.
    }
    }

    View Slide

  54. Versioning)Steps:
    • Build'Tasks
    • Set$up$project$proper+es
    • Load'the'versions'file
    • Set'the'Variant'Version'

    View Slide

  55. build.gradle+(root)
    def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion']
    subprojects {
    project.afterEvaluate {
    // Logic to create bump version tasks goes here.
    }
    project.beforeEvaluate {
    VERSIONS.each { version ->
    project.ext.set(version, 0)
    }
    }
    }

    View Slide

  56. Versioning)Steps:
    • Build'Tasks
    • Set'up'project'proper4es
    • Load%the%versions%file
    • Set'the'Variant'Version'

    View Slide

  57. build.gradle+(root)
    def loadVersions(project) {
    ...
    }
    def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion']
    subprojects {
    project.afterEvaluate {
    // Logic to create bump version tasks goes here.
    }
    project.beforeEvaluate {
    VERSIONS.each { version ->
    project.ext[version] = 0
    }
    loadVersions(project)
    }
    }

    View Slide

  58. Versioning)Steps:
    • Build'Tasks
    • Set'up'project'proper4es
    • Load'the'versions'file
    • Set$the$Variant$Version

    View Slide

  59. build.gradle+(-\app)
    defaultConfig {
    versionCode project.majorVersion * 10 + project.minorVersion // etc
    versionName project.majorVersion + “.” + project.minorVersion // etc
    }

    View Slide

  60. $ ./gradlew :app:bumpminorVersion :appTwo:bumpmajorVersion

    View Slide

  61. $ ./gradlew :app:bumpminorVersion :appTwo:bumpmajorVersion
    $ ./gradlew bumpminorVersion

    View Slide

  62. Op#ons
    1. Copy'pasta
    2. Leverage'the'parent'build'of'a'mul:;project'build'to'
    share'logic
    3. Write&a&re(usable&Gradle&plugin

    View Slide

  63. Gradle'Plugins

    View Slide

  64. Gradle'Plugins
    • In$the$project$class$itself
    • In$buildSrc$
    • As$a$separate$jar

    View Slide

  65. AndroidApp
    -\build.gradle
    -\app
    --\src
    --\build.gradle
    --\versions.gradle
    -\appTwo
    --\src
    --\build.gradle
    --\versions.gradle

    View Slide

  66. AndroidApp
    -\build.gradle
    -\app
    -\appTwo

    View Slide

  67. AndroidApp
    -\buildSrc
    -\build.gradle
    -\app
    -\appTwo

    View Slide

  68. Three%things%you%need
    • groovy'package
    • resources'with'META7INF
    • build.gradle

    View Slide

  69. Components)of)a)Plugin
    • The%Plugin
    • An%Extension%class

    View Slide

  70. The$Plugin
    groovy/com.etsy.gradle/VersionsPlugin.groovy
    class VersionsPlugin implements Plugin {
    def String VERSION_FILE_NAME = "versions.gradle"
    def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion']
    void apply(Project project) {
    // plugin set up logic
    }

    View Slide

  71. The$Plugin
    build.gradle+(root)+
    subprojects {
    project.afterEvaluate { … }
    }
    groovy/com.etsy.gradle/VersionsPlugin.groovy
    void apply(Project project) {
    project.afterEvaluate { … }
    }

    View Slide

  72. groovy/com.etsy.gradle/VersionsPlugin.groovy
    void apply(Project project) {
    project.extensions.create("appVersion", VersionExtension)
    project.appVersion.loadVersions(project)
    project.afterEvaluate {
    VERSIONS.each { targetName ->
    project.tasks.create(name: "bump$targetName") {
    doLast {
    project.appVersion.bump(targetName)
    // Write out to file
    }
    }
    }
    }

    View Slide

  73. groovy/com.etsy.gradle/VersionsPlugin.groovy
    // Project Property
    project.ext.set(“majorVersion”, 0);
    // Project Extension
    project.extensions.create("appVersion", VersionExtension)

    View Slide

  74. Project(Extension
    groovy/com.etsy.gradle/VersionsPlugin.groovy
    class VersionExtension {
    def int alpha
    def int bugFix
    def int minor
    def int major

    View Slide

  75. Project(Extension
    groovy/com.etsy.gradle/VersionsPlugin.groovy
    def alphaString() { major + DOT + minor + DOT + bugFix + DOT + alpha }
    def publicBetaString() { alphaString() + "-b" }
    def releaseString() { major + DOT + minor + DOT + bugFix }
    def code() { major * 10**6 + minor * 10**4 + bugFix * 10**2 + alpha }

    View Slide

  76. Version(Code
    def code() { major * 10**6 + minor * 10**4 + bugFix * 10**2 + alpha }
    1.0.3.1 -> 1000301

    View Slide

  77. Project(Extension
    groovy/com.etsy.gradle/VersionsPlugin.groovy
    def bump(versionPiece) {
    if (versionPiece.equals(VERSIONS[0])) {
    major += 1;
    minor = 0;
    bugFix = 0;
    alpha = 0;
    }
    else if (versionPiece.equals(VERSIONS[1])) {
    minor += 1;
    bugFix = 0;
    alpha = 0;
    }
    else if (versionPiece.equals(VERSIONS[2])) {
    bugFix += 1;
    alpha = 0;
    }
    else if (versionPiece.equals(VERSIONS[3])) {
    alpha += 1;
    }
    println "$versionPiece bumped " + code()
    }

    View Slide

  78. Using&the&Plugin
    • Expose(the(Plugin
    • Apply(the(Plugin
    • Use(the(Plugin

    View Slide

  79. Expose'the'Plugin
    • groovy'package
    • resources'with'META7INF
    • build.gradle

    View Slide

  80. Expose'the'Plugin
    resources/META,INF.gradle,plugins/
    appVersion.proper9es
    implementation-class=com.etsy.gradle.VersionsPlugin

    View Slide

  81. Apply%the%Plugin
    app/build.gradle
    apply plugin: ‘com.android.application’
    apply plugin: 'appVersion'

    View Slide

  82. Use$the$Plugin
    android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"
    defaultConfig {
    applicationId "neigut.lisa.gradlepractice"
    minSdkVersion 15
    targetSdkVersion 21
    versionCode appVersion.code()
    versionName appVersion.releaseString()
    }

    }

    View Slide

  83. Thank&you!

    View Slide