أدوات البناء والوحدات

أساسيات Gradle

15 دقيقة الدرس 5 من 13

أساسيات Gradle

يُعدّ Gradle أداة البناء الأكثر هيمنةً في عالم Android ومنافسًا جديًا لـ Maven في بيئة JVM. يستبدل XML بلغة برمجة حقيقية — إما Groovy DSL أو Kotlin DSL — مانحًا إياك القوة التعبيرية الكاملة لبيئة نصية مع الحفاظ على النموذج التصريحي الذي تحتاجه لبناء قابل للتنبؤ. يتناول هذا الدرس بنية ملف build.gradle ونموذج المهام ومعمقة Groovy/Kotlin DSL.

لماذا Gradle؟

XML في Maven طويل ومتصلّب. يحتفظ Gradle بالجوانب الجيدة — إدارة الاعتماديات التصريحية ومنظومة الإضافات الغنية والاصطلاحات على التهيئة — ويتخلص من أسوأها:

  • بناء تدريجي. يتتبّع Gradle مدخلات ومخرجات كل مهمة. إن لم يتغيّر شيء فتُوسَم المهمة بـ UP-TO-DATE وتُتجاوز. يُعيد Maven تنفيذ المراحل حتى عند غياب أي تغيير.
  • ذاكرة تخزين للبناء. تُخزَّن مخرجات المهام وتُعاد استخدامها عبر الأجهزة مما يُقلّص زمن البناء في CI بشكل كبير للمشاريع الكبيرة.
  • نصوص مرنة. ملف البناء برنامج حقيقي يمكنك فيه استخدام حلقات وشروط ودوال مساعدة.
  • عفريت يُعطي الأداء الأولوية. يعمل عفريت Gradle في الخلفية فتتجنب عمليات البناء اللاحقة تهيئة JVM بالكامل.
Kotlin DSL مقابل Groovy DSL. ينبغي للمشاريع الجديدة تفضيل Kotlin DSL (build.gradle.kts). يمنحك إكمالًا تلقائيًا في IDE ودعمًا لإعادة الهيكلة والتحقق من الأنواع وقت الترجمة. Groovy DSL (build.gradle) أقدم وشائع جدًا، لذا ستصادف كليهما — يعرض هذا الدرس كلتا الصياغتين جنبًا إلى جنب حيثما اختلفتا.

بنية build.gradle

يتضمّن ملف بناء مشروع Java بسيط أربعة أقسام: الإضافات والمستودعات والاعتماديات وتهيئة المهام (اختياريًا).

Groovy DSL (build.gradle):

