plugin for plugin, or extending android new build system

Post on 12-Jul-2015

369 Views

Category:

Engineering

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Plugin for plugin, or extending Android New Build System Anton Rutkevich

About me

›  4+ years of Android development

›  Mobile game-dev experience

›  At Yandex:

1.  Mobile Yandex.Metrica

2.  Continuous Integration

Не удается отобразить рисунок. Возможно, рисунок поврежден или недостаточно памяти для его открытия. Перезагрузите компьютер, а затем снова откройте файл. Если вместо рисунка все еще отображается красный крестик, попробуйте удалить рисунок и вставить его заново.

Intro

Why do I need this?

What can be done?

›  Additional resources/code/manifest processing

›  Output processing (apk, aar, jar)

›  Other things

Story Prod / test

servers Flavors!

Logs on/off Test / prod analytics Ads on/off Unique build

number! ...

I want to configure it

myself!

2 Flavors! 3 Flavors? Hmm...

Android dev Manager

How should it work

Java code build.gradle Teamcity

Actual value: "https://my.server.com"

CI server can do it

Our job

Insert CI value into BuildConfig.java

// app/build.gradle apply plugin: 'com.android.application' android { � defaultConfig { buildConfigField "String", "URL", "\"${teamcity['server-url']}\"" buildConfigField "String", "URL", "\"#server-url\"" } �} project.teamcity = [

"server-url" : "https://my.server.com" // ... �]

buildConfigField "String", "URL", "\"${teamcity['server-url']}\"".

Use BuildConfig.java from Java

