המאמר הזה מיועד למפתחי פלאגינים, והוא מסביר איך לזהות נכון את ההגדרה של Kotlin Multiplatform (KMP), איך ליצור איתה אינטראקציה ואיך להגדיר אותה. המאמר מתמקד במיוחד בשילוב עם יעדי Android בפרויקט KMP.
ההמלצות האלה רלוונטיות גם אם אתם יוצרים פלאגינים של מוסכמות כדי לתקנן את ההגדרות במודולים של הפרויקט, וגם אם אתם מפתחים פלאגינים לשימוש נרחב יותר בקהילה. כדי ליצור כלי פיתוח חזקים ועמידים לעתיד שפועלים בצורה חלקה בכל הפלטפורמות שמוגדרות בפרויקט מרובה פלטפורמות, חשוב להבין את ה-API וה-hooks המתאימים, כמו KotlinMultiplatformExtension, סוגי KotlinTarget וממשקי השילוב הספציפיים ל-Android.
איך בודקים אם פרויקט משתמש בפלאגין Kotlin Multiplatform
כדי להימנע משגיאות ולוודא שהתוסף יפעל רק אם יש KMP, צריך לבדוק אם הפרויקט משתמש בתוסף KMP. השיטה המומלצת היא להשתמש ב-plugins.withId() כדי להגיב להחלת התוסף KMP, במקום לבדוק אותו באופן מיידי. הגישה הזו מונעת מהתוסף להיות שביר ביחס לסדר שבו התוספים מוחלים בסקריפטים של בניית המשתמש.
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
// The KMP plugin is applied, you can now configure your KMP integration.
}
}
}
גישה למודל
נקודת הכניסה לכל ההגדרות של Kotlin Multiplatform היא התוסף KotlinMultiplatformExtension.
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
}
}
}
תגובה ליעדים של Kotlin Multiplatform
משתמשים ב-container targets כדי להגדיר את הפלאגין באופן ריאקטיבי לכל יעד שהמשתמש מוסיף.
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.configureEach { target ->
// 'target' is an instance of KotlinTarget
val targetName = target.name // for example, "android", "iosX64", "jvm"
val platformType = target.platformType // for example, androidJvm, jvm, native, js
}
}
}
}
החלת לוגיקה ספציפית לטירגוט
אם התוסף צריך להחיל לוגיקה רק על סוגים מסוימים של פלטפורמות, גישה נפוצה היא לבדוק את המאפיין platformType. זוהי ספירה שמסווגת את היעד באופן כללי.
לדוגמה, אפשר להשתמש בזה אם הפלאגין צריך רק להבחין באופן כללי (למשל, להפעיל רק ביעדים דומים ל-JVM):
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.configureEach { target ->
when (target.platformType) {
KotlinPlatformType.jvm -> { /* Standard JVM or Android */ }
KotlinPlatformType.androidJvm -> { /* Android */ }
KotlinPlatformType.js -> { /* JavaScript */ }
KotlinPlatformType.native -> { /* Any Native (iOS, Linux, Windows, etc.) */ }
KotlinPlatformType.wasm -> { /* WebAssembly */ }
KotlinPlatformType.common -> { /* Metadata target (rarely needs direct plugin interaction) */ }
}
}
}
}
}
פרטים ספציפיים ל-Android
לכל יעדי Android יש את האינדיקטור platformType.androidJvm, אבל ל-KMP יש שתי נקודות שילוב שונות, בהתאם לפלאגין של Android Gradle שבו נעשה שימוש: KotlinAndroidTarget לפרויקטים שמשתמשים ב-com.android.library או ב-com.android.application, ו-KotlinMultiplatformAndroidLibraryTarget לפרויקטים שמשתמשים ב-com.android.kotlin.multiplatform.library.
API KotlinMultiplatformAndroidLibraryTarget נוסף ב-AGP 8.8.0, ולכן אם הצרכנים של הפלאגין שלכם מריצים גרסה ישנה יותר של AGP, בדיקה של target is KotlinMultiplatformAndroidLibraryTarget עשויה להוביל ל-ClassNotFoundException. כדי לוודא שהפעולה הזו בטוחה, צריך לסמן את התיבה AndroidPluginVersion.getCurrent() לפני שמסמנים את סוג היעד.
שימו לב שנדרשת גרסה 7.1 ואילך של AGP כדי להשתמש ב-AndroidPluginVersion.getCurrent().
import com.android.build.api.AndroidPluginVersion
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.configureEach { target ->
if (target is KotlinAndroidTarget) {
// Old kmp android integration using com.android.library or com.android.application
}
if (AndroidPluginVersion.getCurrent() >= AndroidPluginVersion(8, 8) &&
target is KotlinMultiplatformAndroidLibraryTarget
) {
// New kmp android integration using com.android.kotlin.multiplatform.library
}
}
}
}
}
גישה לתוסף Android KMP ולמאפיינים שלו
הפלאגין שלכם יקיים אינטראקציה בעיקר עם התוסף Kotlin שסופק על ידי הפלאגין Kotlin Multiplatform, ועם התוסף Android שסופק על ידי AGP עבור יעד KMP Android. הבלוק android {} בתוסף Kotlin בפרויקט KMP מיוצג על ידי הממשק KotlinMultiplatformAndroidLibraryTarget, שמרחיב גם את KotlinMultiplatformAndroidLibraryExtension.
כלומר, אפשר לגשת דרך האובייקט הזה גם למאפייני DSL ספציפיים לטירגוט וגם למאפייני DSL ספציפיים ל-Android.
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
// Access the Android target, which also serves as the Android-specific DSL extension
kmpExtension.targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java).configureEach { androidTarget ->
// You can now access properties and methods from both
// KotlinMultiplatformAndroidLibraryTarget and KotlinMultiplatformAndroidLibraryExtension
androidTarget.compileSdk = 34
androidTarget.namespace = "com.example.myplugin.library"
androidTarget.withJava() // enable Java sources
}
}
}
}
בשונה מתוספים אחרים ל-Android (כמו com.android.library או com.android.application), התוסף KMP Android לא רושם את התוסף הראשי של DSL ברמת הפרויקט. הוא נמצא בהיררכיית היעדים של KMP כדי לוודא שהוא חל רק על יעד Android הספציפי שהוגדר בהגדרה שלכם למספר פלטפורמות.
טיפול באוספים ובמקורות
לעתים קרובות, תוספים צריכים לפעול ברמה פרטנית יותר מאשר רק ברמת היעד – באופן ספציפי, הם צריכים לפעול ברמת הקומפילציה. ה-KotlinMultiplatformAndroidLibraryTarget
כולל KotlinMultiplatformAndroidCompilation מופעים (לדוגמה, main, hostTest, deviceTest). כל קובץ קומפילציה משויך לקבוצות של קוד מקור ב-Kotlin. תוספים יכולים ליצור אינטראקציה עם הקבצים האלה כדי להוסיף מקורות, תלות או להגדיר משימות קומפילציה.
import com.android.build.api.dsl.KotlinMultiplatformAndroidCompilation
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.configureEach { target ->
target.compilations.configureEach { compilation ->
// standard compilations are usually 'main' and 'test'
// android target has 'main', 'hostTest', 'deviceTest'
val compilationName = compilation.name
// Access the default source set for this compilation
val defaultSourceSet = compilation.defaultSourceSet
// Access the Android-specific compilation DSL
if (compilation is KotlinMultiplatformAndroidCompilation) {
}
// Access and configure the Kotlin compilation task
compilation.compileTaskProvider.configure { compileTask ->
}
}
}
}
}
}
הגדרת קומפילציות של בדיקות בתוספים מוסכמים
כשמגדירים ערכי ברירת מחדל לקומפילציות של בדיקות (כמו targetSdk לבדיקות עם מכשור) בתוסף מוסכמות, מומלץ להימנע משימוש בשיטות הפעלה כמו withDeviceTest { } או withHostTest { }. הפעלת השיטות האלה גורמת ליצירה של וריאציות תואמות של בדיקות Android וקומפילציות לכל מודול שמוחל עליו תוסף המוסכמות, וזה לא תמיד מתאים. בנוסף, אי אפשר להפעיל את השיטות האלה פעם שנייה במודול ספציפי כדי לשפר את ההגדרות, כי פעולה כזו תגרום לשגיאה שתציין שהקומפילציה כבר נוצרה.
במקום זאת, מומלץ להשתמש בconfigureEachבלוק
תגובתי במאגר של הקומפילציות. כך אתם יכולים לספק הגדרות ברירת מחדל שחלות רק אם מודול מפעיל במפורש את קומפילציית הבדיקה:
import com.android.build.api.dsl.KotlinMultiplatformAndroidDeviceTestCompilation
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val kmpExtension =
project.extensions.getByType(KotlinMultiplatformExtension::class.java)
kmpExtension.targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java)
.configureEach { androidTarget ->
androidTarget.compilations.withType(
KotlinMultiplatformAndroidDeviceTestCompilation::class.java
).configureEach {
targetSdk { version = release(34) }
}
}
}
}
}
התבנית הזו מבטיחה שהתוסף של המוסכמה יישאר עצלן ויאפשר למודולים בודדים לקרוא ל-withDeviceTest { } כדי להפעיל ולהתאים אישית את הבדיקות שלהם בלי להתנגש עם ברירות המחדל.
אינטראקציה עם Variant API
למשימות שדורשות הגדרה בשלב מאוחר, גישה לארטיפקטים (כמו מניפסטים או קוד בייטים) או אפשרות להפעיל או להשבית רכיבים ספציפיים, צריך להשתמש ב-Android Variant API. בפרויקטים של KMP, התוסף הוא מסוג KotlinMultiplatformAndroidComponentsExtension.
התוסף נרשם ברמת הפרויקט כשמחילים את הפלאגין של KMP Android.
משתמשים ב-beforeVariants כדי לשלוט ביצירת וריאציות או ברכיבי הבדיקה המקוננים שלהן (hostTests ו-deviceTests). זה המקום הנכון להשבית בדיקות באופן פרוגרמטי או לשנות את ערכי המאפיינים של DSL.
import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val androidComponents = project.extensions.findByType(KotlinMultiplatformAndroidComponentsExtension::class.java)
androidComponents?.beforeVariants { variantBuilder ->
// Disable all tests for this module
variantBuilder.hostTests.values.forEach { it.enable = false }
variantBuilder.deviceTests.values.forEach { it.enable = false }
}
}
}
}
משתמשים ב-onVariants כדי לגשת לאובייקט הסופי של הווריאנט (KotlinMultiplatformAndroidVariant). כאן אפשר לבדוק מאפיינים שנפתרו או לרשום טרנספורמציות בארטיפקטים כמו המניפסט שעבר מיזוג או מחלקות הספריות.
import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val androidComponents = project.extensions.findByType(KotlinMultiplatformAndroidComponentsExtension::class.java)
androidComponents?.onVariants { variant ->
// 'variant' is a KotlinMultiplatformAndroidVariant
val variantName = variant.name
// Access the artifacts API
val manifest = variant.artifacts.get(com.android.build.api.variant.SingleArtifact.MERGED_MANIFEST)
}
}
}
}
מומלץ עבורך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- הגדרת הסביבה
- הוספת מודול KMP לפרויקט
- הגדרה של Android Gradle Library Plugin ל-KMP