將構(gòu)建配置從 Groovy 遷移到 KTS
前言
作為Android
開發(fā)習(xí)慣了面向?qū)ο缶幊桃怀溃?xí)慣了IDEA
提供的各種輔助開發(fā)快捷功能靴拱。
那么帶有陌生的常規(guī)語法的Groovy
腳本對于我來說一向敬而遠(yuǎn)之悬蔽。
Kotlin DSL
的出現(xiàn)感覺是為了我們量身定做的,因?yàn)椴捎?Kotlin 編寫的代碼可讀性更高,并且 Kotlin 提供了更好的編譯時(shí)檢查和 IDE 支持。
名詞概念解釋
Gradle: 自動化構(gòu)建工具. 平行產(chǎn)品:
Maven
.Groovy: 語言, 編譯后變?yōu)?code>JVM byte code, 兼容
Java
平臺.DSL:
Domain Specific Language
, 領(lǐng)域特定語言.Groovy DSL:
Gradle
的API是Java的,Groovy DSL
是在其之上的腳本語言.Groovy DS
腳本文件后綴:.gradle
.KTS:是指 Kotlin 腳本比原,這是 Gradle 在構(gòu)建配置文件中使用的一種 Kotlin 語言形式。Kotlin 腳本是可從命令行運(yùn)行的 Kotlin 代碼杠巡。
Kotlin DSL:主要是指 Android Gradle 插件 Kotlin DSL量窘,有時(shí)也指底層 Gradle Kotlin DSL。
在討論從 Groovy 遷移時(shí)氢拥,術(shù)語“KTS”和“Kotlin DSL”可以互換使用蚌铜。換句話說,“將 Android 項(xiàng)目從 Groovy 轉(zhuǎn)換為 KTS”與“將 Android 項(xiàng)目從 Groovy 轉(zhuǎn)換為 Kotlin DSL”實(shí)際上是一個(gè)意思嫩海。
Groovy和KTS對比
類型 | Kotlin | Groovy |
---|---|---|
自動代碼補(bǔ)全 | 支持 | 不支持 |
是否類型安全 | 是 | 不是 |
源碼導(dǎo)航 | 支持 | 不支持 |
重構(gòu) | 自動關(guān)聯(lián) | 手動修改 |
優(yōu)點(diǎn):
- 可以使用
Kotlin
, 開發(fā)者可能對這個(gè)語言更熟悉更喜歡. -
IDE
支持更好, 自動補(bǔ)全提示, 重構(gòu),imports
等. - 類型安全:
Kotlin
是靜態(tài)類型. - 不用一次性遷移完: 兩種語言的腳本可以共存, 也可以互相調(diào)用.
缺點(diǎn)和已知問題:
目前冬殃,采用
KTS
的構(gòu)建速度可能比采用Groovy
慢(自測小demo耗時(shí)增加約40%(約8s))。Project Structure
編輯器不會展開在buildSrc
文件夾中定義的用于庫名稱或版本的常量叁怪。KTS
文件目前在項(xiàng)目視圖中不提供文本提示审葬。
Android構(gòu)建配置從Groovy遷移KTS
準(zhǔn)備工作
Groovy
字符串可以用單引號'string'
或雙引號"string"
引用,而Kotlin
需要雙引號"string"
奕谭。Groovy
允許在調(diào)用函數(shù)時(shí)省略括號涣觉,而Kotlin
總是需要括號。Gradle Groovy DSL
允許在分配屬性時(shí)省略=
賦值運(yùn)算符血柳,而Kotlin
始終需要賦值運(yùn)算符官册。
所以在KTS
中需要統(tǒng)一做到:
- 使用雙引號統(tǒng)一引號.
- 消除函數(shù)調(diào)用和屬性賦值的歧義(分別使用括號和賦值運(yùn)算符)。
腳本文件名
Groovy DSL 腳本文件使用 .gradle
文件擴(kuò)展名难捌。
Kotlin DSL 腳本文件使用 .gradle.kt
s 文件擴(kuò)展名攀隔。
一次遷移一個(gè)文件
由于您可以在項(xiàng)目中結(jié)合使用 Groovy build
文件和 KTS build
文件皂贩,因此將項(xiàng)目轉(zhuǎn)換為 KTS
的一個(gè)簡單方法是先選擇一個(gè)簡單的 build
文件(例如 settings.gradle
),將其重命名為 settings.gradle.kts
昆汹,然后將其內(nèi)容轉(zhuǎn)換為 KTS
明刷。之后,確保您的項(xiàng)目在遷移每個(gè) build
文件之后仍然可以編譯满粗。
自定義Task
由于Koltin
是靜態(tài)類型語言辈末,Groovy
是動態(tài)語言,前者是類型安全的映皆,他們的性質(zhì)區(qū)別很明顯的體現(xiàn)在了 task 的創(chuàng)建和配置上挤聘。詳情可以參考Gradle官方遷移教程
// 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")
}
}
使用 plugins
代碼塊
如果您在build
文件中使用 plugins
代碼塊,IDE
將能夠獲知相關(guān)上下文信息捅彻,即使在構(gòu)建失敗時(shí)也是如此组去。IDE
可使用這些信息執(zhí)行代碼補(bǔ)全并提供其他實(shí)用建議,從而幫助您解決 KTS
文件中存在的問題步淹。
在您的代碼中从隆,將命令式 apply plugin
替換為聲明式 plugins
代碼塊。Groovy
中的以下代碼…
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'androidx.navigation.safeargs.kotlin'
在 KTS 中變?yōu)橐韵麓a:
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
id("androidx.navigation.safeargs.kotlin")
}
如需詳細(xì)了解 plugins
代碼塊缭裆,請參閱 Gradle 的遷移指南键闺。
注意:plugins
代碼塊僅解析 Gradle 插件門戶中提供的插件或使用 pluginManagement
代碼塊指定的自定義存儲庫中提供的插件。如果插件來自插件門戶中不存在的 buildScript
依賴項(xiàng)澈驼,那么這些插件在 Kotlin 中就必須使用 apply
才能應(yīng)用辛燥。例如:
apply(plugin = "kotlin-android")
apply {
from("${rootDir.path}/config.gradle")
from("${rootDir.path}/version.gradle.kts")
}
如需了解詳情,請參閱 Gradle 文檔缝其。
強(qiáng)烈建議您
plugins {}
優(yōu)先使用塊而不是apply()
函數(shù)挎塌。有兩個(gè)關(guān)鍵的最佳實(shí)踐可以更輕松地在
Kotlin DSL
的靜態(tài)上下文中工作:
- 使用
plugins {}
塊- 將本地構(gòu)建邏輯放在構(gòu)建的buildSrc目錄中
該plugins {}塊是關(guān)于保持您的構(gòu)建腳本聲明性,以便充分利用
Kotlin DSL
内边。使用buildSrc項(xiàng)目是關(guān)于將您的構(gòu)建邏輯組織成共享的本地插件和約定榴都,這些插件和約定易于測試并提供良好的 IDE 支持。
依賴管理
常見依賴
// 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")))
特別類型庫依賴
//groovy
implementation(name: 'splibrary', ext: 'aar')
//kotlin
implementation (group="",name="splibrary",ext = "aar")
構(gòu)建變體
顯式和隱式 buildTypes
在 Kotlin DSL 中假残,某些 buildTypes
(如 debug
和 release,
)是隱式提供的缭贡。但是,其他 buildTypes
則必須手動創(chuàng)建辉懒。
例如阳惹,在 Groovy 中,您可能有 debug
眶俩、release
和 staging
buildTypes
:
buildTypes
debug {
...
}
release {
...
}
staging {
...
}
在 KTS 中莹汤,僅 debug
和 release
buildTypes
是隱式提供的,而 staging
則必須由您手動創(chuàng)建:
buildTypes
getByName("debug") {
...
}
getByName("release") {
...
}
create("staging") {
...
}
舉例說明
Grovvy
編寫:
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
編寫:
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") {
...
}
}
訪問配置
gradle.properties
我們通常會把簽名信息颠印、版本信息等配置寫在gradle.properties
中纲岭,在kotlin-dsl中我們可以通過一下方式訪問:
rootProject.extra.properties
project.extra.properties
rootProject.properties
properties
System.getProperties()
System.getProperties()
使用的限制比較多
- 參數(shù)名必須按照
systemProp.xxx
格式(例如:systemProp.kotlinVersion=1.3.72
); - 與當(dāng)前執(zhí)行的task有關(guān)(
> Configure project :buildSrc
和> Configure project :
的結(jié)果不同抹竹,后者無法獲取的gradle.properties
中的數(shù)據(jù));
local.properties
獲取工程的local.properties
文件
gradleLocalProperties(rootDir)
gradleLocalProperties(projectDir)
獲取系統(tǒng)環(huán)境變量的值
val JAVA_HOME:String = System.getenv("JAVA_HOME") ?: "default_value"
關(guān)于Ext
Google 官方推薦的一個(gè) Gradle 配置最佳實(shí)踐是在項(xiàng)目最外層 build.gradle 文件的ext
代碼塊中定義項(xiàng)目范圍的屬性,然后在所有模塊間共享這些屬性止潮,比如我們通常會這樣存放依賴的版本號窃判。
// build.gradle
ext {
compileSdkVersion = 28
buildToolsVersion = "28.0.3"
supportLibVersion = "28.0.0"
...
}
但是由于缺乏IDE的輔助(跳轉(zhuǎn)查看、全局重構(gòu)等都不支持)喇闸,實(shí)際使用體驗(yàn)欠佳袄琳。
在KTL
中用extra
來代替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
中的ext
數(shù)據(jù)是可以在build.gradle.kts
中使用extra
進(jìn)行訪問的。
修改生成apk名稱和BuildConfig中添加apk支持的cpu架構(gòu)
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架構(gòu)信息
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
我們在使用Groovy
語言構(gòu)建的時(shí)候燃乍,往往會抽取一個(gè)version_config.gradle
來作為全局的變量控制唆樊,而ext
擴(kuò)展函數(shù)則是必須要使用到的,而在我們的Gradle Kotlin DSL
中刻蟹,如果想要使用全局控制逗旁,則需要建議使用buildSrc
。
復(fù)雜的構(gòu)建邏輯通常很適合作為自定義任務(wù)或二進(jìn)制插件進(jìn)行封裝舆瘪。自定義任務(wù)和插件實(shí)現(xiàn)不應(yīng)存在于構(gòu)建腳本中片效。buildSrc
則不需要在多個(gè)獨(dú)立項(xiàng)目之間共享代碼,就可以非常方便地使用該代碼了介陶。
buildSrc
被視為構(gòu)建目錄堤舒。編譯器發(fā)現(xiàn)目錄后色建,Gradle
會自動編譯并測試此代碼哺呜,并將其放入構(gòu)建腳本的類路徑中。
- 先創(chuàng)建
buildSrc
目錄箕戳;- 在該目錄下創(chuàng)建
build.gradle.kts
文件某残;- 創(chuàng)建一個(gè)
buildSrc/src/main/koltin
目錄;- 在該目錄下創(chuàng)建
Dependencies.kt
文件作為版本管理類陵吸;
需要注意的是buildSrc
的build.gradle.kts
:
plugins {
`kotlin-dsl`
}
repositories {
jcenter()
}
或者
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()
}
不同版本之間buildSrc
下的build.gradle
文件執(zhí)行順序:
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
所以在非buildSrc
目錄下的build.gradle.kts
文件中我們使用Dependencies.kt
需要注意其加載順序玻墅。
參考文檔
Android官網(wǎng)-將構(gòu)建配置從 Groovy 遷移到 KTS
Migrating build logic from Groovy to Kotlin
GitHub:kotlin-dsl-samples/samples/hello-android
Kotlin DSL: Gradle scripts in Android made easy
文章到這里就全部講述完啦,若有其他需要交流的可以留言哦~壮虫!~澳厢!