gradle user to addict sf -...

Post on 06-Oct-2020

7 views 0 download

Transcript of gradle user to addict sf -...

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

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

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.

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.

* Load up android studio

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

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.

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

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?

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

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.

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

What is the Gradle language really doing?

How do I use it?

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

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

Simple PossibleSimpossible

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

* Forming the simpossible

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

build.gradle

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

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!

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?

{ }

* Realized didn’t understand how { } work

example.java

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

* Take a look at anonymous classes in java

example.java

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

* If you declare a variable

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

example.java

final int time = System.currentTimeMillis()

{ System.out.println(time); }

* Groovy Curly braces work a lot like anonymous classes

build.gradle

android { … }

So when we look at this

build.gradle

android({ … })

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

android() =

android.apply()

* Calls without methods get delegated to an apply method

android.apply(Closure configuration)

* android object actually has this method which takes a closure

build.gradle

android.apply({ … })

* And that parameter is our curly braces block!

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

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.

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!

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

build.gradle

android.apply({ it })

changes what “it” is

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

defaultConfig.apply(Closure configuration)

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

build.gradle

android { defaultConfig { minSdkVersion 8 } }

So similarly, this

build.gradle

android { it.defaultConfig { minSdkVersion 8 } }

can look like this

build.gradle

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

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

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

build.gradle

android { defaultConfig { minSdkVersion 8 } }

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

build.gradle

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

Adding a println would output 8

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

build.gradle

android.defaultConfig.minSdkVersion = 8

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

build.gradle

android.defaultConfig.minSdkVersion

and direct access

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?

build.gradle

