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/

67867d2677e7dd983000441ff0b1c089?s=128

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

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

  3. Versioning From%Project%to%Plugin

  4. build.gradle android { compileSdkVersion 20 buildToolsVersion "20.0.0" defaultConfig { applicationId

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

    applicationId "neigut.lisa.gradlepractice" minSdkVersion 15 targetSdkVersion 21 versionCode lookupVersionCode() versionName lookupVersionName() } … }
  6. build.gradle def lookupVersionCode() { return 1 } def lookupVersionName() {

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

    } def lookupVersionName() { return project.versionName; }
  8. Wri$ng'a'Task'to'Bump' Versions

  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 } } }
  10. build.gradle project.android.applicationVariants.all { variant -> variant.mergedFlavor.versionName project.versionName variant.mergedFlavor.versionCode project.versionCode }

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

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

  13. $"./gradlew"bumpVersion"assembleDebug AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="neigut.lisa.gradlepractice" android:versionCode=“2” android:versionName=“2.0”

    >
  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 } } }
  15. build.gradle+(old) defaultConfig { versionCode lookupVersionCode() versionName lookupVersionName() } build.gradle+(new) defaultConfig

    { ... versionCode project.versionCode versionName project.versionName }
  16. Saving'the'State

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

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

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

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

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

  22. Load%the%version

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

  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);
  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) } } } }
  26. Increment(and(Save(to(Disk

  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

  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
  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
  30. ./gradlew*tasks bumpmajorVersion bumpminorVersion bumpbugFixVersion bumpalphaVersion

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

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

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

  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
  35. build.gradle project.afterEvaluate { ... }

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

    }
  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. }
  38. Versioning: 1. Stateful*versions 2. Gradle*tasks*to*manipulate*them 3. Jenkins*can*manage*app*versions*easily 4. Can*be*checked*into*Git

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

  40. Op#ons 1. Copypasta*

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

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

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

  44. Mul$%Project-Builds

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

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

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

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

    -\appTwo --\src --\build.gradle (appTwo project) --\versions.gradle
  49. se#ngs.gradle include ':app', ':appTwo’

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

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

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

    ... } }
  53. build.gradle+(root) def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion'] subprojects {

    project.afterEvaluate { // Logic to create bump version tasks goes here. } }
  54. Versioning)Steps: • Build'Tasks • Set$up$project$proper+es • Load'the'versions'file • Set'the'Variant'Version'

  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) } } }
  56. Versioning)Steps: • Build'Tasks • Set'up'project'proper4es • Load%the%versions%file • Set'the'Variant'Version'

  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) } }
  58. Versioning)Steps: • Build'Tasks • Set'up'project'proper4es • Load'the'versions'file • Set$the$Variant$Version

  59. build.gradle+(-\app) defaultConfig { versionCode project.majorVersion * 10 + project.minorVersion //

    etc versionName project.majorVersion + “.” + project.minorVersion // etc }
  60. $ ./gradlew :app:bumpminorVersion :appTwo:bumpmajorVersion

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

  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

  63. Gradle'Plugins

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

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

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

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

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

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

  70. The$Plugin groovy/com.etsy.gradle/VersionsPlugin.groovy class VersionsPlugin implements Plugin<Project> { def String VERSION_FILE_NAME

    = "versions.gradle" def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion', 'alphaVersion'] void apply(Project project) { // plugin set up logic }
  71. The$Plugin build.gradle+(root)+ subprojects { project.afterEvaluate { … } } groovy/com.etsy.gradle/VersionsPlugin.groovy

    void apply(Project project) { project.afterEvaluate { … } }
  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 } } } }
  73. groovy/com.etsy.gradle/VersionsPlugin.groovy // Project Property project.ext.set(“majorVersion”, 0); // Project Extension project.extensions.create("appVersion",

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

    bugFix def int minor def int major
  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 }
  76. Version(Code def code() { major * 10**6 + minor *

    10**4 + bugFix * 10**2 + alpha } 1.0.3.1 -> 1000301
  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() }
  78. Using&the&Plugin • Expose(the(Plugin • Apply(the(Plugin • Use(the(Plugin

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

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

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

  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() } … }
  83. Thank&you!