Upload
yandex
View
102
Download
0
Tags:
Embed Size (px)
DESCRIPTION
Plugin for Plugin, или расширяем Android New Build System
Citation preview
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
The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the red x still appears, you may have to delete the image and then insert it again.
Intro
Why do I need this?
What can be done?
› Additional resources/code/manifest processing
› Output processing (apk, aar, jar)
› Other things
Flavors 2 Flavors!
Story
Prod / test servers Logs on/off Test / prod analytics Ads on/off Unique build number! ...
I want to configure it myself!
3 Flavors? Hmm…
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
The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the red x still appears, you may have to delete the image and then insert it again.
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 image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the red x still appears, you may have to delete the image and then insert it again.
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
The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the red x still appears, you may have to delete the image and then insert it again.
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]
The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the red x still appears, you may have to delete the image and then insert it again.
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"; }
The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the red x still appears, you may have to delete the image and then insert it again.
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