Slide 1

Slide 1 text

Level Up Your Android Build Florian Mierzejewski, Volker Leck Novoda - Berlin

Slide 2

Slide 2 text

Getting started

Slide 3

Slide 3 text

app/build.gradle apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion '23.0.1' defaultConfig { ... } buildTypes { release { ... } } } dependencies { ... }

Slide 4

Slide 4 text

Relevant Build Files ├── app │ └── build.gradle ├── build.gradle ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle

Slide 5

Slide 5 text

Gradle wrapper $ ./gradlew tasks

Slide 6

Slide 6 text

Gradle wrapper $ gw clean aD https://github.com/dougborg/gdub

Slide 7

Slide 7 text

Using a Gradle plugin /build.gradle buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.1' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' } } app/build.gradle apply plugin: 'com.novoda.android-command' $ gw runDebug https://github.com/novoda/gradle-android-command-plugin

Slide 8

Slide 8 text

Dependencies dependencies { compile 'com.google.guava:guava:18.0' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.9.5' }

Slide 9

Slide 9 text

Dependencies apply plugin: 'com.neenbedankt.android-apt' dependencies { apt 'com.google.dagger:dagger-compiler:2.0' compile 'com.google.dagger:dagger:2.0' compile 'com.google.guava:guava:18.0' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.9.5' } https://bitbucket.org/hvisser/android-apt

Slide 10

Slide 10 text

Dependencies apply plugin: 'com.neenbedankt.android-apt' dependencies { apt 'com.google.dagger:dagger-compiler:2.0' compile 'com.google.dagger:dagger:2.0' compile 'com.google.guava:guava:18.0' paidCompile project(':lvl') testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.9.5' }

Slide 11

Slide 11 text

Proguard

Slide 12

Slide 12 text

Proguard buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg' } } proguard.cfg #Dagger2 -dontwarn dagger.shaded.auto.common.** -dontwarn dagger.internal.codegen.** -keepattributes Signature

Slide 13

Slide 13 text

Archive Release ext.VERSION_NAME=1.0.0 ... task archiveRelease(type: Copy) { from 'build/outputs/apk', 'build/outputs/' into "releases/${VERSION_CODE}" include('android-release.apk', 'mapping/**') rename ('android-release.apk', "${APP_NAME}_${BUILD_DATE}_${VERSION_NAME}_${VERSION_CODE}.apk") }

Slide 14

Slide 14 text

First Success

Slide 15

Slide 15 text

Scaling up ● re-use ● testing ● quality assurance ● continuous integration ● push-button release

Slide 16

Slide 16 text

Re-use - Core project dependencies { compile project(':core') } app/build.gradle core/ gradle init --type java-library include ':core' settings.gradle

Slide 17

Slide 17 text

Re-use - Annotation processing apply plugin: 'net.ltgt.apt' dependencies { apt 'com.google.auto.value:auto-value:1.1' compile 'com.squareup.okhttp:okhttp:2.5.0' ... } core/build.gradle https://github.com/tbroyer/gradle-apt-plugin

Slide 18

Slide 18 text

gradle init --type groovy-library Re-use - Script client scripts/ dependencies { compile project(':core') } scripts/build.gradle include ':scripts' settings.gradle

Slide 19

Slide 19 text

Re-use - Script client def operations = createUserOperations() (1..100).each { try { def profile = operations.readProfile(it) println profile } catch (e) { println e.message } } scripts/src/main/groovy/UserExplorer.groovy

Slide 20

Slide 20 text

Re-use - Reducing duplication rootproject.children.each { project -> project.buildFileName = "${project.name}.gradle" } settings.gradle

Slide 21

Slide 21 text

Testing - Integration Tests in Core sourceSets { integrationTest { java { srcDir 'src/integrationtest/java' } resources { srcDir 'src/integrationtest/resources' } compileClasspath += sourceSets.main.runtimeClasspath } } task integrationTest(type: Test) { testClassesDir = sourceSets.integrationTest.output.classesDir classpath += sourceSets.integrationTest.runtimeClasspath }

Slide 22

Slide 22 text

https://github.com/ben-manes/gradle-versions-plugin Keeping Up To Date apply plugin: 'com.github.ben-manes.versions' $ gw dependencyUpdates The following dependencies have later milestone versions: - com.fasterxml.jackson.core:jackson-annotations [2.1.2 -> 2.6.0- rc1] - com.fasterxml.jackson.core:jackson-core [2.1.1 -> 2.6.0-rc1] - com.fasterxml.jackson.core:jackson-databind [2.1.2 -> 2.6.0-rc1] - com.github.kevinsawicki:http-request [4.0 -> 9.9] - joda-time:joda-time [2.3 -> 2.8] - org.mockito:mockito-core [1.9.0 -> 2.0.8-beta] Generated report file build/dependencyUpdates/report.txt

Slide 23

Slide 23 text

Code Quality Tools apply plugin: 'checkstyle' task checkstyleMain( type: Checkstyle) { description 'Checks whether code complies with coding rules.' ignoreFailures !project.failFastOnError ... doLast { if (project.hasProperty('checkstyle.html')) { ant.xslt(in: reports.xml.destination, style: file("$rulesDir/checkstyle/noframes-sorted. xsl"), out: new File(reports.xml.destination.parent, name - 'checkstyle' + '.html')) } } }

Slide 24

Slide 24 text

Code Quality Tools apply plugin: 'checkstyle' task checkstyleMain(type: Checkstyle) { description 'Checks whether code complies with coding rules.' ignoreFailures !project. failFastOnError ... doLast { if (project.hasProperty(' checkstyle.html')) { ant.xslt(in: reports.xml.destination, style: file("$rulesDir/checkstyle/noframes-sorted. xsl"), out: new File(reports.xml.destination.parent, name - 'checkstyle' + '.html')) } } } $ gw checkstyleMain -PfailFastOnError -Pcheckstyle.html

Slide 25

Slide 25 text

Code Quality Tools apply plugin: 'checkstyle' task checkstyleMain(type: Checkstyle) { description 'Checks whether code complies with coding rules.' ignoreFailures !project.failFastOnError ... doLast { if (project.hasProperty('checkstyle.html')) { ant.xslt(in: reports.xml.destination, style: file("$rulesDir/checkstyle/noframes-sorted. xsl"), out: new File(reports.xml.destination.parent, name - 'checkstyle' + '.html')) } } }

Slide 26

Slide 26 text

Continuous Integration System.getenv("...") Jenkins BUILD_NUMBER

Slide 27

Slide 27 text

Updating the Android SDK apply plugin: 'android-sdk-manager' $ gw assembleDebug Android SDK not found. Downloading... > Configuring > 1/5 projects > :android SDK extracted at '/Users/leck/.android-sdk'. Writing to local. properties. Build tools 21.1.2 missing. Downloading... > Configuring > 2/5 projects Platform tools missing. Downloading... Compilation API android-21 missing. Downloading… ... https://github.com/JakeWharton/sdk-manager-plugin

Slide 28

Slide 28 text

Releasing from CI ● update Version ● commit changes ● upload to Stores

Slide 29

Slide 29 text

Release from CI - Versioning def versionFile = file('version.properties') Version version = Version.read(versionFile) task incrementVersion << { Version.increment(versionFile) }

Slide 30

Slide 30 text

Release from CI - Versioning def versionFile = file('version.properties') Version version = Version.read(versionFile) task incrementVersion << { Version.increment(versionFile) } buildSrc └── src └── main └── groovy └── Version.groovy

Slide 31

Slide 31 text

Release from CI - git buildscript { dependencies { classpath 'org.ajoberstar:grgit:1.1.0' } } ext { git = org.ajoberstar.grgit.Grgit.open file('.') } git.with { add 'version.properties' commit(message: "Bumping version to $VERSION_CODE") push() } https://github.com/ajoberstar/grgit

Slide 32

Slide 32 text

Publishing - Github def release = repository.createRelease(releaseTag) .name(releaseName) .body(releaseBody) .commitish(commitish) .draft(draft) .create() assets.each { asset -> release.uploadAsset(asset, 'application/octet-stream') } buildSrc/src/main/groovy/ReleaseToGithub.groovy extends org.gradle.api.DefaultTask https://github.com/kohsuke/github-api

Slide 33

Slide 33 text

Publishing - Github applicationVariants.all { variant -> project.task("releaseGithub$variant.name", type: ReleaseToGithub) { releaseTag = "${new Date().format('YY.MM.dd')}" releaseName = buildReleaseName(variant) releaseBody = buildReleaseBody(variant) assets = files(apkFile, mappingFile) commitish = project.latestCommitHash ... } } app/build.gradle

Slide 34

Slide 34 text

Publishing - Google Play app/build.gradle apply plugin: 'com.github.triplet.play' adds Tasks: bootstrapReleasePlayResources publishApkRelease publishListingRelease publishRelease https://github.com/Triple-T/gradle-play-publisher

Slide 35

Slide 35 text

Tips & Tricks

Slide 36

Slide 36 text

Shared methods build.gradle File rules(String relativeFilePath) { file("shared/rules/$relativeFilePath") } app/build.gradle findbugs { excludeFilter rules('findbugs/exclude.xml') ... }

Slide 37

Slide 37 text

Shared dependencies dependencies.gradle ext { guava_lib = 'com.google.guava:guava:18.0' ... testing_libs = [junit_lib, assertJ_lib] } build.gradle apply from: 'dependencies.gradle' dependencies { compile guava_lib ... testCompile testing_libs }

Slide 38

Slide 38 text

Shared buildConfig - before android { ... buildTypes { debug { buildConfigField 'String', 'TAG', '"DEBUG_TAG"' ... } release { buildConfigField 'String', 'TAG', '"RELEASE_TAG"' ... } } } app/build.gradle

Slide 39

Slide 39 text

Shared buildConfig - after android { ... buildTypes { debug { tag 'DEBUG_TAG' ... } release { tag 'RELEASE_TAG' ... } } } app/build.gradle

Slide 40

Slide 40 text

Shared buildConfig - how ext.tag = { tag -> buildConfigField 'String', 'TAG', '"'+ tag +'"' } android { buildTypes.all { apply from: 'buildconfig.gradle', to: delegate } ... } buildconfig.gradle build.gradle

Slide 41

Slide 41 text

Auto Login task autoLogin(type: com.novoda.gradle.command.Input) { script { text 'my_fb_email@gmail.com' down() text 'awesomeSuperStrongPwd92' enter() } }

Slide 42

Slide 42 text

Sync images task syncPhotos(type: com.novoda.gradle.command.Files) { deviceId { def moto = android.command.devices().find { it.brand() == 'motorola' } moto.id } script { def backupDir = mkdir 'motoPhoto' list('/sdcard/DCIM/Camera/').findAll { image -> !new File(backupDir.path, image.name).exists() }.each { image -> pull image.path + image.name, backupDir.path } } }

Slide 43

Slide 43 text

Library Shrinking dependencies { classpath 'net.sf.proguard:proguard-gradle:4.11' } ... task shrinkGuava(type: proguard.gradle.ProGuardTask) { injars configurations.injar.files libraryjars configurations.libjar.files outjars file("build/libs/guava_${guavaVersion}_base.jar") configuration 'guava.proguard' } guava.proguard: -dontoptimize -dontobfuscate -keep public class com.google.common.base.** { public *; }

Slide 44

Slide 44 text

War story - Facebook keytool -exportcert -alias -keystore \ | openssl sha1 -binary \ | openssl base64 $ ga0RGNYHvNM5d0SLGQfpQWAPGJ8= Facebook docs on generating key hashes:

Slide 45

Slide 45 text

War story - Facebook def message = 'keytool error: java.io.IOException: Keystore was tampered with, or password was incorrect\\n' def digest = MessageDigest.getInstance('SHA-1') def digestBytes = digest.digest(message.bytes) println digestBytes.encodeBase64() $ ga0RGNYHvNM5d0SLGQfpQWAPGJ8=

Slide 46

Slide 46 text

War story - Facebook def signingMeta = com.android...tasks.SigningReportTask.metaClass MetaMethod metaFinger = signingMeta .getStaticMetaMethod('getFingerprint', java.security.cert.Certificate, String) signingMeta.static.getFingerprint = { Certificate cert, String alg -> def base64 = MessageDigest.getInstance(alg) .digest(cert.encoded).encodeBase64() metaFinger.invoke(delegate, cert, hashAlgorithm) + "\n$hashAlgorithm (base64): $base64" } $ gw app:signingReport

Slide 47

Slide 47 text

Groovy on Android buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.1.0' classpath 'org.codehaus.groovy:gradle-groovy-android-plugin:0.3.6' } } apply plugin: 'groovyx.grooid.groovy-android' dependencies { compile 'org.codehaus.groovy:groovy:2.4.3:grooid' }

Slide 48

Slide 48 text

Udacity course

Slide 49

Slide 49 text

Volker Leck devisnik / florianmski +VolkerLeck / +FlorianMierzejewski devisnik / florianmski Merci ! Florian Mierzejewski

Slide 50

Slide 50 text

TDTD - Tester-Driven Talk Development Thanks to the folks from Android Stammtisch Berlin for serving as Testers for a lot of the presented material. Upcoming book: Growing Subject-Oriented Talks, Guided by Testers

Slide 51

Slide 51 text

Resources (in order of appearance) Gradle https://github.com/gradle/gradle Android plugin for Gradle https://developer.android.com/tools/building/plugin-for-gradle.html gdub Tool https://github.com/dougborg/gdub Command Plugin https://github.com/novoda/gradle-android-command-plugin Gradle Plugin Portal http://plugins.gradle.org

Slide 52

Slide 52 text

Resources (in order of appearance) apt Plugin https://bitbucket.org/hvisser/android-apt Proguard Annotation Squadleader https://bitbucket.org/littlerobots/squadleader Proguard Snippets https://github.com/krschultz/android-proguard-snippets Gradle Udacity Course https://www.udacity.com/course/gradle-for-android-and-java--ud867

Slide 53

Slide 53 text

Resources (in order of appearance) Versions Plugin https://github.com/ben-manes/gradle-versions-plugin Code Analysis Plugin https://github.com/dlabs/soter Groovy library for git https://github.com/ajoberstar/grgit SDK Manager Plugin https://github.com/JakeWharton/sdk-manager-plugin

Slide 54

Slide 54 text

Resources (in order of appearance) Shrink Guava Script Snippet https://gist.github.com/devisnik/e54ea0a629adc82bcfa0 DexCount https://github.com/KeepSafe/dexcount-gradle-plugin Sqlite Analyzer Plugin https://github.com/novoda/sqlite-analyzer Groovy Android Plugin https://github.com/groovy/groovy-android-gradle-plugin

Slide 55

Slide 55 text

Images https://upload.wikimedia.org/wikipedia/commons/d/d9/Florida_Box_Turtle_Digon3a.jpg http://commons.wikimedia.org/wiki/File:Perspective-Button-Stop-icon.png http://commons.wikimedia.org/wiki/File:Up-1.svg http://commons.wikimedia.org/wiki/File:Emblem-money.svg http://commons.wikimedia.org/wiki/File:Hipsterale.jpg https://commons.wikimedia.org/wiki/File:Seal_performing_tricks.jpg