المادة 4: فخ ذاكرة التخزين المؤقت للتكوين في Gradle
نُشر في 26 September 2025
مقدمة
في مغامرتنا لإنشاء المكوّن`site-baker`, لقد اتبعنا نهج TDD صارم. تم اختبار كل ميزة وتأكيد صحتها، وكنّا نتقدم بثقة. ثم، في أحد الأيام، وصل الأمر غير المتوقّع. بدأ البناء يتصرف بشكل غير متوقع. بدَت التعديلات في منطق الإضافة أو في ملفات الإعداد كأنها تُتجاهَل، واختباراتنا الوظيفية التي كانت موثوقة في الماضي كانت تفشل دون سبب واضح.
يمكن أن يكون هذا النوع من المشكلات محبطًا بشكل لا يصدق. إنه يشكك في موثوقية الأداة وصحة كودنا. بعد جلسة تصحيح أخطاء مكثفة، تم تحديد الجاني: الذاكرة التخزين المؤقت لتكوين Gradle.
العرض : بنى شبحية
ظهرت المشكلة بعدة طرق :
-
كنت أعدل سلسلة أحرف في مهمة`println`, لكن السلسلة القديمة استمرت في الظهور أثناء التنفيذ.
-
كنت أغير قيمة في ملفي`managed-jbake-context.yml`، لكن البرنامج كان يتصرف كما لو لم يتم تعديل الملف
-
كانت الاختبارات الوظيفية، التي تنشئ مشاريع اختبار على الفور، تفشل لأن البرنامج المساعد لم يبدو أنه يكتشف ملفات التكوين التي تم إنشاؤها حديثًا.
كان كل شيء يحدث كما لو أن Gradle كان ينفذ نسخة شبحية من بنائنا، متجاهلاً أحدث تغييراتنا.
التحقيق: ما هو ذاكرة التخزين المؤقت للإعدادات؟
ذاكرة التخزين المؤقت للتكوين هي ميزة حديثة نسبياً وقوية للغاية في Gradle، ممكّنة افتراضياً في الإصدارات الأحدث. هدفها هو تسريع عمليات البناء.
-
المبدأ بسيط :
-
lorsqu first execution, Gradle executes the phase of ⇒ Arabic: "عند أول تنفيذ، يقوم Gradle بتنفيذ مرحلة الـ"
Add leading and trailing spaces as in source.
Result:
" عند أول تنفيذ، يقوم Gradle بتنفيذ مرحلة الـ " (with spaces). Ensure no extra characters. I’ll output exactly: a space, then Arabic phrase, then a space.
Let’s craft:
Now output.
</think>
عند أول تنفيذ، يقوم Gradle بتنفيذ مرحلة الـ التكوين(القراءة`build.gradle.kts`، إنشاء المهام، حل الاعتماديات) ويبني رسمًا بيانيًا للمهام.
-
في نهاية هذه المرحلة، Gradleسَلْسِل مخطط المهامويخزّنه في الذاكرة المؤقتة
-
أثناء التنفيذات اللاحقة، إذا لم يتغير شيء (سكريبتات البناء,
gradle.properties، إلخ)، Gradleيتخطى تمامًا مرحلة الإعدادوأَعِيدُ استخدام رسم بياني للمهام المخزن في الكَشّ.
الادخار في الوقت يكون مذهلاً على المشاريع الكبيرة. ومع ذلك، فإن هذا الأداء يأتي بثمن: فهو يفرض قواعد صارمة على طريقة كتابة الإضافات.
@startuml start :التنفيذ الأول; :مرحلة التكوين; :إنشاء رسم بياني للمهام; :التخزين المؤقت للرسم البياني; :مرحلة التنفيذ; end start :التنفيذ التالي; if (Cache valide ?) then (oui) :استعادة الرسم البياني من ذاكرة التخزين المؤقت; note right مرحلة التكوين تم تخطيها ! end note else (non) :مرحلة التكوين; :تحديث الذاكرة المؤقتة; endif :مرحلة التنفيذ; stop @enduml
سبب المشكلة : إضافة غير متطابقة
إضافتنا`site-baker`كان يخالف، دون أن يعلم، عدة قواعد في ذاكرة التخزين المؤقت للتكوين. لكي يكون رسم مهام قابلًا للتسلسل، لا يجب أن تحتوي المهام على مراجع إلى كائنات معقدة مثل الكائن.`Project`أو قراءة الملفات بطريقة تعسفية أثناء مرحلة التنفيذ.
كان الخطأ الرئيسي لدينا هو قراءة محتوى ملف YAML مباشرة داخل منطق تنفيذ المهمة، باستخدام إشارة إلى المسار المخزن في ملحقنا. هذا النهج غير متوافق مع التخزين المؤقت لأن Gradle لا يمكنه معرفة ما إذا كان محتوى الملف قد تغير إذا لم يتم نمذجة هذه القراءة على أنهادخول المهمة
الحل المؤقت : تعطيل الذاكرة المؤقتة
لإلغاء القفل عن أنفسنا وإعادة سلوك البناء المتوقع، كان الحل الأسرع هو تعطيل ذاكرة التخزين المؤقت للتكوين. ما علينا سوى إضافة السطر التالي في الملف`gradle.properties`المشروع الذي يستخدم الإضافة (أو في حالتنا، مشروع الاختبار)site-baker)
# site-baker/gradle.properties
org.gradle.configuration-cache=false
عمليات البناء عادت فورًا إلى سلوكها الطبيعي؛ كل عملية تنفيذ أعادت تشغيل مرحلة الإعداد وتمّ أخذ تغييراتنا في الاعتبار.
ومع ذلك، هذه حل بديل وليس حلًا دائمًا. إنها تضحي بالأداء ولا تحلّ المشكلة الجوهرية لإضافتنا.
الحل الحقيقي : جعل البرنامج المساعد متوافقًا
ليكون المكوّن الإضافي مواطنًا جيدًا في نظام Gradle الحديث، يجب أن يكون متوافقًا مع ذاكرة التخزين المؤقت للتكوين. وهذا يعني أنه يتوجب علينا إعادة التفكير في كيفية وصول البيانات إلى مهامنا.
المفتاح هو استخدام الـواجهات برمجة التطبيقات للمزودمن Gradle. بدلاً من تمرير القيم المباشرة (مثل واحد`String` ou un File) إلى مهامنا، يجب علينا أن نقضي بعض`Property<T>`أو من`Provider<T>`.
-
إعلان إدخالات المهام :يجب أن يعلن مهمة تحليل ملف YAML عن هذا الملف كمدخل. تُستخدم للقيام بذلك التعليمة التوضيحية`@InputFile`.
[source,kotlin] ---- @get:InputFile abstract val configFile: RegularFileProperty ----