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

Don’t be ashamed of your gradle.build files anymore (Droidcon Boston 2017)

Don’t be ashamed of your gradle.build files anymore (Droidcon Boston 2017)

Nobody really knows when, why or how build scripts inevitably end up as a huge mess. Nor why there’s always just the one person on the team knows how not to break it (You know, the build guy). The only thing we can be sure about is that at one time or another everyone has or is experiencing it with their projects.

We’ve already heard how you can keep your app architecture clean with various MV* patterns, but it’s all too easy to neglect your build files. Is there anything that can be done to keep Gradle build files clean? Of course there is!

In this session we’ll look at various ways that we can structure our Gradle build files to clean them up and future proof their tidyness. You will also learn how to write custom Gradle plugins and finally tame those crazy complex projects.

B5b43736709fb47edc3ee649618d84f7?s=128

Madis Pink

April 11, 2017
Tweet

Transcript

  1. Don't be ashamed of your gradle.build files anymore Madis Pink,

    Droidcon Boston 2017
  2. Don't be ashamed of your gradle.build files anymore Madis Pink,

    Droidcon Boston 2017
  3. Don't be ashamed of your gradle.build files anymore Madis Pink,

    Droidcon Boston 2017
  4. Don't be ashamed of your build.gradle files anymore Madis Pink,

    Droidcon Boston 2017
  5. Don't be ashamed of your build.gradle files anymore Madis Pink,

    Droidcon Boston 2017
  6. None
  7. I always end up googling for examples, and then modifying

    them until they coincidentally happen to do what I need1. 1 /u/sacundim commenting on Gradle. From Reddit.
  8. The principle of least surprise

  9. The fine print

  10. Standard Android Gradle project structure . ├── build.gradle ├── app

    │ └── build.gradle └── lib └── build.gradle
  11. Standard Android Gradle project structure . ├── build.gradle <-- root

    project ├── app │ └── build.gradle └── lib └── build.gradle
  12. Standard Android Gradle project structure . ├── build.gradle ├── app

    │ └── build.gradle <-- app (sub-)project └── lib └── build.gradle
  13. Standard Android Gradle project structure . ├── build.gradle ├── app

    │ └── build.gradle └── lib └── build.gradle <-- library (sub-)project
  14. Things you can do with the root project

  15. The buildscript classpath buildscript { repositories { jcenter() } dependencies

    { classpath 'com.android.tools.build:gradle:2.3.1' } } Make plugins and classes available to the project buildscript and all it's subprojects
  16. The buildscript classpath def buildTime = new Date().format("yyyy-mm-dd'T'HH:mm:ssZ") android {

    defaultConfig { buildConfigField "String", "BUILD_TIME", "\"${buildTime}\"" } }
  17. The buildscript classpath buildscript { repositories { jcenter() } dependencies

    { classpath 'com.android.tools.build:gradle:2.3.1' classpath 'joda-time:joda-time:2.9.9' } }
  18. The buildscript classpath import org.joda.time.DateTime // ... android { defaultConfig

    { // using joda-time in our buildscripts buildConfigField "String", "BUILD_TIME", "\"${DateTime.now()}\"" } }
  19. The buildscript classpath Only have the buildscript {} block in

    the root project
  20. ext (ExtraPropertiesExtension)

  21. ext (ExtraPropertiesExtension) In root: ext { compileSdk = 25 }

    In app/lib: android { compileSdkVersion rootProject.ext.compileSdk }
  22. ext (ExtraPropertiesExtension) In root: ext { supportLibVersion = '25.3.1' appCompat

    = "com.android.support:appcompat-v7:${supportLibVersion}" cardView = "com.android.support:cardview-v7:${supportLibVersion}" }
  23. ext (ExtraPropertiesExtension) In app/lib: dependencies { compile rootProject.ext.appCompat compile rootProject.ext.cardView

    }
  24. ext (ExtraPropertiesExtension) In app/lib: dependencies { compile appCompat compile cardView

    }
  25. Configuring multiple projects in one go

  26. Configuring multiple projects in one go In root: allprojects {

    println "In project ${path}" }
  27. Configuring multiple projects in one go In root: allprojects {

    println "In project ${path}" } $ ./gradlew help In project : In project :app In project :lib # ..help output omitted..
  28. Configuring multiple projects in one go allprojects { repositories {

    jcenter() } }
  29. Configuring multiple projects in one go allprojects { repositories {

    jcenter() maven { url "https://jitpack.io" } maven { url 'https://acmecorp.example.com/internal/repository' } } }
  30. Configuring multiple projects in one go allprojects { plugins.withId('com.android.application') {

    android { buildToolsVersion '25.0.2' compileSdkVersion 25 } } }
  31. Subproject gradle files

  32. Ordering within Gradle files import foo.Bar

  33. Ordering within Gradle files import foo.Bar apply plugin: 'foo'

  34. Ordering within Gradle files import foo.Bar apply plugin: 'foo' configurations

    { }
  35. Ordering within Gradle files import foo.Bar apply plugin: 'foo' configurations

    { } android { }
  36. Ordering within Gradle files import foo.Bar apply plugin: 'foo' configurations

    { } android { } dependencies { }
  37. Ordering within Gradle files import foo.Bar apply plugin: 'foo' configurations

    { } android { } dependencies { } task custom(type: CustomTask) { }
  38. Be consistent

  39. Do not repeat configuration blocks dependencies { compile rootProject.ext.appCompat }

    android { // like 300 lines of android config } dependencies { compile rootProject.ext.cardView }
  40. Organizing repetitive config

  41. Organizing repetitive config apply plugin: 'checkstyle' tasks.create("checkstyle", Checkstyle) { tasks.check.dependsOn

    it source 'src' include '**/*.java' configFile file("${rootDir}/checkstyle.xml") classpath = files() }
  42. Organizing repetitive config apply plugin: 'checkstyle' apply plugin: 'findbugs' tasks.create("checkstyle",

    Checkstyle) { tasks.check.dependsOn it source 'src' include '**/*.java' configFile file("${rootDir}/checkstyle.xml") classpath = files() } tasks.create("findbugs", FindBugs) { /* ... */ }
  43. Organizing repetitive config apply plugin: 'checkstyle' apply plugin: 'findbugs' apply

    plugin: 'pmd' tasks.create("checkstyle", Checkstyle) { tasks.check.dependsOn it source 'src' include '**/*.java' configFile file("${rootDir}/checkstyle.xml") classpath = files() } tasks.create("findbugs", FindBugs) { /* ... */ } tasks.create("pmd", Pmd) { /* ... */ }
  44. Organizing repetitive config apply from: "${rootDir}/quality.gradle"

  45. buildSrc

  46. buildSrc def gitHash(project) { def stdout = new ByteArrayOutputStream() project.exec

    { workingDir project.rootDir commandLine 'git', 'rev-parse', '--short', 'HEAD' standardOutput = stdout } return stdout.toString().trim() } // prints f559b99 println gitHash(project)
  47. buildSrc . ├── build.gradle ├── app │ └── build.gradle └──

    lib └── build.gradle
  48. buildSrc . ├── build.gradle ├── buildSrc <-- buildSrc meta-project │

    └── src/main/groovy ├── app │ └── build.gradle └── lib └── build.gradle
  49. buildSrc/src/main/groovy package pink.madis.gradle class Git { static hash(project) { def

    stdout = new ByteArrayOutputStream() project.exec { workingDir project.rootDir commandLine 'git', 'rev-parse', '--short', 'HEAD' standardOutput = stdout } return stdout.toString().trim() } }
  50. buildSrc import pink.madis.gradle.Git // ... // prints f559b99 println Git.hash(project)

  51. buildSrc/src/main/java package pink.madis.gradle; import org.gradle.api.Project; import java.io.ByteArrayOutputStream; public class Git

    { public static String hash(Project project) { ByteArrayOutputStream stdout = new ByteArrayOutputStream(); project.exec(spec -> { spec.workingDir(project.getRootDir()); spec.commandLine("git", "rev-parse", "--short", "head"); spec.setStandardOutput(stdout); }); return stdout.toString().trim(); } }
  52. Java, yaaaay \o/

  53. buildSrc public class Deps { private static final String supportLibVersion

    = "25.3.1"; public static final String appCompat = "com.android.support:appcompat-v7:" + supportLibVersion; public static final String cardView = "com.android.support:cardview-v7:" + supportLibVersion; }
  54. buildSrc import pink.madis.gradle.Deps // ... dependencies { compile Deps.appCompat //

    <-- clickable & autocompletable compile Deps.cardView }
  55. Writing plugins

  56. Writing plugins package org.gradle.api public interface Plugin<T> { void apply(T

    target) }
  57. Writing plugins package org.gradle.api public interface Plugin<Project> { void apply(Project

    target) }
  58. Writing plugins class CheckstylePlugin implements Plugin<Project> { @Override void apply(Project

    project) { project.with { // move the quality.gradle contents here } } }
  59. Writing plugins class CheckstylePlugin implements Plugin<Project> { @Override void apply(Project

    project) { project.with { apply plugin: 'checkstyle' tasks.create("checkstyle", Checkstyle) { tasks.check.dependsOn it source 'src' include '**/*.java' configFile file("${rootDir}/checkstyle.xml") classpath = files() } } } }
  60. Writing plugins project.with { apply plugin: 'checkstyle' } // same

    as project.apply plugin: 'checkstyle'
  61. Writing plugins class CheckstylePlugin implements Plugin<Project> { @Override void apply(Project

    project) { project.apply plugin: 'checkstyle' project.tasks.create("checkstyle", Checkstyle) { project.tasks.check.dependsOn it source 'src' include '**/*.java' configFile project.file("${rootDir}/checkstyle.xml") classpath = project.files() } } }
  62. Writing plugins public class CheckstylePlugin implements Plugin<Project> { @Override public

    void apply(Project project) { project.apply(Collections.singletonMap("plugin", "checkstyle")); project.getTasks().create("checkstyle", Checkstyle.class, task -> { project.getTasks().getByName("check").dependsOn(task); task.source("src"); task.include("**/*.java"); task.setConfigFile(new File(project.getRootDir(), "checkstyle.xml")); task.setClasspath(project.files()); }); } }
  63. Writing plugins replace apply from: "${rootDir}/quality.gradle" with apply plugin: pink.madis.gradle.CheckStylePlugin

  64. Writing plugins build.gradle ↓ quality.gradle ↓ CheckstylePlugin

  65. TL;DR → Use ext {} in root project to share

    constants
  66. TL;DR → Use ext {} in root project to share

    constants → Configure common things like repositories with allprojects {}
  67. TL;DR → Use ext {} in root project to share

    constants → Configure common things like repositories with allprojects {} → Extract common configuration to separate files (and eventually plugins)
  68. TL;DR → Use ext {} in root project to share

    constants → Configure common things like repositories with allprojects {} → Extract common configuration to separate files (and eventually plugins) → Take advantage of buildSrc
  69. TL;DR → Use ext {} in root project to share

    constants → Configure common things like repositories with allprojects {} → Extract common configuration to separate files (and eventually plugins) → Take advantage of buildSrc → Prefer Java where possible
  70. Thanks! Questions? Twitter: @madisp Slides: speakerdeck.com/madisp