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

Don’t be ashamed of your gradle.build files any...

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.

Madis Pink

April 11, 2017
Tweet

More Decks by Madis Pink

Other Decks in Programming

Transcript

  1. 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.
  2. Standard Android Gradle project structure . ├── build.gradle ├── app

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

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

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

    │ └── build.gradle └── lib └── build.gradle <-- library (sub-)project
  6. 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
  7. The buildscript classpath def buildTime = new Date().format("yyyy-mm-dd'T'HH:mm:ssZ") android {

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

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

    { // using joda-time in our buildscripts buildConfigField "String", "BUILD_TIME", "\"${DateTime.now()}\"" } }
  10. ext (ExtraPropertiesExtension) In root: ext { compileSdk = 25 }

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

    = "com.android.support:appcompat-v7:${supportLibVersion}" cardView = "com.android.support:cardview-v7:${supportLibVersion}" }
  12. 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..
  13. Configuring multiple projects in one go allprojects { repositories {

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

    android { buildToolsVersion '25.0.2' compileSdkVersion 25 } } }
  15. Ordering within Gradle files import foo.Bar apply plugin: 'foo' configurations

    { } android { } dependencies { } task custom(type: CustomTask) { }
  16. Do not repeat configuration blocks dependencies { compile rootProject.ext.appCompat }

    android { // like 300 lines of android config } dependencies { compile rootProject.ext.cardView }
  17. 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() }
  18. 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) { /* ... */ }
  19. 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) { /* ... */ }
  20. 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)
  21. buildSrc . ├── build.gradle ├── buildSrc <-- buildSrc meta-project │

    └── src/main/groovy ├── app │ └── build.gradle └── lib └── build.gradle
  22. 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() } }
  23. 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(); } }
  24. 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; }
  25. buildSrc import pink.madis.gradle.Deps // ... dependencies { compile Deps.appCompat //

    <-- clickable & autocompletable compile Deps.cardView }
  26. Writing plugins class CheckstylePlugin implements Plugin<Project> { @Override void apply(Project

    project) { project.with { // move the quality.gradle contents here } } }
  27. 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() } } } }
  28. 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() } } }
  29. 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()); }); } }
  30. TL;DR → Use ext {} in root project to share

    constants → Configure common things like repositories with allprojects {}
  31. 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)
  32. 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
  33. 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