gradle user to addict sf -...

217
Gradle: From User to Addict But my talk: I’m jake ouellette, senior software engineer working at twitter This is my presentation, Gradle From user to addict

Transcript of gradle user to addict sf -...

Page 1: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Gradle: From User to Addict

But my talk:I’m jake ouellette, senior software engineer working at twitterThis is my presentation, GradleFrom user to addict

Page 2: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

If you’re like me, you’ve been using gradle for a while (Or maybe just considering it?) I’ve been using it both for making applications and developing gradle plugins

Page 3: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

At Twitter, I work on the Fabric team to build tools for mobile developers. One tool we have is Crashlytics, a mobile crash reporting SDK that goes into android applications. I’ll talk about how the Gradle plugin works later.

Page 4: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Simple Possible

What I’ve found from personal experience and when teaching others about Gradle* Can see what’s possible, but can’t reach it.

Page 5: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

* Load up android studio

Page 6: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

apply plugin: 'android'

android { compileSdkVersion 19 buildToolsVersion "19.0.3"

defaultConfig { minSdkVersion 8 targetSdkVersion 19 versionCode 1 versionName "1.0" } buildTypes { release { runProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } }

dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:19.+' }

* Look at the default project build.gradle

Page 7: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

I want to add a task!

* Like “I want to add a task to this thing!”just look over the default build script and try and understand it.

Page 8: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

apply plugin: ‘com.android.application’

* Ok, so the first line adds lots of tasks!* Gradle is based on plugins, so it doesn’t do much on its own* Super important, not very informative on how to build grade scripts

Page 9: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { compileSdkVersion 19 … }

* The next line seems to set android properties* What if I have my own variables?* What if I want to change these depending on the build?

Page 10: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

dependencies { compile 'com.android.support:appcompat-v7:19.+' compile fileTree(dir: 'libs', include: ['*.jar']) }

* Ok, finally I get to a dependency block* Gradle is a dependency manager, which means it figures out what dependencies your projects has and downloads them, and their transitive

dependencies, from the web

Page 11: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

dependencies { compile 'com.android.support:appcompat-v7:19.+' compile fileTree(dir: 'libs', include: ['*.jar']) }

* As a naive person, it kind of reminds me of maven.

Page 12: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

dependencies { compile 'com.android.support:appcompat-v7:19.+' compile fileTree(dir: 'libs', include: ['*.jar']) }

* But wait, is fileTree executing code? * Maven doesn’t execute code, maven is just xml

Page 13: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

What is the Gradle language really doing?

How do I use it?

• create tasks • make a multi-app project • write builds once, reuse

Page 14: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

To do this, we’re going to dive under the hood in the Gradle Syntax understand what’s really going on

Page 15: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Simple PossibleSimpossible

So what I’d like to do today, is bridge the gap between simple and possible

* Forming the simpossible

Page 16: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

dependencies { compile 'com.crashlytics.android:crashlytics:1.+' }

build.gradle

* We have directions on how to set up the crashlytics SDK

Page 17: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { dependencies { compile 'com.crashlytics.android:crashlytics:1.+' } }

* And for a while, in the android studio project, that was inside the android block!

Page 18: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { … }

dependencies { compile 'com.crashlytics.android:crashlytics:1.+' }

* But at one point, android studio switched to a different location, outside that block* I was super confused, because the old way didn’t break our build! Why did this work?

Page 19: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

{ }

* Realized didn’t understand how { } work

Page 20: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

example.java

new Runnable() { public void run() { … } }

* Take a look at anonymous classes in java

Page 21: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

example.java

final int time = System.currentTimeMillis() new Runnable() { public void run() { … } }

* If you declare a variable

Page 22: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

example.java

final int time = System.currentTimeMillis() new Runnable() { public void run() { System.out.println(time); } }

* You can reference it, it is in scope of the anonymous class* Outputting time when runnable was CREATED

Page 23: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

example.java

final int time = System.currentTimeMillis()

{ System.out.println(time); }

* Groovy Curly braces work a lot like anonymous classes

Page 24: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { … }

So when we look at this

Page 25: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android({ … })

We should note, the curly braces really are creating a closure, and passing it into an android object / method

Page 26: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

android() =

android.apply()

* Calls without methods get delegated to an apply method

Page 27: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

android.apply(Closure configuration)

* android object actually has this method which takes a closure

Page 28: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android.apply({ … })

* And that parameter is our curly braces block!

Page 29: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android.apply({ it })

In gradle, “IT” is an object works kind of like this, but references the object curly braces are applied to

Page 30: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

println(android.getClass()) android { println(it.getClass()) }

We can see that by printing the class of it, comparing it against the class of the android object.

Page 31: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line output

class com.android.build.gradle.AppExtension_Decorated class com.android.build.gradle.AppExtension_Decorated

println(android.getClass()) android { println(it.getClass()) }

Same classes!

Page 32: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

closure.delegate = this

The reason why that works is that in groovy, you can set closures to delegate to specific objects.Internally, Gradle is delegating the closure to the applied object

Page 33: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android.apply({ it })

changes what “it” is

Page 34: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { defaultConfig { minSdkVersion 8 } }

But inside that android object, we’re not just setting values of things, we’re also opening up new curly braces

Page 35: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

defaultConfig.apply(Closure configuration)

You can think of new curly braces as though the android object has a defaultConfig object with an apply method!

Page 36: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { defaultConfig { minSdkVersion 8 } }

So similarly, this

Page 37: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { it.defaultConfig { minSdkVersion 8 } }

can look like this

Page 38: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

println(android.defaultConfig.getClass()) android { defaultConfig { println(it.getClass()) } }

We can do the same thing of comparing it to an object

Page 39: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line output

class com.android.build.gradle.internal.dsl.ProductFlavorDsl_Decorated class com.android.build.gradle.internal.dsl.ProductFlavorDsl_Decorated

println(android.defaultConfig.getClass()) android { defaultConfig { println(it.getClass()) } }

And we get the same internal object in both cases

Page 40: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { defaultConfig { minSdkVersion 8 } }

minSdk doesn’t open up another closure, it sets a value of a property

Page 41: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { defaultConfig { minSdkVersion 8 println(minSdkVersion) } }

Adding a println would output 8

Page 42: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { defaultConfig { setMinSdkVersion(8) } }

You can actually call its setter like this,This is because gradle code is groovy codeand groovy POGOs translate to java classes with getters and setters

Page 43: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android.defaultConfig.minSdkVersion = 8

So gradle also has a dot notation you’ve probably seen which allows both setting

Page 44: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android.defaultConfig.minSdkVersion

and direct access

Page 45: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { dependencies { compile 'com.crashlytics.android:crashlytics:1.+' } }

So now, going back to the original question, why did a dependency block inside an android block work?

Well, we know compile is a method or field of the dependency object, shouldn’t dependencies be a method or field of the android object?

Page 46: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

repositories { maven { url “example" } }

Let’s explore why this is working by cooking up a weird test

Here’s a repository block

Page 47: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

def maven(Closure closure)

And the reason why that works is that maven is a method of repositories that takes a closure

Page 48: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

MavenArtifactRepository maven(Closure closure)

but it actually isn’t a void method. Not all closures objects return void methods.

Page 49: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

repositories { println maven { url "example" }.url }

Page 50: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line output

example

Page 51: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Ok, are you ready to have your mind blown?

Page 52: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

MavenArtifactRepository maven(Closure closure)

mutation

You see, this call actually mutates the repositories block

Page 53: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

repositories { maven { url “example" maven { url "example2" } } }

Page 54: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle (with annotation)

repositories { SEARCHED NEXT maven { SEARCHED FIRST } }

Page 55: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { dependencies { compile 'com.crashlytics.android:crashlytics:1.+' } }

So the original dependency block weirdness is now part of a pattern to us,

Page 56: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

SEARCHED SECOND android { SEARCHED FIRST

dependencies { compile 'com.crashlytics.android:crashlytics:1.+' } }

* gradle searches up parent scope, just like a closure block* It’s just that the scopes are weird because gradle delegated them

Answer: It always belonged out of the android curly braces, they moved it correctly

Page 57: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Adding a task

Continuing to show you how Gradle works, let’s talk about the syntax for adding a task.

Page 58: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Adding a task: Cache each build.gradle with a

timestamp

Specifically, let’s have our task be to cache each build.grade with a timestamp.

Page 59: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy)

Ok, so I created a task called copy task

Page 60: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

And I wrote it out like this

Page 61: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

The idea is, it takes my build.gradle

Page 62: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

Copies it into a build cache folder

Page 63: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

And renames it based on the current system time.

Page 64: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line output

:app:copyTask UP-TO-DATE

BUILD SUCCESSFUL

Feeling clever, I ran it, and it looked OK, it read “UP-TO-DATE” which was strange… but the build completed…?

Page 65: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

ideal directory listing

buildCache/ build.gradle1399906750071 build.gradle1401281449000 build.gradle1401290623000

Page 66: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line output

> ls app/buildCache …

I later did an LS of that folder, and found nothing there. What did I do wrong??

Page 67: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask

So let’s figure out what copy task is doing

Page 68: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Gradle has great documentation

Page 69: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

org.gradle.api Interface Project

All Superinterfaces: Comparable<Project>, ExtensionAware, PluginAware

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware

This interface is the main API you use to interact with Gradle from your build file. From a Project, you have programmatic access to all of Gradle's features.

and really, what we want to look at is their API for PROJECT

Page 70: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask

The reason we want to look at PROJECT is free floating lines in your build.gradle act on projects

Syntax like this is changing your project object by calling on it

Page 71: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

org.gradle.api Interface Project

All Superinterfaces: Comparable<Project>, ExtensionAware, PluginAware

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware

This interface is the main API you use to interact with Gradle from your build file. From a Project, you have programmatic access to all of Gradle's features.

So if we look at the interface for project, we find a bunch of methods named “TASK” in that documentation

Page 72: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask

So putting the word task in

Page 73: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task(“copyTask”)

is equivalent to a method called on the project object

Page 74: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy)

and giving it a type

Page 75: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task(“copyTask”, [“type” : “Copy”])

groovy map

Is equivalent to passing in a groovy map with type being copy.

Page 76: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Task

task(“copyTask”, [“type” : “Copy”])

The output of this method is a Task

Page 77: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

def apply(Closure closure)

Like android and buildConfig from earlier, TASK has an apply method

Page 78: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

We can pass in a closure, and with it specify from, into, and rename

Page 79: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line output

:app:copyTask

So when you run it…

Page 80: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line output

> ls app/buildCache

build.gradle1399906750071

It runs OK! wait, so.. wait what?

Page 81: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

Here’s the script again, based on my investigation of the api

Page 82: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

Here was my original script

Page 83: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

<< { }

So what does the double arrow do? I saw it in the gradle docs?

Page 84: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

.doLast({ })

Double arrow tells my project to do something after everything else executes

Page 85: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Executes?

wait executes? What’s different between setting parameters for configuring, and adding code for execution?

Page 86: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line

./gradlew assemble

So when you gradle assemble

Page 87: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

configuration

apply plugin: ‘android’

android { minSdkVersion 8 }

task print << { println(“Hi!”) }

assemble.dependsOn(print)

apply plugin: ‘android’

android { minSdkVersion 8 }

task print << { println(“Hi!”) }

assemble.dependsOn(print)

executed

execution

apply plugin: ‘android’

android { minSdkVersion 8 }

task print << { println(“Hi!”) }

assemble.dependsOn(print)

First, all the code except execution steps are parsedThen all the execution steps are ordered and run

Page 88: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

So this is a method call to create a task

Page 89: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

And this immediately is applied to that task

Page 90: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { … } << { println(“Execution”) }

Even the curly braces themselves can be thought of as configuration, creating an object and passing it into the task

Page 91: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { … } << { println(“Execution”) }

It’s this last thing — the block of code that runs when you see your task running inside gradle.

You rarely see execution code in build.gradles because plugins tend to roll them up

During the execution step, you should not change properties (in fact, some don’t let you)

Page 92: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask << { // Roll my own }

Lets look at what happens if I roll my own copy task

Page 93: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis()))

Let’s roll our own copy task, here’s our from

Page 94: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis()))

