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