repositories { maven { url “example" } }

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

Here’s a repository block

def maven(Closure closure)

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

MavenArtifactRepository maven(Closure closure)

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

build.gradle

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

command line output

example

Ok, are you ready to have your mind blown?

MavenArtifactRepository maven(Closure closure)

mutation

You see, this call actually mutates the repositories block

build.gradle

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

build.gradle (with annotation)

repositories { SEARCHED NEXT maven { SEARCHED 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,

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

Adding a task

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

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.

build.gradle

task copyTask(type: Copy)

Ok, so I created a task called copy task

build.gradle

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

And I wrote it out like this

build.gradle

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

The idea is, it takes my build.gradle

build.gradle

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

Copies it into a build cache folder

build.gradle

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

And renames it based on the current system time.

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…?

ideal directory listing

buildCache/ build.gradle1399906750071 build.gradle1401281449000 build.gradle1401290623000

command line output

> ls app/buildCache …

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

build.gradle

task copyTask

So let’s figure out what copy task is doing

Gradle has great documentation

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

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

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

build.gradle

task copyTask

So putting the word task in

build.gradle

task(“copyTask”)

is equivalent to a method called on the project object

build.gradle

task copyTask(type: Copy)

and giving it a type

build.gradle

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

groovy map

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

Task

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

The output of this method is a Task

build.gradle

def apply(Closure closure)

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

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

command line output

:app:copyTask

So when you run it…

command line output

> ls app/buildCache

build.gradle1399906750071

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

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

build.gradle

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

Here was my original script

<< { }

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

.doLast({ })

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

Executes?

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

command line

./gradlew assemble

So when you gradle assemble

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

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

build.gradle

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

And this immediately is applied to that task

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

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)

build.gradle

task copyTask << { // Roll my own }

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

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

build.gradle

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

And here’s our into plus rename

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

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

TASK

So a task

TASKTASK

TASK

@Input @InputDirectory @InputFile @InputFiles

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

TASK

@OutputDirectories @OutputDirectory @OutputFile @OutputFiles

TASK

TASK

TASK

TASK

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

TASKTASK

TASK TASK EXECUTED

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

TASKTASK

TASK TASK EXECUTED

Causing downstream tasks to get rerun too

TASKTASK

TASK UP-TO-DATE

Otherwise, the task is up to date

build.gradle

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

build.gradle

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

You could specify your inputs

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

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

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

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.

build.gradle

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

Good

So in summary,

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.

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.

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

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.

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

Compile

Dependency Graph

PreBuild Assemble

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

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?”

Compile Assemble

no

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

AssembleCompile

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

task.onlyIf { condition }

You can switch the task on and off with conditions

Compile Task

Dependency GraphCrashlytics Task

Crashlytics Task

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

Multi-Project Builds

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

development/app/library/

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

command line

cd development/app/

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

command line

ant build

Ant build the app

development/app/library/

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

App

Project Root Library

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

root/app/library/build.gradle

development/

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

command line

cd development/root

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

command line

./gradlew app:assemble

And build the specific app you want to build

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

include ‘library’ include ‘app’

settings.gradle

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

opensource/library/

day-job/app/

So you want something like this

AppLibrary

Installs Into Depends

OnLocal Maven Cache

So you install into your local maven cache

build.gradle

repositories { mavenLocal() }

Then make your root project depend on maven local.

app library

ExecutionC

onfiguration

./gradlew customBuild

Tasks Executed

Tasks Executed

Before you were doing this

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

app library Maven Local

ExecutionC

onfigurationTasks

Executed

EXEC: ./gradlew :app:install

AppProject Root

Library

So I had a project that looked like this

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

-P

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

./gradlew :app:assemble -PrunOnCi=false

So I’d call my app like this

if (runOnCi) { … }

build.gradle

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

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

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

BUILD SUCCESSFUL

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.

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

BUILD FAILED

app library

ExecutionC

onfiguration

EXEC: ./gradlew :app2:assemble

app2

BUILD FAILED

if (runOnCi)

What did the build fail on?

if (hasProperty(runOnCi)) { … }

Can fix by adding hasProperty

org.gradle.configureondemand=true

gradle.properties

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

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

Code Reuse

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.

build.gradle

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

Good?

but is it.. really?

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

build.gradle

class CacheBuild extends Copy {

}

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

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

build.gradle

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

Then we put our configuration code into that initializer

build.gradle

task cache(type:CacheBuild)

And now we can create our task very easily.

AppProject Root

Library

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

App

Project Root

Library

Imagining each of my projects is a build.gradle

App

Project Root

Library

Custom Code

You can put the class declaration in any of them

App

Project Root

Library

But then the class isn’t visible to the others

App

Project Root

Library

buildsrc/src/main/groovy/CacheBuild.groovy

Custom Code

Or I could put it in a build src folder

App

Project Root

Library

buildsrc/src/main/groovy/CacheBuild.groovy

buildsrc code is visible to all projects relative of a root

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

AppCustom Task

Installs Into Depends

OnLocal Maven Cache

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

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?

build.gradle

apply plugin: 'BuildCache'

Gradle has a solution to this though! Plugins.

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.

build.gradle

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

User’s Build Scriptapply plugin: ‘BuildCache’

plugin codevoid apply(Project project)

calls

Code reuse behaviors here.

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

Inject into dependency graph

compile.dependsOn(buildCache)

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

build.gradle

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

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

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

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

build.gradle

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

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

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.

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

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

Pay attention to Android Gradle updates

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"

java.lang.OutOfMemoryError

Resource Resource Resource Resource

Merge Resources TaskAndroid Gradle v0.4

Resource Resource Resource Resource

Merge Resources TaskAndroid Gradle v0.5.4

MergedResource

Use extra properties to configure on a per-flavor

basis

build.gradle

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

build.gradle

android.productFlavors.each { ext.crashlyticsEnabled }

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

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

plugin code.groovy

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

apply plugin: ‘com.android.application’

android { productFlavors { paid { } } }

apply plugin: ‘io.fabric’

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

Crashlytics used HttpClient 4.3

Android Gradle used HttpClient 4.1.1

command line

./gradlew assemble

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

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

Use the Shadow Plugin: Bytecode Manipulation

apply plugin: ‘shadow’

Avail. on github john engelman

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

shadow { relocation { … } }

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

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

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

Simple PossiblePossimple

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

Jake Ouellette @jakeout

Senior Software Engineer Twitter

Gradle Plugin Syntax has growing pains

buildscript { … }

buildscript { repositories { … } }

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

def crashlytics = … buildscript { … }

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

No such property: crashlytics

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.

./gradlew assemble -Dcrashlytics=…

One easy workaround

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

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

buildScript { ext.crashlytics = … }

buildscript.ext.crashlytics

Remember, build script is just an object like everything else