And here’s our into plus rename

Page 95: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis()))

Just one thing I want to call out here, in this version, I have to figure out the project directory, copy tasks are always relative of the current project

Page 96: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask << { Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis())) }

When I make a new task, and add double arrowDOWNSIDE:This task runs EVERY Time, because I haven’t told the task its inputs or outputs

Page 97: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

TASK

So a task

Page 98: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

TASKTASK

TASK

@Input @InputDirectory @InputFile @InputFiles

has inputs, you can declare them when making a task using annotations

Page 99: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

TASK

@OutputDirectories @OutputDirectory @OutputFile @OutputFiles

TASK

TASK

TASK

TASK

And outputs, which also are declared on the task using an annotation

Page 100: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

TASKTASK

TASK TASK EXECUTED

When the at least one input changes, then the task would be rerun

Page 101: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

TASKTASK

TASK TASK EXECUTED

Causing downstream tasks to get rerun too

Page 102: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

TASKTASK

TASK UP-TO-DATE

Otherwise, the task is up to date

Page 103: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask { inputs.file new File("build.gradle") // outputs } << { // Java file copy code }

Page 104: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask { inputs.file new File("build.gradle") // outputs } << { // Java file copy code }

You could specify your inputs

Page 105: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask { inputs.file new File("build.gradle"), // outputs } << { // Java file copy code }

So then you’d put the java code back here now with inputs and outputs

Page 106: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task example

HAS NO INPUTS YET, ALWAYS RUNS

If you just make a task without saying it is a copy type task, it has no inputs declared, so always runs

Page 107: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy)

COPY TASK HAS EMPTY SET OF THINGS TO COPY

And if you don’t provide any configuration The task has a list of 0 things to copy, so it never runs, is UP-TO-DATE

Page 108: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) << {

}

COPY TASK HAS EMPTY SET OF THINGS TO COPY

// CODE WILL NOT EXECUTE

Copy task has an empty set of things, so if you add a do Last, it never executes, because the task is never executed.

Page 109: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

Good

So in summary,

Page 110: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

BAD

This code doesn’t run because of no inputs or outputs, also, it’d be changing configuration properties if it did run during execution, which is bad.

Page 111: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask << { Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis())) }

