המסמך הזה מיועד למפתחי פלאגינים, והוא מסביר איך לזהות נכון את ההגדרה של Kotlin Multiplatform (KMP), איך ליצור איתה אינטראקציה ואיך להגדיר אותה. הדגש הוא על שילוב עם יעדי Android בפרויקט KMP. ככל ש-KMP ממשיך להתפתח, חשוב להבין את ה-hooks והממשקי API המתאימים – כמו סוגי 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