I tutors Gradle! Kotlin and the buildSrc Plugin made me Love it
Pay attention to scientific Internet
How does Gradle help us build? Most people probably do
Picture taken from the above blog I tutors Gradle! Kotlin and the buildSrc Plugin made me love it
.
The truth that caused the problem
First things first: Android-gradle-plugin! = Gradle
The reason for the above problems is Groovy
Most Android developers don’t use Groovy in “real projects,” so for us the Build. gradle file in Android feels like magic in the hands of a magician.
.
Savior: Kotlin & the buildSrc Module?
On January 23, 2019, Kotlin 1.3.20 was released and provides support for Kotlin DSL build scripts in multi-platform projects.
Autocompletion can be done in the build script
We can write the code as before, read the documentation, click in and see the implementation, and no longer worry about it
Use the buildSrc folder
The function and use of builSrc will not be described here, there are many articles on the web
Gradle dependency management with Kotlin (buildSrc)
Kotlin + buildSrc for Better Gradle Dependency Management
As time goes on, the code in our build.gradle gets longer and longer
Configure product flavor, format apK file name and path, dynamically generate version number. If Jenkins needs to configure some independent logic, the file will get longer and longer
Our multiple Android Modules on the same project are mostly common sample code, and if configuration information needs to be changed, it needs to be changed in all the files
Although we can use apply form “…” To extract some of the logic into xx.gralde, you can use Google recommended Ext to configure the version number, targetVersion and other information.
But is it really flexible enough to expand?
So how do you use Kotlin + buildSrc to change the build file?
Let’s take a look before the changes. This is a very large file, containing nearly 200 lines of code
Click here to see the full code
.
Using the above method, you can refactor into an easy-to-understand piece of code in just 34 lines
.
Click here to see the full code
So what does this new script file do?
- Dependent project-specific plug-ins
com.quickbirdstudios.bluesqaure
- Project custom extensions are used
extension
- Defines module-specific dependencies
.
Let’s see how it works
Procedure Step 1 Configure the customized Gralde plug-in
- Create a name in the project root directory
buildSrc
folder - Create a build script for this folder,
buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
google()
jcenter()
}
dependencies {
/* Example Dependency */
/* Depend on the android gradle plugin, since we want to access it in our plugin */
implementation("Com. Android. Tools. Build: gradle: 3.5.3." ")
/* Example Dependency */
/* Depend on the kotlin plugin, since we want to access it in our plugin */
implementation("Org. Jetbrains. Kotlin: kotlin - gradle - plugin: 1.3.61")
/* Depend on the default Gradle API's since we want to build a custom plugin */
implementation(gradleApi())
implementation(localGroovy())
}
Copy the code
Now let’s implement our own plugin, for example: MyPlugin
.
For details, see the official documentation. How to create the Gralde Plugin
.
Kotlin language is used here
.
MyPlugin class file implements Plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
open class MyPlugin : Plugin<Project> {
override fun apply(project: Project){}}Copy the code
Be careful not to misguide the package
Configure the id of a custom plug-in, for example, com.plugin.test
To create this file buildSrc/SRC/main/resources/meta-inf/gradle – plugins/com. The plugin. The test. The properties
Com. The plugin. The test. The properties of the file name corresponds to the plug-in id
Add the full path of MyPlugin (including package name) to the file
implementation-class=com.test.MyPlugin
Copy the code
We can now use the plug-in in all modules
plugins {
// ...
id("com.android.library")
id("com.plugin.test")}Copy the code
This processing causes the apply function (method) inside our plug-in to be called
Step 2 Extract common code to a custom plug-in
We’ll have this code when we configure the Android profile
android {
compileSdkVersion(29)
// ...
}
Copy the code
Android {} code blocks are called extensions.
Does this function receiver implement AppExtension
Here is the source code
/** * Retrieves the [android][com.android.build.gradle.AppExtension] extension. */
val org.gradle.api.Project.`android`: com.android.build.gradle.AppExtension get() =
(this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("android") as com.android.build.gradle.AppExtension
/** * Configures the [android][com.android.build.gradle.AppExtension] extension. */
fun org.gradle.api.Project.`android`(configure: com.android.build.gradle.AppExtension. () -> Unit) :Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("android", configure)
Copy the code
.
We don’t need to configure all module build.gradle. KTS at once, we can extract this logic into a function when our plugin is applied:
// MyPlugin.kt
open class BluesquarePlugin : Plugin<Project> {
override fun apply(project: Project) {
project.configureAndroid()
}
}
// Android.kt
internal fun Project.configureAndroid(a) = this.extensions.getByType<AppExtension>().run {
compileSdkVersion(29)
defaultConfig {
minSdkVersion(21)
targetSdkVersion(28)
versionCode = 2
versionName = "1.0.1"
testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
getByName("debug") {
isTestCoverageEnabled = true
}
}
packagingOptions {
exclude("META-INF/NOTICE.txt")
// ...
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
Copy the code
.
It’s easy to create a new Android Module by applying our plugin com.test.myplugin to automatically configure the Android {} code block
plugins {
id("com.android.library")
id("com.test.MyPlugin")
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
testImplementation("Junit: junit: 4.12")
andriodTestImplementation("Com. Android. Support. Test: runner: 1.0.2")
androidTestImplementation("Com. Android. Support. Test. Espresso: espresso - core: 3.0.2." ")}Copy the code
Note: Simply reconfigure using the Android {} code block to override the preset configuration in the plug-in
,
Step 3 Configure default dependencies.
In general, we use the following dependencies for each Android
- Kotlin Standard library
- JUnit
- Support Test Runner
- Espresso
We can add another function, configureDependencies, to the plug-in
// MyPlugin.kt
open class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.configureAndroid()
project.configureDependencies()
}
}
// Dependencies.kt
const val jUnit = "Junit: junit: 4.12"
const val androidTestRunner = "Com. Android. Support. Test: runner: 1.0.2"
const val androidTestRules = "Com. Android. Support. Test: rules: 1.0.2"
const val mockkAndroid = "IO. Mockk: mockk - android: 1.9"
const val mockk = "IO. Mockk: mockk: 1.9"
const val espressoCore = "Com. Android. Support. Test. Espresso: espresso - core: 3.0.2." "
internal fun Project.configureDependencies(a) = dependencies {
add("testImplementation", jUnit)
if (project.containsAndroidPlugin()) {
add("androidTestImplementation", androidTestRunner)
add("androidTestImplementation", androidTestRules)
add("androidTestImplementation", espressoCore)
}
}
internal fun Project.containsAndroidPlugin(a): Boolean {
return project.plugins.toList().any { plugin -> plugin is AndroidBasePlugin }
}
Copy the code
.
Creating an Android Module now only requires
plugins {
id("com.android.library")
id("com.test.MyPlugin")}Copy the code
.
Step 4 Configure the default plug-in
There is a drawback to the above configuration: our configured plug-in will not work until the Android plugin is loaded.
But if all of our modules are Android modules, we can use custom plug-ins to manage them
// MyPlugin.kt
open class BluesquarePlugin : Plugin<Project> {
override fun apply(project: Project) {
project.configurePlugins()
project.configureAndroid()
project.configureDependencies()
}
}
//Plugins.kt
internal fun Project.configurePlugins(a) {
plugins.apply("com.android.library")
plugins.apply("org.gradle.maven-publish")
// Other public plug-ins
}
Copy the code
.
Now creating a new Android Module is just a matter of
plugins {
id("com.test.MyPlugin")}Copy the code
The above configuration allows us to apply the Android Library Plugin and configure the Android code block and default dependencies
.
What if you want to create a pure Java Module that doesn’t load Android Plguin?
Simple, we can create a few more plug-ins (don’t call them buildSrc)
For example, I can create MyBasePlugin, MyAndroidPlugin, MyJavaPlugin, MyMultiplatformPlugin
.
Make your plugin configurable
.
We can use the Android {} code block configuration in the Module where the Android Library or Android Application plug-in is applied
android {
compileSdkVersion(29)
}
Copy the code
.
The Gradle API defines this as an Extension
We can also define some custom extensions
.
For example, we want to deal with two configurations
- Has the Module been released?
- What is packageName when it is published?
.
Once configured, we can use it like this
plugins {
id("com.plugin.test")
}
test {
publish = true
packageName = "my-package"
}
Copy the code
.
We just need to create a class
open class TestExtension {
var publish: Boolean = false
var packageName: String = ""
}
Copy the code
.
Next, tell Gradle what configuration is available
// MyPlugin.kt
open class BluesquarePlugin : Plugin<Project> {
override fun apply(project: Project) {
val testExtension: TestExtension = project.extensions.create(
"test", TestExtension::class.java
)
project.configurePlugins()
project.configureAndroid()
project.configureDependencies()
}
}
Copy the code
.
The authors conclude that their company uses the buildSrc Plugin to accomplish the following in just seven lines of code
- Configure our Android builds
- Apply default plugins
- Apply default dependencies
- Run a custom linter
- Run aggregated coverage reports
- Run aggregated test result reports
- Install default git hooks into the project
- Setup Multiplatform builds
- Setup publications for our library modules
- Deploy libraries to the correct repository (Snapshot/Release)
.
The above is the main information expressed by the original author, the original text please move
.
The demo code
.
English level is limited, if any mistakes please advise.