Weird

And I just don’t recommend doing this if you have a DSL to help you.

Page 112: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line

./gradlew tasks

Gradle tasks is a command that lists tasks without executing them

One of the things I really like about gradle is how the configuration step sets up your project’s structure

Page 113: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line output

Build tasks ----------- assemble assembleDebug assembleDebugTest assembleRelease build buildDependents buildNeeded clean

So you can see all the tasks that you can call here.

Page 114: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

configuration

apply plugin: ‘android’

android { minSdkVersion 8 }

task print << { println(“Hi!”) }

assemble.dependsOn(print)

apply plugin: ‘android’

android { minSdkVersion 8 }

task print << { println(“Hi!”) }

assemble.dependsOn(print)

not executed

Basically just configures the project, then outputs the configured tasks

Page 115: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Compile

Dependency Graph

PreBuild Assemble

And during the configuration step, gradle figures out the dependencies between things

Page 116: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

call?

Compile Assemble

You might be familiar with ant, which has antcall,

and say “Hey wait, what if I decide I want to call a task from another task? Is there a language for that?”

Page 117: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Compile Assemble

no

Page 118: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

assemble.dependsOn(task)task.dependsOn(compile)

AssembleCompile

“The answer is no. You should add a task dependency.

Page 119: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

task.onlyIf { condition }

