لماذا ينهار مشروعك فجأة على Kotlin وJava؟
\n\nهل سبق لك أن واجهت موقفًا تكتب فيه كودًا بلغة Kotlin، وتستخدم Java 11 للكود الأساسي، وفجأة يظهر خطأ توافق من Gradle؟ مألوف؟ هذه واحدة من أكثر المشكلات شيوعًا عند إعداد المشاريع متعددة الوحدات. خاصة عند استخدام build.gradle.kts (Kotlin DSL) ومحاولة التوفيق بين compileJava وcompileKotlin. في هذه المقالة، سنشرح كيفية ضبط توافق هدف JVM بشكل صحيح لـ Java 11 وKotlin 1.8، بحيث يتم بناء مشروعك بدون أخطاء. نعم، سنفعل ذلك بالعربية، مع لمسة فكاهية وبدون نظرية مملة.
إذا أتيت من Stack Overflow، فأنت تعلم أن السؤال الأصلي حصد آلاف المشاهدات. نحن نُكيّفه ليتناسب مع الشكل التعليمي، بحيث لا تكتفي بنسخ الإجابة، بل تفهم سبب ظهور الخطأ وكيفية تجنبه في المستقبل.
\n\nالمشكلة: عندما تتحدث Java وKotlin بلغتين مختلفتين
\n\nتخيل أن Java 11 هي أستاذ صارم يطلب أن يكون كل شيء وفقًا للمعيار. أما Kotlin 1.8 فهو زميله المبدع الذي يريد استخدام ميزات جديدة، لكنه ينسى إبلاغ الأستاذ. النتيجة: صراع.
\n\nفي Gradle، كل مهمة ترجمة (compileJava، compileKotlin) لها هدف JVM خاص بها - إصدار البايت كود الذي يُترجم إليه الكود. إذا لم يتطابقا، ستحصل على خطأ مثل:
Error: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.\n\n\nأو ما هو أسوأ - انخفاض صامت في الأداء بسبب عدم توافق البايت كود.
\n\nلماذا يحدث هذا تحديدًا في build.gradle.kts؟
\n\nKotlin DSL (ملفات .gradle.kts) هي أداة قوية، لكنها تتطلب تحديد المعاملات بشكل صريح. على عكس Groovy DSL حيث يتم "التقاط" بعض الإعدادات تلقائيًا، في Kotlin DSL يجب أن تكون دقيقًا. هذا ينطبق بشكل خاص على هدف JVM.
الخطأ النمطي: تقوم بتعيين java.targetCompatibility = JavaVersion.VERSION_11، لكنك تنسى تحديد نفس الشيء لـ Kotlin. أو العكس - تقوم بإعداد compileKotlin، بينما يبقى compileJava على Java 8.
الحل الصحيح: هدف JVM موحد لـ Java وKotlin
\n\nلتجنب الأخطاء، يجب تحديد هدف JVM بشكل صريح لكلتا المهمتين. إليك كيفية القيام بذلك في build.gradle.kts:
الخطوة 1: إعداد Java (compileJava)
\n\nأضف في كتلة java {} أو في tasks.withType<JavaCompile>():
java {\n sourceCompatibility = JavaVersion.VERSION_11\n targetCompatibility = JavaVersion.VERSION_11\n}\n\ntasks.withType<JavaCompile>() {\n options.encoding = "UTF-8"\n options.compilerArgs.add("-parameters")\n}\n\n\nلاحظ: يجب أن يكون sourceCompatibility وtargetCompatibility متطابقين. إذا كنت تكتب كودًا بلغة Java 11، فيجب أن يكون البايت كود أيضًا Java 11.
الخطوة 2: إعداد Kotlin (compileKotlin)
\n\nالآن الجزء الأهم - تحديد هدف JVM لـ Kotlin. يتم ذلك من خلال كتلة kotlin {} أو من خلال tasks.withType<KotlinCompile>():
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\ntasks.withType<KotlinCompile>() {\n kotlinOptions {\n jvmTarget = "11" // انتبه: هذه سلسلة نصية، وليست JavaVersion!\n apiVersion = "1.8"\n languageVersion = "1.8"\n }\n}\n\n\nلماذا jvmTarget عبارة عن سلسلة نصية؟ لأن Kotlin تتوقع تمثيلًا نصيًا للإصدار (مثل "1.8"، "11"، "17"). إذا حاولت تمرير JavaVersion.VERSION_11، فسيصدر المترجم خطأ.
مثال كامل لملف build.gr