فهم Make
August 1, 2026 · – زوار
يظهر make في كل مشروع مدمج أو نظمي تقريبًا مررت به — وصفات Yocto، بناء النواة، U-Boot، مستودعات برامج ثابتة صغيرة. وهو أيضًا أحد تلك الأدوات التي يستخدمها الناس لسنوات بالتقليد، ناسخين ملف Makefile من المشروع السابق دون أن يحدّدوا يومًا النموذج الكامن وراءه. إليك ذلك النموذج، مطروحًا مباشرة.
ما هو Make؟
Make أداة أتمتة بناء (build automation): تقرأ ملفًا (Makefile) يصف مجموعة من الأهداف (targets)، والمتطلبات المسبقة (prerequisites) التي يعتمد عليها كل هدف، والوصفة (recipe) — أي أوامر الصدفة (shell commands) — اللازمة لإنتاج ذلك الهدف من متطلباته المسبقة. انطلاقًا من هذا الوصف، يحدد make أي الأهداف أصبحت قديمة (out of date) وينفّذ فقط الوصفات اللازمة لتحديثها — لا أكثر.
main: main.o utils.o
gcc -o main main.o utils.o
main.o: main.c
gcc -c main.c
عند تشغيل make، فإنه يبني main.o فقط إذا كان main.c أحدث من main.o (أو إذا لم يكن main.o موجودًا بعد)، ثم يربط main فقط إذا تغيّر main.o أو utils.o. لا يُعاد بناء أي شيء دون داعٍ.
لمن هو موجّه؟
كتب Stuart Feldman أداة Make في مختبرات Bell Labs في أبريل 1976. وبحسب روايته، كان الدافع زميلًا له — Steve Johnson، مؤلف yacc — أمضى صباحًا كاملًا في تصحيح خطأ برنامج كان في الواقع صحيحًا؛ فالثنائي (binary) لم يُعَد ربطه ببساطة بعد تعديل المصدر. حصل Feldman على جائزة ACM لأنظمة البرمجيات (ACM Software System Award) لعام 2003 عن هذا العمل. الأداة التي نتجت عن ذلك المساء — تتبّع التبعيات مرة واحدة، ودع الآلة تحدد ما يحتاج إلى إعادة العمل — أصبحت واحدة من أكثر الأفكار تقليدًا في أدوات البرمجيات. أما GNU Make، النسخة التي يستخدمها الجميع تقريبًا اليوم، فهي إعادة تنفيذ حرة (free reimplementation) من مشروع GNU (1) (5) (6).
من الناحية العملية، هي موجهة لأي شخص يجمّع منتجًا غير بسيط — ثنائيًا مُصرَّفًا (compiled binary)، أو مستندًا، أو مجموعة من الملفات المولّدة — من أجزاء أصغر، حيث تكون إعادة بناء كل شيء من الصفر عند كل تغيير مضيعة أو بطيئة. ويشمل ذلك مشاريع C/C++، وبناء النواة والمُحمِّل الإقلاعي، ومستندات LaTeX، والكثير من الأتمتة المخصصة التي لا علاقة لها بتصريف الكود إطلاقًا.
متى ينبغي اللجوء إليه؟
يُثبت Make جدارته عندما تتحقق ثلاثة أمور في آن واحد:
- وجود مخطط تبعيات (dependency graph) حقيقي — بعض المخرجات تعتمد على غيرها، وإعادة البناء ليست مجرد "أعِد توليد كل شيء".
- إعادة البناء مكلفة بما يكفي بحيث يكون تجنّب عمليات إعادة البناء غير الضرورية أمرًا مهمًا (عملية تصريف تستغرق ثانيتين لا تحتاج هذا؛ أما بناء نواة يستغرق عشرين دقيقة فيحتاجه).
- القواعد مستقرة بقدر معقول — فنموذج Make القائم على الطوابع الزمنية للملفات (file-timestamp) لا يتعامل مع خطوط الأنابيب شديدة الديناميكية بسلاسة قدر ما قد يفعله منفّذ مهام (task runner) يعتمد على إبطال صريح (explicit invalidation).
بالنسبة لحفنة من الملفات دون تبعيات فيما بينها، غالبًا ما يكون سكربت الصدفة أبسط وأكثر صدقًا. يبدأ Make في تحقيق فائدته عندما يصبح السؤال "هل احتاج هذا فعليًا لإعادة البناء؟" سؤالًا يستحق طرحه تلقائيًا بدلًا من طرحه يدويًا.
أين يقع في سلسلة الأدوات؟
يقع Make عادة في طبقة فوق المُصرِّف (compiler) وطبقة تحت ما يشغّله الإنسان مباشرة. في مشروع C نموذجي: ملفات المصدر ← (المُصرِّف، بقيادة Make) ← ملفات الكائن (object files) ← (الرابط linker، بقيادة Make) ← الثنائي النهائي. لا يقوم Make نفسه بتصريف أو ربط أي شيء — بل يستدعي الأدوات التي تفعل ذلك، كأوامر صدفة ضمن كل وصفة.
كما يميل Make إلى الوقوع أسفل أدوات تهيئة البناء الأعلى مستوى. فأدوات مثل CMake وAutotools وBitBake الخاصة بـ Yocto لا تحل محل Make بقدر ما تولّد ملفات Makefile (أو ما يعادلها) لتكون خلفية التنفيذ الفعلية. لذا حتى المشاريع التي لا تُظهر لك Makefile مباشرة كثيرًا ما تشغّل واحدًا في مكان ما ضمن خط الأنابيب.
لماذا يعمل بهذه الطريقة؟
الخيار التصميمي الجوهري — مقارنة أوقات تعديل الملفات لتحديد ما هو قديم — هو ما يجعل Make سريعًا على الأشجار الكبيرة: فحص طابع زمني عملية رخيصة، لذا فإن إعادة بناء مشروع ضخم بعد تغيير سطر واحد يمكن أن يتجاوز كل شيء تقريبًا ويبقى صحيحًا مع ذلك، شريطة أن يكون مخطط التبعيات المُعطى له دقيقًا.
هذا الشرط الأخير هو مصدر كل شكوى تقريبًا من نوع "Make معطّل" يواجهها الناس، ويستحق التوقف عنده: Make ليس دقيقًا إلا بقدر دقة المخطط الذي أُبلغ به. إذا كان ملف .c يستخدم #include لترويسة (header) غير مدرجة كمتطلب مسبق لملف .o المقابل، فإن تعديل تلك الترويسة لن يُحفّز إعادة بناء — ليس لأن Make مرتبك، بل لأن أحدًا لم يخبره قط بوجود تلك التبعية. لهذا السبب تقترن ملفات Makefile في الواقع العملي دائمًا تقريبًا بخطوة توليد تبعيات يقوم بها المُصرِّف نفسه (-MMD -MP مع GCC/Clang) بدلًا من قوائم ترويسات مكتوبة يدويًا — فالمُصرِّف يعرف بالفعل مخطط الـ include الحقيقي، لذا فهو المصدر الوحيد الموثوق له (4).
كيف يعمل فعليًا؟
الأهداف، المتطلبات المسبقة، الوصفات. الوحدة الأساسية هي:
target: prerequisite1 prerequisite2
recipe line 1
recipe line 2
يجب أن تُزاح أسطر الوصفة بـ تبويب (tab)، وليس بمسافات — وهو عيب تاريخي شهير لا يزال يوقع الناس في الخطأ حتى اليوم.
المتغيرات التلقائية (automatic variables) تقلل من التكرار:
%.o: %.c
gcc -c $< -o $@
$@ هو الهدف، و$< هو أول متطلب مسبق — هذه القاعدة النمطية (pattern rule) وحدها كافية لبناء كل ملف .o من ملف .c المطابق له دون كتابة سطر لكل ملف.
أهداف .PHONY تعلّم الأسماء التي لا تقابل ملفات حقيقية — مثل clean وall وtest — حتى لا يتشوش Make إذا صادف وجود ملف بذلك الاسم، ويشغّل الوصفة دائمًا بدلًا من فحص الطوابع الزمنية: (2)
.PHONY: clean
clean:
rm -f *.o main
المتغيرات (variables) تتجنب تكرار الخيارات (flags) في كل مكان وتسهّل إعادة الاستخدام بين المشاريع:
CC = gcc
CFLAGS = -Wall -O2
main.o: main.c
$(CC) $(CFLAGS) -c main.c
التوليد التلقائي للتبعيات (automatic dependency generation) يسدّ الفجوة الموصوفة أعلاه:
CFLAGS = -Wall -O2 -MMD -MP
-include $(wildcard *.d)
%.o: %.c
$(CC) $(CFLAGS) -c $<
يجعل -MMD المُصرِّف يُصدر ملف .d لكل كائن (object)، يسرد كل ترويسة (header) دخلت فعليًا في ذلك التصريف. ويعيد -include طيّ تلك الملفات في الـ Makefile بحيث يبقى مخطط التبعيات دقيقًا مع تغيّر الترويسات — دون أن يحتاج أحد لصيانته يدويًا (3).
خاطرة ختامية
معظم ما يبدو غرائب في Make — مقارنات الطوابع الزمنية، والإصرار على التبويب، والطريقة التي تفشل بها تبعية مفقودة بصمت بدلًا من الفشل بصخب — ينبع من قرار تصميمي واحد اتُّخذ عام 1976: أبقِ النموذج بسيطًا والفحص رخيصًا، وثق بأن Makefile يصف الواقع بدقة. وبمجرد تبني هذه الزاوية في النظر، يتضح أن معظم لحظات "Make يتصرف بغرابة" هي في الحقيقة "المخطط الذي أعطيته له كان ناقصًا" — وهو أمر أسهل بكثير إصلاحه.
قراءات ذات صلة
- Git: حذف الفروع، الدمج، والتراجع عن push سيئ — أوامر الفروع والدفع اليومية
المراجع
- Feldman, S. I. (1979). Make — A Program for Maintaining Computer Programs. Software: Practice and Experience, 9(4), 255–265. — الورقة البحثية التي كتبها Feldman بنفسه لتقديم Make، بما في ذلك الدافع الأصلي وراءه.
- GNU Make Manual — المرجع الكامل والموثوق لصياغة ودلالات GNU Make.
- GNU Make Manual: Automatic Prerequisites — نمط
-MMD/-includeبكلمات القائمين على المشروع أنفسهم. - GCC: Preprocessor Options (
-Mfamily) — ما الذي يفعله-MMDو-MPفي الخلفية. - Make (software) — Wikipedia — تاريخ عام ونسب تطبيقات Make (BSD Make، وGNU Make، وNMAKE، وغيرها).
- Stuart Feldman — Wikipedia — خلفية عن عمل Feldman في Bell Labs وجائزة ACM لأنظمة البرمجيات لعام 2003.
التعليقات