Plugin for Plugin, или расширяем Android New Build System. Антон Руткевич
Plugin for plugin, or extending android new build system
-
Upload
anton-rutkevich -
Category
Engineering
-
view
369 -
download
3
Transcript of Plugin for plugin, or extending android new build system
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
Senior software engineer