plugins { id 'java' // تطبيق إضافة Java id 'application' // يُضيف مهام run و installDist و distZip } group = 'com.example' version = '1.0.0' java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } repositories { mavenCentral() // تحليل الاعتماديات من Maven Central } dependencies { implementation 'com.google.guava:guava:33.2.1-jre' testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } application { mainClass = 'com.example.App' // تستخدمه مهمة run } test { useJUnitPlatform() // مطلوب لـ JUnit 5 }

Kotlin DSL (build.gradle.kts) — المشروع ذاته:

plugins { java application } group = "com.example" version = "1.0.0" java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } repositories { mavenCentral() } dependencies { implementation("com.google.guava:guava:33.2.1-jre") testImplementation("org.junit.jupiter:junit-jupiter:5.11.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } application { mainClass = "com.example.App" } tasks.test { useJUnitPlatform() }

يلفّ Kotlin DSL كل شيء في استدعاءات دوال (الأقواس إلزامية) ويستخدم سلاسل بعلامات اقتباس مزدوجة ويُحلّ الأنواع وقت الترجمة — تُكتشف أخطاء ملف البناء قبل تشغيله.

تهيئات الاعتماديات

تأتي إضافة Java في Gradle مع عدة تهيئات. أهمها:

  • implementation — على مسار ترجمة هذا المشروع؛ غير مُسرَّبة للمستهلكين. افضلها دائمًا.
  • api — (من إضافة java-library) مُسرَّبة للمستهلكين؛ استخدمها فقط للأنواع التي تكشفها واجهتك العامة.
  • compileOnly — متاحة وقت الترجمة فقط (مثل Lombok ومعالجات التعليقات التوضيحية).
  • runtimeOnly — على مسار التنفيذ فقط (مثل برامج تشغيل JDBC).
  • testImplementation — مثل implementation لكنها مقتصرة على الاختبارات.
  • testRuntimeOnly — تنفيذ فقط للاختبارات (مثل JUnit launcher).
افضل دائمًا implementation على compile المهجورة. كانت تهيئة compile القديمة تُسرِّب جميع الاعتماديات العابرة للمستهلكين مما ينفّخ مسارات الترجمة. تحتفظ implementation بالاعتماديات الداخلية داخليةً مما يعني ترجمة أسرع وشجرة اعتماديات أنظف.

نموذج المهام في Gradle

كل ما يفعله Gradle هو مهمة. المهمة وحدة عمل ذات مدخلات ومخرجات مُحدَّدة النوع. حين تشغّل ./gradlew build يبني Gradle رسمًا بيانيًا موجَّهًا لا دوري (DAG) لجميع المهام التي تحتاج التنفيذ وينفّذها بترتيب التبعيات.

أبرز المهام المدمجة في إضافة Java:

  • compileJava — يُجمّع src/main/java.
  • processResources — ينسخ src/main/resources إلى مجلد الإخراج.
  • classes — مهمة دورة الحياة: تعتمد على compileJava + processResources.
  • test — يُجمّع الاختبارات ويشغّلها.
  • jar — يحزم الفئات المُجمَّعة في ملف JAR.
  • build — يُجمّع المشروع ويختبره (البناء الكامل القياسي).
  • clean — يحذف مجلد build/.

تعريف مهام مخصصة

يمكنك تعريف مهامك مباشرةً في ملف البناء. يجعل Groovy DSL هذا موجزًا بشكل خاص:

// Groovy DSL — مهمة بسيطة tasks.register('hello') { group = 'Demo' description = 'Prints a greeting' doLast { println "Hello from Gradle ${gradle.gradleVersion}" } } // Groovy DSL — مهمة بتبعيات tasks.register('packageReport') { dependsOn 'build' // تعمل بعد build doLast { copy { from layout.buildDirectory.dir('libs') into 'dist' } println "Report packaged into dist/" } }

المكافئ بـ Kotlin DSL:

tasks.register("hello") { group = "Demo" description = "Prints a greeting" doLast { println("Hello from Gradle ${gradle.gradleVersion}") } } tasks.register("packageReport") { dependsOn("build") doLast { copy { from(layout.buildDirectory.dir("libs")) into("dist") } println("Report packaged into dist/") } }

المهام ذات الأنواع — الأسلوب المفضَّل

للمهام الجدية يوفّر Gradle فئات أساسية ذات أنواع. استخدام مهمة ذات نوع يتيح لـ Gradle تتبّع المدخلات والمخرجات للتنفيذ التدريجي:

// Kotlin DSL — مهمة Copy ذات نوع؛ يعرف Gradle بالضبط ما تغيّر tasks.register<Copy>("copyDocs") { group = "Documentation" description = "Copies generated Javadoc into the site directory" from(tasks.named("javadoc")) // مدخل: مخرج مهمة javadoc into(layout.projectDirectory.dir("site/api")) // مخرج: يتتبّعه Gradle }

لأن from وinto مُعلَن عنهما كمدخلات/مخرجات للمهمة سيتجاوز Gradle هذه المهمة تلقائيًا عند عدم تغيّر مصدر Javadoc ولا الوجهة.

لا تستخدم أبدًا doFirst/doLast لتهيئة مدخلات أو مخرجات المهمة. كود التهيئة الذي يعمل وقت التهيئة ينتمي لكتلة تهيئة المهمة؛ كود الإجراء (العمل الفعلي) ينتمي لـ doLast. خلطهما يُفسد البناء التدريجي وذاكرة التخزين المؤقتة.

مغلّف Gradle (Gradle Wrapper)

يجب أن يُودِع كل مشروع مغلّف Gradle — نص برمجي صغير وملف JAR يُنزّل إصدار Gradle الصحيح تلقائيًا. هذا يضمن أن كل مطوّر وكل خادم CI يستخدم الإصدار ذاته من Gradle:

# توليد المغلّف (مرة واحدة) gradle wrapper --gradle-version 8.10.2 # بعد ذلك استخدم المغلّف دائمًا — لا Gradle المثبّت محليًا ./gradlew build # Linux/Mac gradlew.bat build # Windows

تُخزَّن تهيئة المغلّف في gradle/wrapper/gradle-wrapper.properties. أودِع هذا الملف ونصوص المغلّف؛ لا تودِع مجلد .gradle/ للتخزين المؤقت.

أوامر سطر الأوامر الأساسية

  • ./gradlew tasks — يُدرج جميع المهام المتاحة مجمَّعةً حسب الفئة.
  • ./gradlew build — يُجمّع ويختبر ويحزم.
  • ./gradlew test — يشغّل الاختبارات فقط.
  • ./gradlew dependencies — يطبع شجرة الاعتماديات الكاملة.
  • ./gradlew :subproject:build — يبني مشروعًا فرعيًا محددًا.
  • ./gradlew build --scan — يولّد فحصًا للبناء على scans.gradle.com (لا غنى عنه لتشخيص البناء البطيء).

الخلاصة

يستبدل Gradle XML بـ DSL حقيقي (Groovy أو Kotlin) ويُنمذج كل إجراء كمهمة ذات مدخلات ومخرجات صريحة ويستخدم التنفيذ التدريجي لإعادة بناء ما تغيّر فقط. يضمن المغلّف قابلية إعادة إنتاج البناء في كل بيئة. في الدرس القادم سنتعمّق في إدارة الاعتماديات وكتالوجات الإصدارات وكتابة مهام Gradle مخصصة تتكامل مع بناء متعدد المشاريع.