تبعيات Gradle والمهام المخصصة
تبعيات Gradle والمهام المخصصة
تتمحور قوة Gradle حول نظامين متكاملين: نموذج إدارة التبعيات — المبني على تهيئات مسمّاة — ورسم بياني للمهام يتيح لك تأليف وتمديد وأتمتة كل جانب من جوانب عملية البناء. في هذا الدرس ستتقن كلا النظامين بالعمق المطلوب في مشاريع الإنتاج.
تهيئات التبعيات — الصورة الكاملة
في Gradle، التهيئة هي دلو مسمّى يجمع تبعيات لغرض محدد (التجميع، تشغيل الاختبارات، توليد معالجات التعليقات التوضيحية، وغيرها). تسجّل ملحقات Java وApplication/Library عدة تهيئات تلقائيًا.
أهم التهيئات في مشروع Java حديث:
implementation— التبعية موجودة في مسار تجميع هذه الوحدة لكنها لا تُكشف للمستهلكين الذين يعتمدون على هذه الوحدة. استخدمها لغالبية تبعياتك.api(ملحق المكتبة فقط) — مثلimplementationلكنها تكشف التبعية للمستهلكين. استخدمها باعتدال: الإفراط فيapiيُجبر على إعادة تجميع غير ضرورية عبر رسم مشروع بالكامل.compileOnly— موجودة أثناء التجميع وغائبة عن مسار التشغيل. مثال كلاسيكي: Lombok، وواجهات برمجة Jakarta التوضيحية عندما يوفّرها حاوي التشغيل.runtimeOnly— غائبة وقت التجميع وموجودة وقت التشغيل. مثال نموذجي: مشغّلات JDBC، وربطات SLF4J مثلlogback-classic.testImplementation— مثلimplementationلكن مقيّدة بمجموعة مصادر الاختبار.testCompileOnly/testRuntimeOnly— معادلات مقيّدة بالاختبار للحالات أعلاه.annotationProcessor— معالجات التعليقات التوضيحية التي يشغّلهاjavac؛ لا تُوضع على مسار التطبيق نهائيًا.
implementation وapi؟ تتابع Gradle أي ملفات jar تظهر على مسار التجميع للمشاريع الفرعية. إن استخدمت api، فأي تغيير في تلك التبعية العابرة يُفجّر إعادة تجميع كل وحدة فرعية. في بناء متعدد الوحدات الكبير قد يُضيف ذلك دقائق على البنيات التدريجية. الزم implementation افتراضيًا ولا ترقِّ إلى api إلا حين يكون النوع جزءًا من سطح واجهتك البرمجية العامة.
تعريف التبعيات
تُعرَّف التبعيات داخل كتلة dependencies { } في build.gradle (Groovy) أو build.gradle.kts (Kotlin DSL). الشكل القياسي هو إحداثيات Maven: group:artifact:version.
platform(...) واحذف سلاسل الإصدار من التبعيات الفردية. BOM الخاصة بـ Spring Boot هي المثال القياسي.
حل التبعيات واستراتيجيات تعارض الإصدارات
عندما يحتوي رسم التبعيات على نفس المكتبة بإصدارين مختلفين، تطبّق Gradle اختيار الإصدار التفاؤلي افتراضيًا: تختار أعلى إصدار مطلوب. يختلف هذا عن استراتيجية Maven الأقرب-يفوز وعادةً يُنتج نتيجة أكثر صحة.
يمكنك تجاوز سلوك الحل عند الحاجة:
المهام المخصصة — الأساسيات
كل إجراء في بناء Gradle هو مهمة. للمهمة مدخلات ومخرجات ومجموعة من الإجراءات. يستخدم نظام البناء التدريجي في Gradle المدخلات والمخرجات لتحديد ما إذا كانت المهمة محدَّثة ويمكن تخطّيها كليًا — وهذا المصدر الرئيسي لميزة سرعة Gradle على Maven في المشاريع الكبيرة.
أبسط طريقة لتعريف مهمة مخصصة هي استخدام واجهة برمجة tasks.register، التي تُنشئ المهمة بشكل كسول (لا تُهيَّأ إلا حين يحتاجها شيء ما فعليًا):
شغّلها بـ ./gradlew printBuildInfo. حقلا group وdescription يجعلان المهمة قابلة للاكتشاف عبر ./gradlew tasks --all.
المهام المكتوبة — المنهج الاحترافي
تمتد معظم المهام الحقيقية من نوع مدمج مثل Copy أو Jar أو JavaExec أو Exec أو Zip. تحصل المهام المكتوبة على دعم البناء التدريجي تلقائيًا عند تعريف المدخلات والمخرجات بشكل صحيح.
تبعيات المهام والترتيب
تشكّل المهام رسمًا بيانيًا موجّهًا لا دوريًا (DAG). تتحكم في العلاقات بثلاث آليات:
dependsOn— يضمن تشغيل المهمة المسمّاة قبل هذه المهمة ويُشغّلها فعليًا.mustRunAfter— يُلزم ترتيبًا عندما تكون كلتا المهمتين مجدوَلتين لكن لا يُفعّل المهمة الأخرى.finalizedBy— يشغّل مهمة بعد هذه المهمة حتى لو فشلت (مفيد للتنظيف / توليد تقرير الاختبار).
tasks.create (التسجيل الفوري). يهيّئ المهمة فورًا عند وقت التهيئة — في كل مرة يُقيَّم فيها سكربت البناء — حتى لو لن تعمل المهمة أبدًا. مع عشرات المهام المخصصة يُهدر ذلك ثوانٍ في كل استدعاء لـ Gradle. استخدم دائمًا tasks.register (الكسول) بدلًا من ذلك.
البنيات التدريجية والتخزين المؤقت
لكي تكون المهام المخصصة تدريجية، عرِّف مدخلاتك ومخرجاتك بشكل صريح باستخدام تعليقات خاصيات Gradle التوضيحية أو واجهة برمجة inputs/outputs:
مع وجود @Input و@OutputFile، ستتخطى Gradle المهمة إن لم تتغيّر سلسلة الإصدار منذ آخر بناء — ومع تفعيل ذاكرة التخزين المؤقت للبناء (org.gradle.caching=true في gradle.properties)، يمكنها حتى استعادة المخرجات من ذاكرة تخزين بعيدة مشتركة بين عوامل CI، مما يُنتج أوقات إعادة بناء شبه صفرية للعمل غير المتغيّر.
الخلاصة
تمنحك تهيئات التبعيات (implementation وapi وcompileOnly وruntimeOnly وannotationProcessor) تحكمًا دقيقًا فيما يظهر على كل مسار وما يُكشف للمستهلكين. أما المهام المخصصة — المسجَّلة بكسل عبر tasks.register، والمكتوبة لدعم التدريج، والمرتبطة عبر dependsOn/finalizedBy — فتتيح لك توسيع البناء بأي أتمتة مع إبقائه سريعًا. عرِّف المدخلات والمخرجات بدقة وستتولى آليتا التدريج والتخزين المؤقت في Gradle الباقي.