You can switch the task on and off with conditions

Page 120: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Compile Task

Dependency GraphCrashlytics Task

Crashlytics Task

The crashlytics plugin, for example, uses this to put our tasks between steps.

Page 121: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Multi-Project Builds

Page 122: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

opensource/library/

day-job/app/

Alright, so hey, I had an open source project I wanted to reference it from an app I was working on

Page 123: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

development/app/library/

If you had a project with a library, you might have a folder structure like this

Page 124: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line

cd development/app/

In ant, you’d CD into the app’s folder

Page 125: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line

ant build

Ant build the app

Page 126: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

development/app/library/

During its build, the app would look at the library and maybe build it

Page 127: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

Project Root Library

In order for gradle to understand all the tasks during the configuration step, a build needs to see everything

Page 128: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

root/app/library/build.gradle

development/

So gradle makes your root folder have both projects, and a root build.gradle

Page 129: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line

cd development/root

and now, to build, first you cd into your root folder

Page 130: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line

./gradlew app:assemble

And build the specific app you want to build

Page 131: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

dependencies { compile project(‘:library’) }

To link apps, you use a special syntax called project, that requires the other project be visible at the root

Page 132: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

include ‘library’ include ‘app’

settings.gradle

You can make apps visible in the root by adding them to your settings.gradle

