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

Gradlin': Plugging it in for Build Success v2

67867d2677e7dd983000441ff0b1c089?s=47 NeiL saitug
September 03, 2015

Gradlin': Plugging it in for Build Success v2

Gradle is a cool (not so) new tool that everyone should learn how to use and love. But figuring out how to build a plugin or where to start is difficult.

This talk will walk you through the 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.

This is a (slightly) updated version of the same talk that I gave at DroidCon UK last year.

http://droidcon.se/events/stockholm-2015/

67867d2677e7dd983000441ff0b1c089?s=128

NeiL saitug

September 03, 2015
Tweet

More Decks by NeiL saitug

Other Decks in Programming

Transcript

  1. Gradlin' Pluggin' it in for Build Success

  2. Versioning From Project to Plugin

  3. build.gradle android { compileSdkVersion 23 buildToolsVersion "23.0000000" defaultConfig { applicationId

    "neigut.lisa.gradlepractice" minSdkVersion 16 targetSdkVersion 21 versionCode 200 versionName "200.0.1" } }
  4. None
  5. * d2b1b18 (origin/master, origin/HEAD) bump version ... * f0f0771 bump

    app version number
  6. build.gradle android { compileSdkVersion 23 buildToolsVersion "23.0000000" defaultConfig { applicationId

    "neigut.lisa.gradlepractice" minSdkVersion 16 targetSdkVersion 21 versionCode lookupVersionCode() versionName lookupVersionName() } }
  7. build.gradle def lookupVersionCode() { return 1 } def lookupVersionName() {

    return "1.0" }
  8. build.gradle project.ext.set("versionCode", 1); project.ext.set("versionName", "1.0");

  9. build.gradle def lookupVersionCode() { return 1 } def lookupVersionName() {

    return "1.0" }
  10. build.gradle def lookupVersionCode() { return project.versionCode } def lookupVersionName() {

    return project.versionName }
  11. None
  12. ./gradlew :project:assemble -PversionCode=10 -PversionName="10"

  13. Writing A Task To Bump Versions

  14. build.java project.tasks.create("bumpVersion") { ... }

  15. build.java project.tasks.create("bumpVersion") { doLast { ... } }

  16. build.java project.tasks.create("bumpVersion") { doLast { project.versionCode += 1; project.versionName =

    String.valueOf(project.versionCode + ".0") ... } }
  17. build.java project.tasks.create("bumpVersion") { doLast { project.versionCode += 1; project.versionName =

    String.valueOf(project.versionCode + ".0") project.android.applicationVariants.all { variant -> variant.mergedFlavor.versionCode project.versionCode variant.mergedFlavor.versionName project.versionName } } }
  18. android { compileSdkVersion 23 buildToolsVersion "23.0000000" defaultConfig { applicationId "neigut.lisa.gradlepractice"

    minSdkVersion 16 targetSdkVersion 21 versionCode project.versionCode versionName project.versionName } }
  19. Caveats: —applicationVariants is only available for com.android.application projects —All product

    flavors will have the same versionCode and versionName
  20. $ ./gradlew tasks Other tasks ----------- bumpVersion

  21. $ ./gradlew bumpVersion assembleDebug <?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">
  22. build.gradle project.ext.set("versionCode", 1); project.ext.set("versionName", "1.0"); android { compileSdkVersion 23 buildToolsVersion

    "23.0000000" defaultConfig { applicationId "neigut.lisa.gradlepractice" minSdkVersion 16 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.versionCode project.versionCode variant.mergedFlavor.versionName project.versionName } } }
  23. build.gradle old defaultConfig { versionCode lookupVersionCode() versionName lookupVersionName() ... }

    build.gradle new defaultConfig { versionCode project.versionCode versionName project.versionName ... }
  24. None
  25. $ ./gradlew bumpVersion assembleDebug

  26. $ ./gradlew bumpVersion assembleDebug <?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">
  27. Saving the State

  28. AndroidApp - build.gradle - app/ -- src/ -- build.gradle

  29. AndroidApp - build.gradle - app/ -- src/ -- build.gradle --

    versions.gradle
  30. versions.gradle majorVersion=2 minorVersion=0 bugFixVersion=0

  31. versions.gradle majorVersion=2 minorVersion=1 bugFixVersion=0

  32. just released version: 2.0.0 version currently in testing: 2.1.0

  33. Load the State

  34. build.gradle def String VERSION_FILE_NAME = "versions.gradle"

  35. build.gradle def String VERSION_FILE_NAME = "versions.gradle" project.ext.set("majorVersion", 0); project.ext.set("minorVersion", 0);

    project.ext.set("bugFixVersion", 0);
  36. build.gradle loadVersion() { def versionFile = new File(project.projectDir, VERSION_FILE_NAME) versionFile.eachLine()

    { line -> def (key, value) = line.split("=").collect { it.trim() } if ("majorVersion".equals(key)) { project.majorVersion = Integer.parseInt(value) } ... } }
  37. Increment and Save To Disk

  38. //TODO: - Create 3 tasks (one for each version 'type')

    - Each task increments the appropriate project property - Write out the new values to verisons.gradle
  39. build.gradle def VERSIONS = ["majorVersion", "minorVersion", "bugFixVersion"] ... VERSIONS.each {

    versionType -> projects.tasks.create(name: "bump$versionType") { doLast { project.ext[versionType] += 1 ... } }
  40. build.gradle def VERSIONS = ["majorVersion", "minorVersion", "bugFixVersion"] ... VERSIONS.each {

    versionType -> projects.tasks.create(name: "bump$versionType") { doLast { project.ext[versionType] += 1 // write to versions.gradle file // update the applicationVariants values } }
  41. build.gradle def VERSIONS = ["majorVersion", "minorVersion", "bugFixVersion"] ... VERSIONS.each {

    versionType -> projects.tasks.create(name: "bump$versionType") { doLast { project.ext[versionType] += 1 new File(project.projectDir, VERSIONS_FILE_NAME).withWriter { out -> out.write { """majorVersion=${project.majorVersion} minorVersion=${project.minorVersion} bugFixVersion=${project.bugFixVersion} """ } } // update the applicationVariants values } }
  42. $ ./gradlew tasks Other tasks ----------- bumpmajorVersion bumpminorVersion bumpbugFixVersion

  43. $ ./gradlew bumpmajorVersion versions.gradle majorVersion=3 minorVersion=0 bugFixVersion=0

  44. $ ./gradlew bumpbugFixVersion versions.gradle majorVersion=3 minorVersion=0 bugFixVersion=1

  45. AndroidManifest.xml android:versionName="3.0.1"

  46. build.gradle def VERSIONS = ["majorVersion", "minorVersion", "bugFixVersion"] ... VERSIONS.each {

    versionType -> projects.tasks.create(name: "bump$versionType") { doLast { project.ext[versionType] += 1 new File(project.projectDir, VERSIONS_FILE_NAME).withWriter { out -> out.write { """majorVersion=${project.majorVersion} minorVersion=${project.minorVersion} bugFixVersion=${project.bugFixVersion} """ } } // update the applicationVariants values } }
  47. build.gradle def VERSIONS = ["majorVersion", "minorVersion", "bugFixVersion"] afterEvaluate { VERSIONS.each

    { versionType -> projects.tasks.create(name: "bump$versionType") { doLast { project.ext[versionType] += 1 new File(project.projectDir, VERSIONS_FILE_NAME).withWriter { out -> out.write { """majorVersion=${project.majorVersion} minorVersion=${project.minorVersion} bugFixVersion=${project.bugFixVersion} """ } } // update the applicationVariants values } }
  48. build.gradle afterEvaluate { ... }

  49. beforeEvaluate { project -> ... } afterEvaluate { project ->

    ... }
  50. beforeEvaluate { project -> // set up project properties //

    load the versions from disk } afterEvaluate { project -> // create tasks to bump versions }
  51. None
  52. Versioning 1. Stateful versions 2. Gradle tasks to change the

    version number 3. Build server (Jenkins) can easily manage version numbers 4. Can be checked into source control (Git)
  53. None
  54. Let's Make A New App

  55. Options —Ctrl-C, Ctrl-V

  56. Options —Ctrl-C, Ctrl-V —Share logic via the root project

  57. Options —Ctrl-C, Ctrl-V —Share logic via the root project —Use

    a Gradle Plugin
  58. Options —Ctrl-C, Ctrl-V —Share logic via the root project —Use

    a Gradle Plugin
  59. Multi-Project Builds

  60. AndroidApp - build.gradle - app/ -- src/ -- build.gradle

  61. AndroidApp - build.gradle - app/ -- src/ -- build.gradle -

    app2/ -- src/ -- build.gradle
  62. AndroidApp - build.gradle (root) - app/ -- src/ -- build.gradle

    (app1) - app2/ -- src/ -- build.gradle (app2)
  63. Versioning Steps —Add bumpVersion tasks —Set up project properties —Load

    the versions file —Set the variant version
  64. Root Project Hooks —allprojects {} —subprojects {} —project(':app') {}

  65. app/build.gradle afterEvaluate { ... }

  66. app/build.gradle afterEvaluate { ... } build.gradle (root) subprojects { project.afterEvaluate

    { ... } }
  67. Versioning Steps —Add bumpVersion tasks —Set up project properties —Load

    the versions file —Set the variant version
  68. build.gradle (root) def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion'] subprojects {

    project.afterEvaluate { // create bump version tasks here, dynamically } }
  69. Versioning Steps —Add bumpVersion tasks —Set up project properties —Load

    the versions file —Set the variant version
  70. build.gradle (root) def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion'] subprojects {

    project.afterEvaluate { // create bump version tasks here, dynamically } project.beforeEvaluate { VERSIONS.each { version -> project.ext.set(version, 0) } } }
  71. Versioning Steps —Add bumpVersion tasks —Set up project properties —Load

    the versions file —Set the variant version
  72. build.gradle (root) def VERSIONS = ['majorVersion', 'minorVersion', 'bugFixVersion'] subprojects {

    project.afterEvaluate { // create bump version tasks here, dynamically } project.beforeEvaluate { VERSIONS.each { version -> project.ext.set(version, 0) } loadVersions(project) } }
  73. Versioning Steps —Add bumpVersion tasks —Set up project properties —Load

    the versions file —Set the variant version
  74. app/build.gradle (app1/app2) defaultConfig { versionCode project.majorVersion * 10 + project.minorVersion

    // etc versionName project.majorVersion + " " + project.minorVersion // etc // ... }
  75. $ ./gradlew :app:bumpminorVersion :app2:bumpmajorVersion

  76. $ ./gradlew :app:bumpminorVersion :app2:bumpmajorVersion $ ./gradlew bumpminorVersion

  77. Options —Ctrl-C, Ctrl-V —Share logic via the root project —Use

    a Gradle Plugin
  78. Gradle Plugins

  79. Where can Plugin code live?

  80. Where can Plugin code live? —in the project class itself

    —in buildSrc —as a separate jar
  81. AndroidApp - build.gradle (root) - app/ - app2/

  82. AndroidApp - buildSrc/ - build.gradle (root) - app/ - app2/

  83. —groovy package —resources directory with a META-INF folder —build.gradle file

  84. —an extension class —the plugin class

  85. // Project Property project.ext.set("majorVersion", 0) // Project Extension project.extensions.create("appVersion", VersionExtension)

    project.appVersion.majorVersion = 0
  86. VersionExtension.groovy class VersionExtension { def int majorVersion def int minorVersion

    def int bugFixVersion // ... }
  87. VersionExtension.groovy class VersionExtension { // ... def releaseString() { majorVersion

    + DOT + minorVersion + DOT + bugFixVersion } def code() { majorVersion * 10**6 + minorVersion * 10**4 + bugFixVersion } // 1.4.1 // 1040001 }
  88. VersionsPlugin.groovy class VersionPlugin implements Plugin<Project> {

  89. VersionsPlugin.groovy class VersionPlugin implements Plugin<Project> { void apply(Project project) {

    \\ plugin set up logic } }
  90. build.gradle (root) subprojects { project.afterEvaluate { ... } }

  91. build.gradle (root) subprojects { project.afterEvaluate { ... } } VersionsPlugin.groovy

    void apply(Project project) { project.afterEvaluate { ... } }
  92. VersionsPlugin.groovy class VersionPlugin implements Plugin<Project> { void apply(Project project) {

    project.extensions.create("appVersion", VersionExtension) project.appVersion.loadVersions(project) project.afterEvaluate { VERSIONS.each { version -> project.tasks.create(name: "bump$version") { doLast { project.appVersion.bump(version) // Write out to file // Update `android` plugin values } } } } } }
  93. Use your Plugin

  94. —Expose —Apply —Use

  95. Expose

  96. Expose buildSrc/resources/META-INF.gradle- plugins/appVersion.properties implementation-class=neigut.lisa.gradle.VersionsPlugin

  97. Apply app/build.gradle apply plugin: 'com.android.application' apply plugin: 'appVersion'

  98. Use app/build.gradle android { defaultConfig { versionCode appVersion.code() versionName appVersion.releaseString()

    } }
  99. Kevin Grant's Sample Project https://github.com/kevinthecity/ GradlePluginExample

  100. Lisa Neigut work @electricobjects me on the internet, @niftynei

  101. ~thank you~