ماده ۴ : الفخ کش تنظیمات Gradle
منتشر شده در 26 September 2025
مقدمه
در ماجرای ما در ساخت پلاگین`site-baker`, ما یک رویکرد TDD سختگیرانه را دنبال کردیم. هر ویژگی تست میشد، تأیید میشد و ما با اعتماد به نفس پیش میرفتیم. و یکروز، اتفاق غیرمنتظره رخ داد. سازها شروع به رفتار ناپایدار کردند. تغییرات در منطق پلاگین یا در فایلهای پیکربندی بهنظر میرسید که نادیده گرفته شوند و تستهای عملکردی ما که قبلاً قابل اعتماد بودند، بدون دلیل واضح شکست میخورند.
این نوع مشکل میتواند به شدت خستهکننده باشد. این به قابلیت ابزار و صحت کد ما شک میآورد. پس از یک جلسه اشکالزدایی شدید، مرتکب شناسایی شد: leکش تنظیمات Gradle.
علامت: ساختهای فانتوم
مشکل به صورتهای مختلفی نشان میداد :
-
من یک رشتهٔ کاراکتر را در یک وظیفه تغییر میدادم.
println, اما رشته قدیمی در زمان اجرا نمایش داده میشد. -
من یک مقدار را در فایل خود تغییر میدادم`managed-jbake-context.yml`, اما پلاگین رفتار کرد بهمانند فایل تغییر نکرده باشد.
-
تستهای عملکردی که پروژههای تست را به صورت لحظهای میسازند، به این دلیل که پلاگین بهنظر نمیرسد فایلهای پیکربندی تازهساخته شده را تشخیص دهد، شکست میخوردند.
مثل بهنظر میرسید که Gradle یک «نسخه خیالی» از ساخت ما را اجرا میکند و تغییرات جدیدترین ما را نادیده میگیرد.
تحقیق: حافظهی پیکربندی چیست؟
کش پیکربندی یک ویژگی نسبتاً جدید و بسیار قدرتمند Gradle است که بهصورت پیشفرض در نسخههای جدید فعال میشود. هدف آن این است که ساختها (builds) سریعتر شوند.
-
در اولین اجرا، Gradle مرحله را اجرا میکندپیکربندی(خواندن از`build.gradle.kts`, ایجاد وظایف، حل وابستگیها) و یک گراف وظایف میسازد.
-
در انتهای این مرحله, Gradleاین گراف وظیفهها را سریالسازی میکندو آن را در کش قرار میدهد.
-
در 실행های بعدی، اگر چیزی تغییر نکرده باشد (اسکریپتهای ساخت,
gradle.properties, و غیره)، Gradleمراحل پیکربندی را بهطور کامل رد میکندو دوباره از گراف کارهای ذخیرهشده استفاده میکند.
زمان صرفهجویی در پروژههای بزرگ شگفتانگیز است. با این حال، این عملکرد قیمت دارد: آن قوانین سختگیرانهای را بر سر نحوه نوشتن پلاگینها تحمیل میکند.
@startuml
start
:Première exécution;
:Phase de Configuration;
:Création du graphe de tâches;
:Mise en cache du graphe;
:Phase d'Exécution;
end
start
:Exécution suivante;
if (Cache valide ?) then (oui)
:Restauration du graphe depuis le cache;
note right
La phase de configuration
est sautée !
end note
else (non)
:Phase de Configuration;
:Mise à jour du cache;
endif
:Phase d'Exécution;
stop
@enduml
دلیل مسئله: یک پلاگین غير matching
پلاگین ما`site-baker`نقض میکرد، بدون اطلاع، چندین قانون از کش تنظیمات. برای اینکه یک گراف وظیفه قابل سریالیزه شود، وظایف نباید حاوی ارجاع به اشیاء پیچیده مثل آبجکت`Project`یا خواندن فایلها به صورت تصادفی در حین اجرا.
خطای اصلی ما خواندن محتوای فایل YAML بهصورت مستقیم در داخل منطق اجرای وظیفه بود، با استفاده از ارجاع به مسیر ذخیرهشده در افزونه ما. این رویکرد با کش ناسازگار است زیرا Gradle نمیتواند بداند آیا محتوای فایل تغییر کرده است اگر این خواندن بهعنوان یکورودی وظیفه(Task Input)
محلول موقت : کش را غیرفعال کنید
برای رفع مشکل ما و بازیابی رفتار ساخت پیشبینیپذیر، راهحل سریعترین این بود که کش تنظیمات را غیرفعال کنیم. کافیست خط زیر را در فایل اضافه کنیم.`gradle.properties`projets that use the plugin (or in our case, the test project) is not correct. Let’s produce proper Persian.
Actually I need to output only the translated text, no explanation.
Let’s craft:
Space + "پروژهای که از پلاگین استفاده میکند (یا در مورد ما، پروژهی تست)". Ensure the leading space is present. Let’s output exactly.
</think>
پروژهای که از پلاگین استفاده میکند (یا در مورد ما، پروژهی تست)site-baker).
# site-baker/gradle.properties
org.gradle.configuration-cache=false
بهطور لحظهای، ساختها به رفتار عادی خود باز گشتند. هر اجرا فاز پیکربندی را دوباره راهاندازی میکرد و تغییرات ما در نظر گرفته میشد.
اما این یک راهحل موقت است، نه یک راهحل پایدار. عملکرد را ضایع میکند و مشکل اساسی پلاگین ما را حل نمیکند.
راهحل واقعی : سازگار کردن پلاگین
برای اینکه یک پلاگین یک شهروند خوب در اکوسیستم مدرن Gradle باشد، باید با کش تنظیمات سازگار باشد. این به این معناست که باید نحوه انتقال دادهها به وظایف خود را بازنگری کنیم.
کلید این است که از آنها استفاده شودAPIs ارائهدهندهاز Gradle. به جای ارسال مقادیر مستقیم (مثل یک`String` ou un File) به کارهای ما، باید زمان صرف کنیم`Property<T>`یا چند`Provider<T>`.
-
ورودهای وظیفه را اعلام کنید :وظیفهای که فایل YAML را تجزیه میکند باید این فایل را بهعنوان ورودی اعلام کند. برای این منظور از annotation استفاده میشود.
@InputFile.
[source,kotlin] ---- @get:InputFile abstract val configFile: RegularFileProperty (empty)