Page 133: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

opensource/library/

day-job/app/

So you want something like this

Page 134: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

AppLibrary

Installs Into Depends

OnLocal Maven Cache

So you install into your local maven cache

Page 135: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

repositories { mavenLocal() }

Then make your root project depend on maven local.

Page 136: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

app library

ExecutionC

onfiguration

./gradlew customBuild

Tasks Executed

Tasks Executed

Before you were doing this

Page 137: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

app library Maven Local

ExecutionC

onfiguration

EXEC: ./gradlew :library:install

Tasks Executed

But, in order to keep your open source project separate from your day job

Page 138: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

app library Maven Local

ExecutionC

onfigurationTasks

Executed

EXEC: ./gradlew :app:install

Page 139: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

AppProject Root

Library

So I had a project that looked like this

Page 140: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

app library

ExecutionC

onfiguration

Tasks Executed

EXEC: ./gradlew :app:install

Tasks Executed

And I’d compile it, so during the configuration step, gradle figured out the DAG, then executed compile tasks in the correct order

Page 141: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

-P

I was using a gradle -P syntax, which sets a property in your root project

Page 142: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

./gradlew :app:assemble -PrunOnCi=false

So I’d call my app like this

Page 143: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

if (runOnCi) { … }

build.gradle

And so I and this flag, runOnCi, that seemed to work when I build this project

Page 144: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

App2

Project Root

Library

./gradlew :app:assemble -PrunOnCi=false

but then I added a second project to the root and tried to compile the first one

Page 145: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

app library

ExecutionC

onfiguration

Tasks Executed

EXEC: ./gradlew :app:assemble -PrunOnCi=false

Tasks Executed

app2

So the project ran through the configure stepAnd the app2 didn’t need to be executed

Page 146: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

BUILD SUCCESSFUL

Page 147: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

App2

Project Root

Library

./gradlew :app2:assemble

so then I tried to compile the second projectThis project didn’t yet have different behavior on CI, so I didn’t pass that parameter in.

Page 148: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

app library

ExecutionC

onfiguration

EXEC: ./gradlew :app2:assemble

app2

BUILD FAILED

So the project ran through the configure stepAnd the app2 didn’t need to be executed

Page 149: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

BUILD FAILED

Page 150: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

app library

ExecutionC

onfiguration

EXEC: ./gradlew :app2:assemble

app2

BUILD FAILED

if (runOnCi)

What did the build fail on?

Page 151: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

if (hasProperty(runOnCi)) { … }

Can fix by adding hasProperty

Page 152: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

org.gradle.configureondemand=true

gradle.properties

Or you can speed up your build using gradle incubating configureondemand feature

Page 153: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

app library

ExecutionC

onfiguration

EXEC: ./gradlew :app2:assemble

Tasks Executed

app2

Tasks Executed

So once you set that, it only configures the parts of the app you want to configure

Page 154: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Code Reuse

Page 155: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

Good

Earlier we had that build caching task that we said was good.

Page 156: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

Good?

but is it.. really?

Page 157: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }

because, I’m going to put this in every project that I have

Page 158: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

class CacheBuild extends Copy {

}

So let’s say we could extend the copy task and set intelligent defaults

Page 159: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

class CacheBuild extends Copy { { … } }

plain ol’ java initializer

One way we could do that is make a java initializer block Not a closure

Page 160: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

class CacheBuild extends Copy { { from('build.gradle'); into('buildCache'); rename({ it + System.currentTimeMillis() }) } }

Then we put our configuration code into that initializer

Page 161: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

task cache(type:CacheBuild)

And now we can create our task very easily.

Page 162: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

AppProject Root

Library

The questions becomes, where do we save that new gradle task?

Page 163: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

Project Root

Library

Imagining each of my projects is a build.gradle

Page 164: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

Project Root

Library

Custom Code

You can put the class declaration in any of them

Page 165: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

Project Root

Library

But then the class isn’t visible to the others

Page 166: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

Project Root

Library

buildsrc/src/main/groovy/CacheBuild.groovy

Custom Code

Or I could put it in a build src folder

Page 167: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

Project Root

