Migrating build configurations from Groovy to KTS
Article directory
- Migrating build configurations from Groovy to KTS
-
- foreword
- Comparison between Groovy and KTS
- Android build configuration migration from Groovy to KTS
- buildSrc
- reference documents
foreword
As Android
a developer, I am used to object-oriented programming, and I am used to IDEA
various auxiliary development shortcut functions provided.
Well, scripts with unfamiliar, conventional syntax Groovy
have always been my thing.
Kotlin DSL
The emergence of feels tailor-made for us, because code written in Kotlin is more readable, and Kotlin provides better compile-time checking and IDE support.
Noun concept explanation
-
Gradle : Automated build tool. Parallel products:
Maven
. -
Groovy : language, compiled into
JVM byte code
, compatibleJava
platform. -
DSL :
Domain Specific Language
, Domain Specific Language. -
Groovy DSL :
Gradle
The API is Java,Groovy DSL
which is a scripting language on top of it.Groovy DS
The script file suffix:.gradle
. -
KTS : Refers to Kotlin Script, a form of the Kotlin language that Gradle uses in build configuration files . Kotlin scripts are Kotlin code that can be run from the command line .
-
Kotlin DSL : mainly refers to the Android Gradle plugin Kotlin DSL , and sometimes refers to the underlying Gradle Kotlin DSL .
The terms "KTS" and "Kotlin DSL" are used interchangeably when discussing migration from Groovy. In other words, "convert an Android project from Groovy to KTS" and "convert an Android project from Groovy to Kotlin DSL" actually mean the same thing.
Comparison between Groovy and KTS
type | Kotlin | Groovy |
---|---|---|
Automatic code completion | support | not support |
Is it type safe | yes | no |
Source code navigation | support | not support |
refactor | automatic association | manual modification |
advantage:
- Can be used
Kotlin
, developers may be more familiar with this language and prefer it. IDE
Better support, autocompletion hints, refactoring,imports
etc.- Type Safety:
Kotlin
It is statically typed. - There is no need to migrate all at once: scripts in the two languages can coexist and call each other.
Cons and Known Issues:
-
Currently, the build speed
KTS
of may beGroovy
slower than that of adopting (about 40% (about 8s) of self-test small demo time-consuming). -
Project Structure
The editor does not expand constants defined inbuildSrc
folders for library names or versions. -
KTS
Files currently do not provide text hints in the project view .
Android build configuration migration from Groovy to KTS
Preparation
-
Groovy
Strings can be quoted with single'string'
or double quotes"string"
, while double quotesKotlin
are required"string"
. -
Groovy
Parentheses are allowed to be omitted when calling a function, whereas parentheses areKotlin
always required. -
Gradle Groovy DSL
It is allowed to omit the=
assignment , whereasKotlin
an assignment operator is always required.
So KTS
you need to do it uniformly in:
- Use double quotes to unify quotes.
- Disambiguate function calls and property assignments (using parentheses and assignment operators, respectively).
script file name
Groovy DSL script files use .gradle
the file extension.
Kotlin DSL script files use .gradle.kt
the s file extension.
Migrate files one at a time
Since you can combine Groovy build
files ,KTS build
an easy way to convert your project to is to first select a simple file (for example ), rename it to , and convert its contents to . Afterwards, make sure your project still compiles after migrating each file .KTS
build
settings.gradle
settings.gradle.kts
KTS
build
Custom Task
Since Koltin
it is a statically typed language and Groovy
a dynamic language, the former is type-safe, and the difference in their nature is clearly reflected in the creation and configuration of tasks. For details, please refer to the official Gradle migration tutorial
// groovy
task clean(type: Delete) {
delete rootProject.buildDir
}
// kotiln-dsl
tasks.register("clean", Delete::class) {
delete(rootProject.buildDir)
}
val clean by tasks.creating(Delete::class) {
delete(rootProject.buildDir)
}
open class GreetingTask : DefaultTask() {
var msg: String? = null
@TaskAction
fun greet() {
println("GreetingTask:$msg")
}
}
val msg by tasks.creating(GreetingTask::class) {
}
val testTask: Task by tasks.creating {
doLast {
println("testTask:Run")
}
}
val testTask2: Task = task("test2") {
doLast {
println("Hello, World!")
}
}
val testTask3: Task = tasks.create("test3") {
doLast {
println("testTask:Run")
}
}
Use plugins
code blocks
If you build
use plugins
code , IDE
you'll be able to get contextual information even when the build fails. IDE
This information can be used to perform code completion and provide other helpful suggestions to help you resolve issues with KTS
your files .
In your code, apply plugin
replace with declarative plugins
blocks. Groovy
The following code in...
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'androidx.navigation.safeargs.kotlin'
becomes the following code in KTS:
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
id("androidx.navigation.safeargs.kotlin")
}
For more information on plugins
code blocks, see Gradle's migration guide .
Note : plugins
Code blocks only resolve plugins provided in the Gradle Plugin Portal or in custom repositories specified with pluginManagement
code blocks. If plugins come from buildScript
dependencies , then those plugins must be used in Kotlin apply
to be applied. For example:
apply(plugin = "kotlin-android")
apply {
from("${
rootDir.path}/config.gradle")
from("${
rootDir.path}/version.gradle.kts")
}
See the Gradle documentation for details .
It is strongly recommended that you
plugins {}
use blocks in preference toapply()
functions.There are two key best practices to make it
Kotlin DSL
easier to work in the static context of :
- use
plugins {}
blocks- Put your local build logic in your build's buildSrc directory
The plugins {} block is about keeping your build scripts declarative to take full advantage
Kotlin DSL
.Using the buildSrc project is about organizing your build logic into shared native plugins and conventions that are easy to test and have good IDE support.
dependency management
common dependencies
// groovy
implementation project(':library')
implementation 'com.xxxx:xxxx:8.8.1'
// kotlin
implementation(project(":library"))
implementation("com.xxxx:xxx:8.8.1")
freeTree
// groovy
implementation fileTree(include: '*.jar', dir: 'libs')
//kotlin
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
special type library dependencies
//groovy
implementation(name: 'splibrary', ext: 'aar')
//kotlin
implementation (group="",name="splibrary",ext = "aar")
build variant
explicit and implicitbuildTypes
In the Kotlin DSL, some buildTypes
(like debug
and release,
) are provided implicitly. However, buildTypes
others must be created manually.
For example, in Groovy you might have debug
, release
and staging
buildTypes
:
buildTypes
debug {
...
}
release {
...
}
staging {
...
}
In KTS, only debug
and release
buildTypes
are provided implicitly, staging
while must be created manually by you:
buildTypes
getByName("debug") {
...
}
getByName("release") {
...
}
create("staging") {
...
}
for example
Grovvy
write:
productFlavors {
demo {
dimension "app"
}
full {
dimension "app"
multiDexEnabled true
}
}
buildTypes {
release {
signingConfig signingConfigs.signConfig
minifyEnabled true
debuggable false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
debuggable true
}
}
signingConfigs {
release {
storeFile file("myreleasekey.keystore")
storePassword "password"
keyAlias "MyReleaseKey"
keyPassword "password"
}
debug {
...
}
}
kotlin-KTL
write:
productFlavors {
create("demo") {
dimension = "app"
}
create("full") {
dimension = "app"
multiDexEnabled = true
}
}
buildTypes {
getByName("release") {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
isDebuggable = false
proguardFiles(getDefaultProguardFile("proguard-android.txtt"), "proguard-rules.pro")
}
getByName("debug") {
isMinifyEnabled = false
isDebuggable = true
}
}
signingConfigs {
create("release") {
storeFile = file("myreleasekey.keystore")
storePassword = "password"
keyAlias = "MyReleaseKey"
keyPassword = "password"
}
getByName("debug") {
...
}
}
access configuration
gradle.properties
We usually write signature information, version information and other configurations in gradle.properties
. In kotlin-dsl, we can access them in the following ways:
rootProject.extra.properties
project.extra.properties
rootProject.properties
properties
System.getProperties()
System.getProperties()
There are more restrictions on the use
- Parameter names must follow
systemProp.xxx
the format (eg:systemProp.kotlinVersion=1.3.72
); - It is related to the currently executed task (
> Configure project :buildSrc
and> Configure project :
the result is different, the latter cannot obtaingradle.properties
the data in the task);
local.properties
Get project local.properties
files
gradleLocalProperties(rootDir)
gradleLocalProperties(projectDir)
Get the value of the system environment variable
val JAVA_HOME:String = System.getenv("JAVA_HOME") ?: "default_value"
About Ext
A Gradle configuration best practice officially recommended by Google is ext
to define project-wide properties in the code block of the outermost build.gradle file of the project , and then share these properties among all modules. For example, we usually store the version number of dependencies in this way.
// build.gradle
ext {
compileSdkVersion = 28
buildToolsVersion = "28.0.3"
supportLibVersion = "28.0.0"
...
}
However, due to the lack of IDE assistance (jump view, global refactoring, etc. are not supported), the actual use experience is not good.
usedKTL
in place of inextra
Groovy
ext
// The extra object can be used for custom properties and makes them available to all
// modules in the project.
// The following are only a few examples of the types of properties you can define.
extra["compileSdkVersion"] = 28
// You can also create properties to specify versions for dependencies.
// Having consistent versions between modules can avoid conflicts with behavior.
extra["supportLibVersion"] = "28.0.0"
android {
// Use the following syntax to access properties you defined at the project level:
// rootProject.extra["property_name"]
compileSdkVersion(rootProject.extra["sdkVersion"])
// Alternatively, you can access properties using a type safe delegate:
val sdkVersion: Int by rootProject.extra
...
compileSdkVersion(sdkVersion)
}
...
dependencies {
implementation("com.android.support:appcompat-v7:${
rootProject.ext.supportLibVersion}")
...
}
build.gralde
The data inext
is accessiblebuild.gradle.kts
using inextra
.
Modify the name of the generated apk and add the cpu architecture supported by the apk in BuildConfig
val abiCodes = mapOf("armeabi-v7a" to 1, "x86" to 2, "x86_64" to 3)
android.applicationVariants.all {
val buildType = this.buildType.name
val variant = this
outputs.all {
val name =
this.filters.find {
it.filterType == com.android.build.api.variant.FilterConfiguration.FilterType.ABI.name }?.identifier
val baseAbiCode = abiCodes[name]
if (baseAbiCode != null) {
//写入cpu架构信息
variant.buildConfigField("String", "CUP_ABI", "\"${
name}\"")
}
if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
//修改apk名称
if (buildType == "release") {
this.outputFileName = "KotlinDSL_${
name}_${
buildType}.apk"
} else if (buildType == "debug") {
this.outputFileName = "KotlinDSL_V${
variant.versionName}_${
name}_${
buildType}.apk"
}
}
}
}
buildSrc
When we use Groovy
language construction, we often extract one version_config.gradle
as a global variable control, and ext
the extension function must be used. In our system Gradle Kotlin DSL
, if we want to use global control, we need to recommend it buildSrc
.
Complex build logic is often well suited for packaging as custom tasks or binary plugins. Custom tasks and plugin implementations should not exist in build scripts. buildSrc
Then you don't need to share the code between multiple independent projects, and you can use the code very conveniently.
buildSrc
Treated as a build directory. Once the compiler discovers the directory, Gradle
it automatically compiles and tests this code and puts it in the build script's classpath.
- Create a directory first
buildSrc
;- Create files in this directory
build.gradle.kts
;- Create a
buildSrc/src/main/koltin
directory;- Create a file in this directory
Dependencies.kt
as a version management class;
Note buildSrc
that build.gradle.kts
:
plugins {
`kotlin-dsl`
}
repositories {
jcenter()
}
or
apply {
plugin("kotlin")
}
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath(kotlin("gradle-plugin", "1.3.72"))
}
}
//dependencies {
// implementation(gradleKotlinDsl())
// implementation(kotlin("stdlib", "1.3.72"))
//}
repositories {
gradlePluginPortal()
}
File execution order buildSrc
between different versions :build.gradle
gradle-wrapper.properties:5.6.4
com.android.tools.build:gradle:3.2.0
BuildSrc:build.gradle
setting.gradle
Project:build.gradle
Moudle:build.gradle
gradle-wrapper.properties:6.5
com.android.tools.build:gradle:4.1.1
setting.gradle
BuildSrc:build.gradle
Project:build.gradle
Moudle:build.gradle
Therefore, we need to pay attention to the loading order of files buildSrc
in non-directories .build.gradle.kts
Dependencies.kt
reference documents
Android Official Website - Migrate build configuration from Groovy to KTS
Migrating build logic from Groovy to Kotlin
GitHub:kotlin-dsl-samples/samples/hello-android
Kotlin DSL: Gradle scripts in Android made easy
buildSrc Official Documentation
The article is all told here, if you have other needs to communicate, you can leave a message~! ~!
If you want to read more articles by the author, you can check out my personal blog and public account: