Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

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.

Slide 8

Slide 8 text

The principle of least surprise

Slide 9

Slide 9 text

The fine print

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Things you can do with the root project

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

The buildscript classpath def buildTime = new Date().format("yyyy-mm-dd'T'HH:mm:ssZ") android { defaultConfig { buildConfigField "String", "BUILD_TIME", "\"${buildTime}\"" } }

Slide 17

Slide 17 text

The buildscript classpath buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.1' classpath 'joda-time:joda-time:2.9.9' } }

Slide 18

Slide 18 text

The buildscript classpath import org.joda.time.DateTime // ... android { defaultConfig { // using joda-time in our buildscripts buildConfigField "String", "BUILD_TIME", "\"${DateTime.now()}\"" } }

Slide 19

Slide 19 text

The buildscript classpath Only have the buildscript {} block in the root project

Slide 20

Slide 20 text

ext (ExtraPropertiesExtension)

Slide 21

Slide 21 text

ext (ExtraPropertiesExtension) In root: ext { compileSdk = 25 } In app/lib: android { compileSdkVersion rootProject.ext.compileSdk }

Slide 22

Slide 22 text

ext (ExtraPropertiesExtension) In root: ext { supportLibVersion = '25.3.1' appCompat = "com.android.support:appcompat-v7:${supportLibVersion}" cardView = "com.android.support:cardview-v7:${supportLibVersion}" }

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Configuring multiple projects in one go

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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..

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Configuring multiple projects in one go allprojects { plugins.withId('com.android.application') { android { buildToolsVersion '25.0.2' compileSdkVersion 25 } } }

Slide 31

Slide 31 text

Subproject gradle files

Slide 32

Slide 32 text

Ordering within Gradle files import foo.Bar

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Ordering within Gradle files import foo.Bar apply plugin: 'foo' configurations { } android { } dependencies { } task custom(type: CustomTask) { }

Slide 38

Slide 38 text

Be consistent

Slide 39

Slide 39 text

Do not repeat configuration blocks dependencies { compile rootProject.ext.appCompat } android { // like 300 lines of android config } dependencies { compile rootProject.ext.cardView }

Slide 40

Slide 40 text

Organizing repetitive config

Slide 41

Slide 41 text

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() }

Slide 42

Slide 42 text

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) { /* ... */ }

Slide 43

Slide 43 text

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) { /* ... */ }

Slide 44

Slide 44 text

Organizing repetitive config apply from: "${rootDir}/quality.gradle"

Slide 45

Slide 45 text

buildSrc

Slide 46

Slide 46 text

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)

Slide 47

Slide 47 text

buildSrc . ├── build.gradle ├── app │ └── build.gradle └── lib └── build.gradle

Slide 48

Slide 48 text

buildSrc . ├── build.gradle ├── buildSrc <-- buildSrc meta-project │ └── src/main/groovy ├── app │ └── build.gradle └── lib └── build.gradle

Slide 49

Slide 49 text

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() } }

Slide 50

Slide 50 text

buildSrc import pink.madis.gradle.Git // ... // prints f559b99 println Git.hash(project)

Slide 51

Slide 51 text

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(); } }

Slide 52

Slide 52 text

Java, yaaaay \o/

Slide 53

Slide 53 text

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; }

Slide 54

Slide 54 text

buildSrc import pink.madis.gradle.Deps // ... dependencies { compile Deps.appCompat // <-- clickable & autocompletable compile Deps.cardView }

Slide 55

Slide 55 text

Writing plugins

Slide 56

Slide 56 text

Writing plugins package org.gradle.api public interface Plugin { void apply(T target) }

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Writing plugins class CheckstylePlugin implements Plugin { @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() } } } }

Slide 60

Slide 60 text

Writing plugins project.with { apply plugin: 'checkstyle' } // same as project.apply plugin: 'checkstyle'

Slide 61

Slide 61 text

Writing plugins class CheckstylePlugin implements Plugin { @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() } } }

Slide 62

Slide 62 text

Writing plugins public class CheckstylePlugin implements Plugin { @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()); }); } }

Slide 63

Slide 63 text

Writing plugins replace apply from: "${rootDir}/quality.gradle" with apply plugin: pink.madis.gradle.CheckStylePlugin

Slide 64

Slide 64 text

Writing plugins build.gradle ↓ quality.gradle ↓ CheckstylePlugin

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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)

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Thanks! Questions? Twitter: @madisp Slides: speakerdeck.com/madisp