Library

buildsrc/src/main/groovy/CacheBuild.groovy

buildsrc code is visible to all projects relative of a root

Page 168: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

App

Project Root

Library

buildsrc/src/main/groovy/CacheBuild.groovy

day-job/app

However, then you run into the issue that that app in another project root can’t see the cool code

Page 169: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

AppCustom Task

Installs Into Depends

OnLocal Maven Cache

So you can install build scripts (locally or not), and then use them elsewhere

Page 170: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

assemble.dependsOn(‘cache’)

Even still, we’d have to wire this shared code up in every build between two tasks, wouldn’t it be nice if we could automate usage?

Page 171: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

apply plugin: 'BuildCache'

Gradle has a solution to this though! Plugins.

Page 172: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

org.gradle.api Interface Project

All Superinterfaces: Comparable<Project>, ExtensionAware, PluginAware

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware

This interface is the main API you use to interact with Gradle from your build file. From a Project, you have programmatic access to all of Gradle's features.

Page 173: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

apply([‘plugin’ : ‘BuildCache’])

Page 174: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

User’s Build Scriptapply plugin: ‘BuildCache’

plugin codevoid apply(Project project)

calls

Code reuse behaviors here.

Page 175: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

class BuildCache implements Plugin<Project> {

void apply(Project project) {

And so you declare a plugin class, and that’s where you put this apply method

Page 176: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Inject into dependency graph

compile.dependsOn(buildCache)

Inside apply, add your tasks and hook them onto user’s tasks

Page 177: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

buildCache = … android.applicationVariants.all { assemble.dependsOn(buildCache) }

On android, you can hook each build type and flavor using applicationVariants.all

Page 178: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

So this is actually how Crashlytics is integrated into gradle, it’s hooking onto Gradle tasks

Page 179: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

So as a reminder, crashlytics is a crash reporting tool that shows you stack traces of your mobile applications

Page 180: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

ExampleActivity.onClick()a.b()com.crashlytics.obfuscation.

If you’re using proguard and your app crashes, the crashes would be obfuscated

Page 181: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Previously, the Crashlytics plugin was responsible for uploading these mapping files. I was part of the team that integrated Twitter & MoPub into the Crashlytics plugin, so you now can provision Twitter keys with the same tool.

Page 182: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Fabric was actually announced last week, so more on that by Andrea Falconee: it’s pretty cool stuff.

Page 183: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Building on top of Android

Anyway, like I said, we upload the mapping files via hooking onto tasks.

I’ve learned a lot building on top of the Android Gradle plugin,and just wanted to mention a couple tips to help people out

Page 184: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Pay attention to Android Gradle updates

Page 185: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

0.3 * System requirements: * Gradle 1.3+ (tested on 1.3/1.4). Will not be compatible with 1.5. An update will be required. * Android Platform Tools 16.0.2+ * New Features: * Renderscript support. * Support for multi resource folders. See 'multires' sample. * PNG crunch is now done incrementally and in parallel. * Support for multi asset folders. * Support for asset folders in Library Projects. * Support for versionName suffix provided by the BuildType. * Testing * Default sourceset for tests now src/instrumentTest (instrumentTest<Name> for flavors) * Instrumentation tests now: * started from "deviceCheck" instead of "check" * run on all connected devices in parallel. * break the build if any test fails. * generate an HTML report for each flavor/project, but also aggregated. * New plugin 'android-reporting' to aggregate android test results across projects. See 'flavorlib' sample. * Improved DSL: * replaced android.target with android.compileSdkVersion to make it less confusing with targetSdkVersion * signing information now a SigningConfig object reusable across BuildType and ProductFlavor * ability to relocate a full sourceSet. See 'migrated' sample. * API to manipulate Build Variants. * Fixes: * Default Java compile target set to 1.6. * Fix generation of R classes in case libraries share same package name as the app project. 0.2 * Fixed support for windows. * Added support for customized sourceset. (http://tools.android.com/tech-docs/new-build-system/using-the-new-build-system#TOC-Working-with-and-Customizing-SourceSets) * Added support for dependency per configuration. * Fixed support for dependency on local jar files. * New samples "migrated" and "flavorlib"

Page 186: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

java.lang.OutOfMemoryError

Resource Resource Resource Resource

Merge Resources TaskAndroid Gradle v0.4

Page 187: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Resource Resource Resource Resource

Merge Resources TaskAndroid Gradle v0.5.4

MergedResource

Page 188: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Use extra properties to configure on a per-flavor

basis

Page 189: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android { productFlavors { paid { ext.crashlyticsEnabled = true } free { ext.crashlyticsEnabled = false } } }

Page 190: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

android.productFlavors.each { ext.crashlyticsEnabled }

Page 191: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

apply plugin: ‘com.android.application’

android { productFlavors { paid { } } }

apply plugin: ‘io.fabric’apply plugin: ‘com.android.application’

android { productFlavors { paid { } } }

apply plugin: ‘io.fabric’apply plugin: ‘io.fabric’

Finally, a note on the timing of things.

When this build script runs, each line runs

crashlytics doesn’t look at what flavors you have until AFTER the android block runs

Page 192: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

build.gradle

apply plugin: ‘com.android.application’

android { productFlavors { paid { } } }

android.applicationVariants.all { variant -> // Rename package name }

apply plugin: ‘io.fabric’apply plugin: ‘com.android.application’

android { productFlavors { paid { } } }

apply plugin: ‘io.fabric’apply plugin: ‘io.fabric’

android.applicationVariants.allandroid.applicationVariants.all { variant -> // Rename package name }

Now, if you add a line of code that does something for each application variant, e.g., rename a package, does our plugin see it?

First, all the code runs, our plugin sets up a hook to do something after the android block runs.

Then, you set up a hook, the applicationsVariants call actually doesn’t run until after the entire build.grade script is configured

Page 193: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

plugin code.groovy

afterEvaluate { // READ PACKAGE NAME }afterEvaluate { // CHANGE PACKAGE NAME }

Page 194: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

apply plugin: ‘com.android.application’

android { productFlavors { paid { } } }

apply plugin: ‘io.fabric’

android.applicationVariants.all { variant -> // Rename package name }

Page 195: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Crashlytics used HttpClient 4.3

Page 196: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Android Gradle used HttpClient 4.1.1

Page 197: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

command line

./gradlew assemble

Page 198: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

[ERROR] Caused by: java.lang.NoSuchMethodError:

org.apache.http.entity.mime.content.StringBody.<init>

Page 199: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Use the Shadow Plugin: Bytecode Manipulation

Page 200: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

apply plugin: ‘shadow’

Avail. on github john engelman

Page 201: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

org.apache -> com.crashlytics.org.apache

Page 202: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

shadow { relocation { … } }

Page 203: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

shadow { relocation { pattern = 'org.apache' shadedPattern = ‘com.crashlytics’ + pattern } }

Page 204: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

So here’s a screenshot inside our plugin JAR file, you can see apache commons at a redirected path

Page 205: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Showed what Gradle is really doing.

• created tasks • made a multi-app project • wrote a plugin

Ok, so tonight I showed you what gradle is really doing.

We created tasks, made a multi-app project, and wrote a plugin

Page 206: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Simple PossiblePossimple

Ok, so hopefully, by now, we’ve bridged the gap between simple and possible, if not forming simpossible, at least forming possimple?

Page 207: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Jake Ouellette @jakeout

Senior Software Engineer Twitter

Page 208: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first
Page 209: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Gradle Plugin Syntax has growing pains

Page 210: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

buildscript { … }

Page 211: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

buildscript { repositories { … } }

We have a staging repository for our plugins I wanted to test against,

Page 212: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

def crashlytics = … buildscript { … }

So I thought maybe I could set a variable, visible by both.

Page 213: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

No such property: crashlytics

Page 214: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

Turns out the build script block is magic. Literally, what he means is it’s as though it’s executed as another file, configuring the build script.

Page 215: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

./gradlew assemble -Dcrashlytics=…

One easy workaround

Page 216: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

buildscript { repositories { maven { url System.getProperty(“crashlytics”) } } }

repositories { maven { url System.getProperty(“crashlytics”) } }

Page 217: gradle user to addict sf - files.meetup.comfiles.meetup.com/1715787/gradle_user_to_addict_notes.pdf · build.gradle apply plugin: ‘com.android.application’ * Ok, so the first

buildScript { ext.crashlytics = … }

buildscript.ext.crashlytics

Remember, build script is just an object like everything else