public class SomeJavaClass { � // ... public static final String SERVER_URL = "https://my.server.com"; public static final String SERVER_URL = BuildConfig.URL; // ... }

public static final String SERVER_URL = "https://my.server.com";

BuildConfig placeholder plugin

›  Replaces placeholder values with values from some map

›  Map can come from anywhere

Goal

// app/build.gradle apply plugin: 'com.android.application' apply plugin: 'placeholder' �placeholder { � replacements = project.teamcity } android { � defaultConfig { � buildConfigField "String", "URL", "\"#server-url\"" � } �}

Table of contents

›  Gradle basics

›  New Build System workflow

›  Hello, Gradle plugin!

›  Extending Android New Build System

Не удается отобразить рисунок. Возможно, рисунок поврежден или недостаточно памяти для его открытия. Перезагрузите компьютер, а затем снова откройте файл. Если вместо рисунка все еще отображается красный крестик, попробуйте удалить рисунок и вставить его заново.

Gradle basics

Tools we will use

Plugins everywhere

NBS

Tasks

›  Can be configured with { }

›  Consist of actions

›  Can depend on other tasks

›  Can have inputs / outputs

Task consists of actions

Action Action

Action Action

doFirst() doLast(), or <<

Task

Tasks execution order

Task 2

Task 3

Task 4

Execution order

dependsOn

Task 1

Task 2 Task 3 Task 4 Task 1

dependsOn

dependsOn

Outputs

Task inputs / outputs

Task Inputs

Inputs & outputs did not change =>

UP-TO-DATE

Task example

task myTask { ext.myMessage = "hello" } myTask << { println myMessage } task otherTask(dependsOn: myTask)

Task example output

>> gradle otherTask :app:myTask hello :app:otherTask

Build lifecycle

Initialization Configuration Execution

settings.gradle

Projects creation Projects configuration Tasks creation Tasks configuration project.afterEvaluate { }

Task graph execution

Task graph

build.gradle

Build initialization

Не удается отобразить рисунок. Возможно, рисунок поврежден или недостаточно памяти для его открытия. Перезагрузите компьютер, а затем снова откройте файл. Если вместо рисунка все еще отображается красный крестик, попробуйте удалить рисунок и вставить его заново.

The New Build System workflow

What's so special?

What tasks will be launched?

build

check assemble

assembleDebug assembleRelease

assemble<VariantName> Guaranteed

Android project build overview

Tasks we will need

assemble<VariantName>

generate<VariantName>BuildConfig

compile<VariantName>Java

....

....

....

Variant API

Source code is your documentation!

›  Access to most variant's tasks

›  Variant output related properties

›  Different for apps & libraries

Не удается отобразить рисунок. Возможно, рисунок поврежден или недостаточно памяти для его открытия. Перезагрузите компьютер, а затем снова откройте файл. Если вместо рисунка все еще отображается красный крестик, попробуйте удалить рисунок и вставить его заново.

Hello, Gradle plugin!

The first steps

The very basic one

src/main/groovy/com/example/gradle/PlaceholderPlugin.gradle

public class PlaceholderPlugin implements Plugin<Project> { @Override � void apply(Project project) { project.task('hello') << { � println "Hello Gradle plugin!" } } }

Bind plugin class to plugin name

src/main/resources/META-INF/gradle-plugins/placeholder.properties

implementation-class=com.example.gradle.PlaceholderPlugin

Extension

Extension

Add extension

src/main/groovy/com/example/gradle/PlaceholderExtension.gradle

class PlaceholderExtension { � def replacements = [:] } �

Add extension

@Override �void apply(Project project) { � project.task('hello') << { println "Hello Gradle plugin!" println "Hi, ${project.placeholder.replacements}" } } �

PlaceholderExtension extension = project.extensions.create( � "placeholder", PlaceholderExtension�);

println "Hello Gradle plugin!"

Use extension

// app/build.gradle apply plugin: 'placeholder' ��placeholder { � replacements = ["server-url" : "https://my.server.com"] } �--------------------------------------------- >> gradle hello :app:hello�Hi, [server-url:https://my.server.com]

Не удается отобразить рисунок. Возможно, рисунок поврежден или недостаточно памяти для его открытия. Перезагрузите компьютер, а затем снова откройте файл. Если вместо рисунка все еще отображается красный крестик, попробуйте удалить рисунок и вставить его заново.

Extending The New Build System

Let's do it!

Check for New Build System

// PlaceholderPlugin.groovy @Override �void apply(Project project) { if (project.hasProperty("android")) { PlaceholderExtension extension = project.extensions.create( � "placeholder", PlaceholderExtension� ); def android = project.android // all code goes here } } �

Let the New Build System do its job

// PlaceholderPlugin.apply() if (project.hasProperty("android")) { PlaceholderExtension extension = project.extensions.create( � "placeholder", PlaceholderExtension� ); def android = project.android project.afterEvaluate { � // at this point we have all // tasks from New Build System } }

Process every variant

// PlaceholderPlugin.apply() project.afterEvaluate { if (android.hasProperty('applicationVariants')) { android.applicationVariants.all { variant -> addActions(project, variant, extension) } } else if (android.hasProperty('libraryVariants')) { android.libraryVariants.all { variant -> addActions(project, variant, extension) } } }

Add task

// PlaceholderPlugin.groovy def addActions(Project project, variant, PlaceholderExtension extension) { Task processPlaceholders = project.task( "process${variant.name.capitalize()}Placeholders" ) processPlaceholders << { println "I will replace ${variant.name}!" } }

Insert task into build process assemble<VariantName>

generate<VariantName>BuildConfig

compile<VariantName>Java

....

....

....

process<VariantName>Placeholders

Insert task into build process

// PlaceholderPlugin.groovy def addActions(Project project, variant, PlaceholderExtension extension) { Task processPlaceholders = ... ... variant.javaCompile.dependsOn processPlaceholders� processPlaceholders.dependsOn variant.generateBuildConfig }

Does it really work?

>> gradle assembleDebug :app:preBuild ... :app:generateDebugBuildConfig ... :app:processDebugPlaceholders I will replace debug! :app:compileDebugJava ...

Actual work. Perform replacements

// PlaceholderPlugin.addActions() processPlaceholders << { def buildConfigFile = getBuildConfig(variant) � extension.replacements.each { replacement -> � project.ant.replace( � file: buildConfigFile, � token: "#${replacement.key}", value: replacement.value � ) � } }

Handling inputs / outputs

process Placeholders

BuildConfig.java BuildConfig.java

replacements

generate BuildConfig

...

replacements << Action

BuildConfig.java

Replace task with 'doLast'

// PlaceholderPlugin.addActions() processPlaceholders << { variant.generateBuildConfig << { def buildConfigFile = ... extension.replacements.each { ... } } variant.generateBuildConfig.inputs.property( "replacements", extension.replacements )

processPlaceholders << {

│ We've done it!

Remember how to use it?

// app/build.gradle apply plugin: 'com.android.application' �apply plugin: 'placeholder' ��placeholder { � replacements = project.teamcity } ��android { � defaultConfig { � buildConfigField "String", "URL", "\"#server-url\"" � } �}

Does it work?

app/build/generated/source/buildConfig/debug/com/example/sample/BuildConfig.java

public final class BuildConfig { � // Fields from default config. � public static final String URL = "https://my.server.com"; }

Не удается отобразить рисунок. Возможно, рисунок поврежден или недостаточно памяти для его открытия. Перезагрузите компьютер, а затем снова откройте файл. Если вместо рисунка все еще отображается красный крестик, попробуйте удалить рисунок и вставить его заново.

To summarize

Key steps

›  Create Gradle plugin

›  Inside afterEvaluate { }

›  Process every variant

›  Add action to generateBuildConfig

›  Handle inputs / outputs

What's next?

›  Default values support

›  Errors handling

›  Publish ( jcenter / mavenCentral / other )

Links

Gradle http://www.gradle.org/

The New Build System http://tools.android.com/tech-docs/new-build-system http://tools.android.com/tech-docs/new-build-system/build-workflow

Github sample https://github.com/roottony/android-placeholder-plugin

Thank you for your attention!

anton.rutkevich@gmail.com

Anton Rutkevich

Senior software engineer

antonrut@yandex-team.ru

top related