gradle user to addict sf - meetupfiles.meetup.com/1715787/gradle_user_to_addict.pdf · build.gradle...
TRANSCRIPT
Gradle: From User to Addict
Simple Possible
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.+' }
I want to add a task!
build.gradle
apply plugin: ‘com.android.application’
build.gradle
android { compileSdkVersion 19 … }
build.gradle
dependencies { compile 'com.android.support:appcompat-v7:19.+' compile fileTree(dir: 'libs', include: ['*.jar']) }
build.gradle
dependencies { compile 'com.android.support:appcompat-v7:19.+' compile fileTree(dir: 'libs', include: ['*.jar']) }
build.gradle
dependencies { compile 'com.android.support:appcompat-v7:19.+' compile fileTree(dir: 'libs', include: ['*.jar']) }
What is the Gradle language really doing?
How do I use it?
• create tasks • make a multi-app project • write builds once, reuse
Simple PossibleSimpossible
dependencies { compile 'com.crashlytics.android:crashlytics:1.+' }
build.gradle
build.gradle
android { dependencies { compile 'com.crashlytics.android:crashlytics:1.+' } }
build.gradle
android { … }
dependencies { compile 'com.crashlytics.android:crashlytics:1.+' }
{ }
example.java
new Runnable() { public void run() { … } }
example.java
final int time = System.currentTimeMillis() new Runnable() { public void run() { … } }
example.java
final int time = System.currentTimeMillis() new Runnable() { public void run() { System.out.println(time); } }
example.java
final int time = System.currentTimeMillis()
{ System.out.println(time); }
build.gradle
android { … }
build.gradle
android({ … })
android() =
android.apply()
android.apply(Closure configuration)
build.gradle
android.apply({ … })
build.gradle
android.apply({ it })
build.gradle
println(android.getClass()) android { println(it.getClass()) }
command line output
class com.android.build.gradle.AppExtension_Decorated class com.android.build.gradle.AppExtension_Decorated
println(android.getClass()) android { println(it.getClass()) }
build.gradle
closure.delegate = this
build.gradle
android.apply({ it })
build.gradle
android { defaultConfig { minSdkVersion 8 } }
defaultConfig.apply(Closure configuration)
build.gradle
android { defaultConfig { minSdkVersion 8 } }
build.gradle
android { it.defaultConfig { minSdkVersion 8 } }
build.gradle
println(android.defaultConfig.getClass()) android { defaultConfig { println(it.getClass()) } }
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()) } }
build.gradle
android { defaultConfig { minSdkVersion 8 } }
build.gradle
android { defaultConfig { minSdkVersion 8 println(minSdkVersion) } }
build.gradle
android { defaultConfig { setMinSdkVersion(8) } }
build.gradle
android.defaultConfig.minSdkVersion = 8
build.gradle
android.defaultConfig.minSdkVersion
build.gradle
android { dependencies { compile 'com.crashlytics.android:crashlytics:1.+' } }
build.gradle
repositories { maven { url “example" } }
def maven(Closure closure)
MavenArtifactRepository maven(Closure closure)
build.gradle
repositories { println maven { url "example" }.url }
command line output
example
MavenArtifactRepository maven(Closure closure)
mutation
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.+' } }
build.gradle
SEARCHED SECOND android { SEARCHED FIRST
dependencies { compile 'com.crashlytics.android:crashlytics:1.+' } }
Adding a task
Adding a task: Cache each build.gradle with a
timestamp
build.gradle
task copyTask(type: Copy)
build.gradle
task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
build.gradle
task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
build.gradle
task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
build.gradle
task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
command line output
:app:copyTask UP-TO-DATE
BUILD SUCCESSFUL
ideal directory listing
buildCache/ build.gradle1399906750071 build.gradle1401281449000 build.gradle1401290623000
command line output
> ls app/buildCache …
build.gradle
task copyTask
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
task copyTask
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
task copyTask
build.gradle
task(“copyTask”)
build.gradle
task copyTask(type: Copy)
build.gradle
task(“copyTask”, [“type” : “Copy”])
groovy map
Task
task(“copyTask”, [“type” : “Copy”])
build.gradle
def apply(Closure closure)
build.gradle
task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
command line output
:app:copyTask
command line output
> ls app/buildCache
build.gradle1399906750071
build.gradle
task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
build.gradle
task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
<< { }
.doLast({ })
Executes?
command line
./gradlew 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)
build.gradle
task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
build.gradle
task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
build.gradle
task copyTask(type: Copy) { … } << { println(“Execution”) }
build.gradle
task copyTask(type: Copy) { … } << { println(“Execution”) }
build.gradle
task copyTask << { // Roll my own }
build.gradle
Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis()))
build.gradle
Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis()))
build.gradle
Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis()))
build.gradle
task copyTask << { Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis())) }
TASK
TASKTASK
TASK
@Input @InputDirectory @InputFile @InputFiles
TASK
@OutputDirectories @OutputDirectory @OutputFile @OutputFiles
TASK
TASK
TASK
TASK
TASKTASK
TASK TASK EXECUTED
TASKTASK
TASK TASK EXECUTED
TASKTASK
TASK 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 }
build.gradle
task copyTask { inputs.file new File("build.gradle"), // outputs } << { // Java file copy code }
build.gradle
task example
HAS NO INPUTS YET, ALWAYS RUNS
build.gradle
task copyTask(type: Copy)
COPY TASK HAS EMPTY SET OF THINGS TO COPY
build.gradle
task copyTask(type: Copy) << {
}
COPY TASK HAS EMPTY SET OF THINGS TO COPY
// CODE WILL NOT EXECUTE
build.gradle
task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
Good
build.gradle
task copyTask(type: Copy) << { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
BAD
build.gradle
task copyTask << { Files.copy( new File("build.gradle"), new File(“${project.projectDir}/buildCache/build.gradle" + System.currentTimeMillis())) }
Weird
command line
./gradlew tasks
command line output
Build tasks ----------- assemble assembleDebug assembleDebugTest assembleRelease build buildDependents buildNeeded clean
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
Compile
Dependency Graph
PreBuild Assemble
call?
Compile Assemble
Compile Assemble
assemble.dependsOn(task)task.dependsOn(compile)
AssembleCompile
task.onlyIf { condition }
Compile Task
Dependency GraphCrashlytics Task
Crashlytics Task
Multi-Project Builds
opensource/library/
day-job/app/
development/app/library/
command line
cd development/app/
command line
ant build
development/app/library/
App
Project Root Library
root/app/library/build.gradle
development/
command line
cd development/root
command line
./gradlew app:assemble
build.gradle
dependencies { compile project(‘:library’) }
include ‘library’ include ‘app’
settings.gradle
opensource/library/
day-job/app/
AppLibrary
Installs Into Depends
OnLocal Maven Cache
build.gradle
repositories { mavenLocal() }
app library
ExecutionC
onfiguration./gradlew customBuild
Tasks Executed
Tasks Executed
app library Maven Local
ExecutionC
onfigurationEXEC: ./gradlew :library:install
Tasks Executed
app library Maven Local
ExecutionC
onfiguration
Tasks Executed
EXEC: ./gradlew :app:install
AppProject Root
Library
app library
ExecutionC
onfiguration
Tasks Executed
EXEC: ./gradlew :app:install
Tasks Executed
-P
./gradlew :app:assemble -PrunOnCi=false
if (runOnCi) { … }
build.gradle
App
App2
Project Root
Library
./gradlew :app:assemble -PrunOnCi=false
app library
ExecutionC
onfiguration
Tasks Executed
EXEC: ./gradlew :app:assemble -PrunOnCi=false
Tasks Executed
app2
BUILD SUCCESSFUL
App
App2
Project Root
Library
./gradlew :app2:assemble
app library
ExecutionC
onfigurationEXEC: ./gradlew :app2:assemble
app2
BUILD FAILED
BUILD FAILED
app library
ExecutionC
onfigurationEXEC: ./gradlew :app2:assemble
app2
BUILD FAILED
if (runOnCi)
if (hasProperty(runOnCi)) { … }
org.gradle.configureondemand=true
gradle.properties
app library
ExecutionC
onfigurationEXEC: ./gradlew :app2:assemble
Tasks Executed
app2
Tasks Executed
Code Reuse
build.gradle
task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
Good
build.gradle
task copyTask(type: Copy) { from ‘build.gradle’ into ‘buildCache’ rename {it + System.currentTimeMillis()} }
Good?
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()} }
build.gradle
class CacheBuild extends Copy {
}
build.gradle
class CacheBuild extends Copy { { … } }
plain ol’ java initializer
build.gradle
class CacheBuild extends Copy { { from('build.gradle'); into('buildCache'); rename({ it + System.currentTimeMillis() }) } }
build.gradle
task cache(type:CacheBuild)
AppProject Root
Library
App
Project Root
Library
App
Project Root
Library
Custom Code
App
Project Root
Library
App
Project Root
Library
buildsrc/src/main/groovy/CacheBuild.groovy
Custom Code
App
Project Root
Library
buildsrc/src/main/groovy/CacheBuild.groovy
App
Project Root
Library
buildsrc/src/main/groovy/CacheBuild.groovy
day-job/app
AppCustom Task
Installs Into Depends
OnLocal Maven Cache
build.gradle
assemble.dependsOn(‘cache’)
build.gradle
apply plugin: 'BuildCache'
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
build.gradle
class BuildCache implements Plugin<Project> {
void apply(Project project) {
Inject into dependency graph
compile.dependsOn(buildCache)
build.gradle
buildCache = … android.applicationVariants.all { assemble.dependsOn(buildCache) }
build.gradle
ExampleActivity.onClick()a.b()com.crashlytics.obfuscation.
Building on top of Android
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’
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 }
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’
org.apache -> com.crashlytics.org.apache
shadow { relocation { … } }
shadow { relocation { pattern = 'org.apache' shadedPattern = ‘com.crashlytics’ + pattern } }
Showed what Gradle is really doing.
• created tasks • made a multi-app project • wrote a plugin
Simple PossiblePossimple
Jake Ouellette @jakeout
Senior Software Engineer Twitter
Gradle Plugin Syntax has growing pains
buildscript { … }
buildscript { repositories { … } }
def crashlytics = … buildscript { … }
No such property: crashlytics
./gradlew assemble -Dcrashlytics=…
buildscript { repositories { maven { url System.getProperty(“crashlytics”) } } }
repositories { maven { url System.getProperty(“crashlytics”) } }
buildScript { ext.crashlytics = … }
buildscript.ext.crashlytics