إنشاء ملفات Makefiles وإتقانها: أتمتة تجميع مشاريعك

آخر تحديث: 16/06/2025
نبذة عن الكاتب: إسحاق
  • يقوم ملف Makefile المصمم جيدًا بأتمتة عملية التجميع وإدارة التبعيات وتسهيل المهام مثل التنظيف والتعبئة، وتحسين سير العمل في مشاريع البرامج.
  • يؤدي استخدام المتغيرات والقواعد الضمنية والأهداف الخاصة والإعلان الصحيح عن التبعيات إلى زيادة الكفاءة وإمكانية الصيانة، مما يسمح بتوسيع المشروع دون تعقيدات.
  • توفر البدائل مثل CMake إدارة متعددة الأنظمة وأتمتة متقدمة، ولكن إتقان Makefile يظل ضروريًا للمبرمجين في البيئات يونكس وللفهم الكامل لعمليات البناء.

MAKEFILE

في عالم البرمجة وتطوير البرمجيات، يُعدّ تحسين عملية التجميع وإدارة المشاريع أمرًا بالغ الأهمية، لا سيما مع ازدياد عدد الملفات وظهور تبعيات معقدة. هنا تبرز أهمية استخدام أدوات مثل make وإنشاء ملفات Makefiles . إذا تساءلت يومًا عن كيفية أتمتة إنشاء الملفات التنفيذية في مشاريع C أو C++، أو أردت ببساطة تسهيل العمل على كل من يتعامل مع شفرتك البرمجية، فإن إتقان أسرار ملفات Makefiles يصبح ضروريًا.

في هذه المقالة الشاملة، ستتعرف خطوة بخطوة وبشكل مفصل على كيفية إنشاء ملفات Makefiles وتخصيصها، بغض النظر عن حجم مشروعك . بدءًا من أبسط الأمثلة وصولًا إلى الإعدادات الأكثر تقدمًا، ستتعلم النظريات الأساسية، والنصائح العملية، والحيل لتجنب الأخطاء الشائعة، والبدائل الحديثة مثل CMake. الهدف هو أن تكتسب، بنهاية هذه المقالة، فهمًا واضحًا وشاملًا لكيفية عمل Make، ولماذا هو مفيد للغاية، وكيفية الاستفادة منه إلى أقصى حد من خلال تخصيصه وفقًا لاحتياجاتك التطويرية الخاصة.

ما هو الـmake وما هو استخدام ملف Makefile؟

يُعدّ Make أحد أقدم وأكثر الأدوات تنوعًا في بيئة Unix/Linux ، مع وجود تطبيقات له لأنظمة تشغيل أخرى مثل Windows (مثل NMAKE). يتمثل غرضه الرئيسي في إدارة عملية التجميع والبناء الآلي للمشاريع، لا سيما تلك التي تتكون من ملفات مصدرية ومكتبات ورؤوس متعددة. ملف Makefile هو البرنامج النصي الذي يُحدد منطق بناء المشروع : فهو يُحدد ما يجب القيام به، وترتيبه، والتبعيات المطلوبة، مما يسمح لـ Make بتنفيذ المهام الضرورية فقط عند حدوث تغييرات.

تخيل المشكلة الشائعة : لديك مشروع يحتوي على عشرات الملفات بامتداد .cy و .h. إذا قمت بتجميعه يدويًا، ستكون العملية بطيئة ومملة، ومن السهل الوقوع في أخطاء بسبب نسيان مُعامل أو تبعية أو إعادة تجميع كل شيء في كل مرة. مع أداة make وملف Makefile مُصمم جيدًا، يصبح التجميع في غاية السهولة ، حيث يتم إعادة إنشاء المكونات التي تحتاجها فقط، مما يوفر الوقت والجهد. علاوة على ذلك، لا يقتصر استخدام ملف Makefile على التجميع فقط، بل يمكن استخدامه أيضًا لتنظيف الملفات المؤقتة ، وتغليف التعليمات البرمجية، وتثبيت الملفات التنفيذية، وتشغيل الاختبارات، وغير ذلك الكثير.

مزايا استخدام Makefile في مشاريعك

  • أتمتة البناء: انسى خطوط العمل اليدوية الطويلة الأوامر للتجميع. بأمر "make" البسيط، يعرف النظام ما يجب فعله.
  • إدارة التبعية: قم بتجميع ما تغير أو يعتمد على الملفات المتغيرة فقط، مما يوفر الوقت.
  • إمكانية نقل أكبر وسهولة أكبر للمطورين الآخرين: يمكن لأي شخص تجميع مشروعك بطريقة موحدة.
  • إمكانية تحديد مهام إضافية: تنظيف الملفات المؤقتة، وإنشاء الحزم، وتشغيل الاختبارات التلقائية، وما إلى ذلك.

كيف يتم العمل داخليا؟

يعتمد برنامج Make على منطق التبعيات والأهداف: لكل "هدف" في ملف Makefile تبعيات (ملفات يعتمد عليها في عملية البناء) ووصفات (أوامر التنفيذ، مُرتبة دائمًا بمسافات بادئة باستخدام علامات الجدولة). عند تشغيل الأمر "make goal" ، يتحقق البرنامج مما إذا كان هذا الهدف موجودًا بالفعل وما إذا كان مُحدّثًا فيما يتعلق بتبعياته. إذا كانت أي تبعية أحدث من الهدف، فإنه يُنفّذ الوصفة المرتبطة بها لإعادة بنائه.

سيكون بناء الجملة الأساسي هو:

الهدف: التبعيات
    الأمر 1 الأمر 2

على سبيل المثال، لتجميع ملف تنفيذي يسمى main من الملفين main.c و util.o :

الرئيسي: main.c util.o gcc -o main main.c util.o

كما ترون، الهدف هو main ، وهو يعتمد على main.c و util.o. إذا تغير أي من هذين الملفين، فسيقوم make بتنفيذ الوصفة الموجودة أسفله مباشرة (أمر gcc).

  أوقف Safari من فتح جلسة البحث النهائية على iPhone

البدء: التجميع الأساسي بدون ملف Makefile

قبل الخوض في تفاصيل إنشاء ملف Makefile، لنلقِ نظرة على كيفية تجميع برنامج بسيط بالطريقة التقليدية. لنفترض أن لديك هذا الملف في C:

#يشمل int main() { printf("مرحبا بالعالم\n"); return 0; }

لتجميعها يدويًا:

gcc hello.c -o hello

ممتاز، لديك الآن ملفك التنفيذي "hola" . مع ذلك، إذا عدّلت ملف المصدر، فسيتعين عليك تشغيل هذا الأمر بالكامل مرة أخرى في كل مرة. وإذا كبر مشروعك، ستصبح الأسطر أطول فأصعب في التذكر.

التجميع باستخدام make: سحر القواعد الضمنية والتلقائية

MAKEFILE

أداة make ذكية للغاية لدرجة أنها تستطيع، حتى بدون ملف Makefile صريح، تجميع برامج بسيطة باستخدام اصطلاحاتها الداخلية . على سبيل المثال، إذا كان لديك ملف hola.c في مجلدك وقمت بتشغيله:

قل مرحبا

يكتشف برنامج Make وجود الملف، ويستخدم قواعده الضمنية، ثم يستخدم المُصرّف المناسب لإنشاء الملف التنفيذي hello من الملف المصدر hello.c . إذا كان الملف التنفيذي موجودًا بالفعل وأحدث من الملف المصدر، فلن يقوم البرنامج بأي إجراء. إذا حذفت الملف التنفيذي وشغّلت make hello مرة أخرى ، فسيعيد تجميعه دون الحاجة إلى تذكّر المعاملات.

لماذا يعد Makefile ضروريًا في المشاريع الحقيقية؟

سرعان ما تتلاشى فعالية قواعد make الداخلية بمجرد أن يبدأ المشروع باحتوائه على ملفات مصدرية متعددة، وملفات رأسية، وتبعيات متبادلة. في هذه الحالة، لن يتمكن make وحده من معرفة ما يجب تضمينه، أو خيارات المُصرّف التي يجب استخدامها، أو كيفية ربط الكائنات. هنا تبرز أهمية ملف Makefile ، فهو يُخبر make بكيفية التعامل مع كل ملف، وكيفية تجميع الكائنات الفردية، وكيفية ربطها لإنتاج ملف تنفيذي واحد أو أكثر.

بنية Makefile: النظرية والتطبيق

يتكون ملف Makefile التقليدي من قاعدة واحدة أو أكثر تتألف من ثلاثة أجزاء: الهدف، والتبعيات، وقائمة الأوامر. يجب أن تبدأ سطور الأوامر بعلامة جدولة (بدون استثناء)، ولا تبدأ أبدًا بمسافات . وإلا، سيُنتج الأمر `make` خطأً.

الهدف: التبعيات command1 command2 ...

شرح المصطلحات:

  • الهدف: هذا هو عادةً اسم الملف القابل للتنفيذ، أو ملف كائن، أو حتى إجراء داخلي مثل "تنظيف".
  • التبعيات: الملفات التي يجب أن تكون موجودة ومحدثة ليتم بناء الهدف. إذا كانت أي تبعية أحدث من الهدف، فسيُشغّل الأمر make الوصفة.
  • الأوامر: يجب أن تكون تعليمات التجميع والحذف والحزم وما إلى ذلك متباعدة بـ منظم الجدوال.

مثال بسيط جدًا لملف Makefile لتجميع ملفين مصدريين وإنشاء ملف تنفيذي:

اختبار: test.o main.o gcc -o اختبار test.o main.o اختبار.o: test.c gcc -o test.o -c اختبار.c main.o: main.c اختبار.h gcc -o main.o -c main.c

لذلك إذا قمت بتنفيذ الأمر `make` ، فسيقوم أولاً بالتحقق مما إذا كانت الكائنات موجودة؛ وإذا لم تكن كذلك، فسيقوم بتجميعها، ثم ربطها في الملف التنفيذي `test`.

مراجعة المفاهيم الرئيسية بناءً على سير العمل الفعلي

مع ازدياد تعقيد مشروعك، يصبح من الضروري فهم كيفية تنظيم التبعيات والكائنات والقواعد بحيث لا يُعيد الأمر `make` تجميع إلا ما هو ضروري. على سبيل المثال، إذا عدّلت ملفًا واحدًا فقط بامتداد `.h`، فستحتاج إلى التأكد من إعادة تجميع جميع ملفات `.c` التي تتضمنه، دون غيرها.

افترض أن لديك مجموعة الملفات التالية:

  • main.c:البرنامج الرئيسي، يتضمن hello.h
  • مرحبا.ج: الوظائف المساعدة
  • مرحبا.ح: رأس الوظيفة

ملف Makefile البسيط ولكن العملي سيكون على النحو التالي:

مرحباً: main.o مرحباً.o gcc -o مرحباً main.o مرحباً.o main.o: main.c hello.h gcc -c main.c مرحباً.o: hello.c hello.h gcc -c hello.c

الآن كل شيء يعتمد على ما يلمسه، ويتم إعادة التجميع فقط ما تغير أو يعتمد على ما تغير.

المتغيرات في Makefile: إعادة الاستخدام والتبسيط

تُعدّ المتغيرات من أقوى الأدوات في ملفات Makefile . فباستخدامها، يمكنك تحديد، على سبيل المثال، المُصرّف، والعلامات العامة، واسم الملف التنفيذي، والكائنات، وما إلى ذلك. وبهذه الطريقة، يكفي تغيير سطر واحد فقط، ليتم تحديث باقي ملف Makefile تلقائيًا.

مثال نموذجي:

CC=gcc CFLAGS=-I. OBJ=main.o hello.o hello: $(OBJ) $(CC) -o hello $(OBJ) $(CFLAGS) %.o: %.c hello.h $(CC) -c -o $@ $< $(CFLAGS)

هنا:

  • $ (CC) هو متغير المترجم.
  • $ (CFLAGS) هي الخيارات التي يتم تمريرها إلى المترجم.
  • (OBJ) يحتوي على قائمة الكائنات.
  • $@ يتم استبداله باسم الهدف، و $< بالتبعية الأولى.
  الحل: يظل WiFi معلقًا على نوافذ المنزل 10

يتيح لك هذا إنشاء قواعد أكثر عمومية ومرونة. على سبيل المثال، إذا أضفت ملف .o آخر، فما عليك سوى تعديل متغير OBJ.

التوصية: استخدم المتغيرات لتجنب التكرار وتسهيل التغييرات.

يُعدّ تعريف المتغيرات في بداية ملف Makefile ممارسة أساسية لضمان سهولة صيانة المشروع وقراءته . يمكنك تعريف كل شيء بدءًا من اسم الملف التنفيذي وصولًا إلى مجلدات الرأس والكائنات والمصادر.

CC=gcc CFLAGS=-Wall -g -I./include OBJ=main.o func.o utils.o برنامجي: $(OBJ) $(CC) -o $@ $^ $(CFLAGS)

يمثل الرمز $^ جميع التبعيات (في هذه الحالة، الكائنات). لذا، ما عليك سوى تعديل المتغير OBJ لتغيير قائمة المصادر.

إضافة قواعد خاصة: الكل، والتنظيف، والمهام المفيدة الأخرى

في معظم المشاريع، تحتاج إلى مهام إضافية إلى جانب عملية التجميع . على سبيل المثال:

  • تنظيف الملفات الوسيطة (.o) والثنائية (نظيف).
  • بناء جميع الثنائيات دفعة واحدة (الكل).
  • إنشاء الحزم والتوثيق والاختبارات وما إلى ذلك.

مثال Makefile مع الكل والنظيف:

CC=gcc CFLAGS=-I. OBJ=main.o hello.o الكل: hello hello: $(OBJ) $(CC) -o hello $(OBJ) $(CFLAGS) %.o: %.c hello.h $(CC) -c -o $@ $< $(CFLAGS) تنظيف: rm -f hello *.o

الآن، إذا شغّلت الأمر `make` أو `make todo` ، فسيتم تجميع الملف التنفيذي. أما إذا شغّلت الأمر `make clean` ، فسيتم حذف الملفات التنفيذية وملفات الكائنات.

هام: إذا كان لديك ملف في الدليل يسمى "clean"، فلن يقوم make بتنفيذ الوصفة عند استخدام "make clean" إلا إذا قمت بتحديد الهدف على أنه .PHONY.

.PHONY: نظيف نظيف: rm -f hello *.o

تحسين التبعية والصيانة: تضمين ملفات .h بشكل صحيح

أحد أكثر الأخطاء شيوعًا التي يرتكبها المبتدئون هو عدم تعريف التبعيات على ملفات .h ، مما قد يؤدي إلى عدم إعادة تجميع الكود المصدري الذي يتضمن ملف رأس عند تعديله.

لحل هذه المشكلة، قم بتحديد تبعية كل كائن لملف (ملفات) .h الخاصة به بشكل صريح . أو بدلاً من ذلك، استخدم القواعد التلقائية.

%.o: %.c hello.h $(CC) -c -o $@ $< $(CFLAGS)

وبالتالي، في كل مرة يتغير فيها ملف hola.h ، يقوم make بإعادة تجميع الكائنات التابعة.

أتمتة التبعيات المتقدمة: MakeDepend والبدائل

في المشاريع الكبيرة، قد تكون إدارة التبعيات يدويًا أمرًا شاقًا للغاية ، خاصةً إذا كانت بعض ملفات .h تتضمن ملفات أخرى. هنا تبرز أهمية أدوات مثل makedepend ، المصممة لإنشاء التبعيات تلقائيًا والحفاظ على تحديث ملف Makefile. لإدارة الحالات الأكثر تعقيدًا، لا سيما عند العمل على منصات متعددة، يُعدّ فهم كيفية تحقيق الكفاءة والمرونة أمرًا بالغ الأهمية.

  • يركض:
    makedepend -I./include *.c
  • سيؤدي هذا إلى إضافة أسطر التبعية الصحيحة إلى نهاية Makefile.

الهيكل النموذجي لملف Makefile الاحترافي

CFLAGS=-I./include OBJECTS=main.o utils.o func.o SOURCES=main.c utils.c func.c البرنامج: $(OBJECTS) gcc $(OBJECTS) -o يعتمد البرنامج على: makedepend $(CFLAGS) $(SOURCES) clean: rm -f البرنامج *.o

Makefile للمشاريع المنتشرة عبر أدلة متعددة

عندما يزداد عدد المجلدات (على سبيل المثال، مع وجود العديد من الوحدات والمكتبات)، فمن الشائع وجود ملف Makefile رئيسي يُفوّض المهام إلى ملفات Makefile ثانوية أخرى . على سبيل المثال، قد يكون لديك ملف Makefile في المجلد الجذر يستدعي ملفات Makefile لكل وحدة باستخدام:

module1: make -C module1 module2: make -C module2

ثم الرابط النهائي لجميع مكتبات .oo التي تم إنشاؤها في كل مجلد.

دمج ملفات Makefiles في بيئات غير تابعة لنظام Unix: Visual Studio وNMAKE

إذا كنت تعمل على نظام ويندوز، يوفر لك Visual Studio دعمًا للمشاريع القائمة على ملفات Makefile من خلال أداة NMAKE أو خيار "مشاريع Makefile". يمكنك إنشاء مشروع من قالب Makefile، وتحديد أوامر البناء والتنظيف والإخراج باستخدام المعالج، والعمل مع ملف Makefile المدمج داخل بيئة التطوير المتكاملة (IDE)، والاستفادة من أدوات مثل IntelliSense وتصحيح الأخطاء المتقدم.

في Visual Studio 2017 والإصدارات الأحدث:

  • اختر "ملف > جديد > مشروع"، وابحث عن "Makefile"، وحدد القالب، وقم بتحديد الأوامر الخاصة بالتجميع والتنظيف وتصحيح الأخطاء.
  • في علامة التبويب خصائص المشروع، يمكنك تكوين المسارات والأوامر والخيارات.
  • لتحقيق تكامل أفضل، راجع البرنامج التعليمي على كيفية إدارة الأخطاء في نظام التشغيل Windows.

بدائل Makefile الحديثة: CMake والمزيد

على الرغم من أن ملف Makefile لا يزال المعيار الفعلي في بيئات يونكس، فقد اكتسبت أنظمة أخرى مثل CMake شعبية كبيرة ، ويعود ذلك أساسًا إلى إمكانياتها المتعددة المنصات وسهولة استخدامها في المشاريع الضخمة. وللحصول على إرشادات عملية، قد يكون من المفيد معرفة كيفية استخدام Makefile مع CMake.

  كيفية تثبيت تحديثات ويندوز 11 يدويًا عند فشلها

يقوم CMake بإنشاء ملفات Makefiles لك من وصف عالي المستوى. بالنسبة لمشروع بسيط، يكفي ملف CMakeLists.txt كهذا:

cmake_minimum_required(الإصدار 2.8) project(Hello) add_executable(hello main.c hello.c)

ثم شغّل الأمر "cmake ." لإنشاء ملفات Makefiles، ثم شغّل الأمر "make" للتجميع. يمكنك أيضًا إنشاء مجلد فرعي باسم build ، وتشغيل الأمر cmake .. من هناك، ثم تشغيل الأمر make للحفاظ على تنظيم المشروع وقابليته للتوسع.

أفضل الممارسات والنصائح الرئيسية لـ Makefile

  • العناية بالعلامات التبويبية: يجب أن تبدأ أوامر الوصفة دائمًا بعلامة تبويب، وليس بمسافة.
  • إعلان جميع التبعيات بشكل صحيح، بما في ذلك ملفات .h: سيؤدي هذا إلى منع إعادة التجميع غير السليم والأخطاء الناتجة عن الكود القديم.
  • استخدم المتغيرات لأسماء الملفات القابلة للتنفيذ، والدلائل، والأعلام: يسهل التعديل ويقلل الأخطاء.
  • تتضمن أهدافًا مثل التنظيف، الكل، الاعتماد على: بهذه الطريقة سيكون مشروعك أسهل في الاستخدام والصيانة.
  • بالنسبة للمشاريع الكبيرة، فكر في استخدام makedepend أو CMake: بهذه الطريقة يمكنك أتمتة إدارة التبعيات والتوسع بشكل أفضل.
  • يدعم تنظيف الملفات المؤقتة: لا تملأ الدليل بكائنات أو ملفات ثنائية غير مستخدمة يمكن أن تسبب مشاكل عند مشاركة التعليمات البرمجية.
  • اجعل الهدف الأول هو الملف التنفيذي الرئيسي أو الهدف "الكل": لذلك عندما يقوم شخص ما بكتابة شيء بدون حجج، هذا ما سيتم تجميعه.
  • أضف التعليقات إلى Makefile عندما يكون ذلك مناسبًا: على الرغم من أنها ليست إلزامية، إلا أنها تجعل الأمر أسهل بالنسبة للآخرين (أو بالنسبة لك بعد بضعة أسابيع) لفهمها.

الأخطاء الشائعة وكيفية تجنبها

  • الخلط بين المسافات وعلامات التبويب عند بدء وصفات الأوامر.
  • نسيان إعلان التبعيات على ملفات الرأس (.h).
  • لا تعلن عن هدف كـ .PHONY إذا لم ينشئ ملفًا، مما قد يؤدي إلى سلوك غريب إذا كان هناك ملف أو دليل بهذا الاسم موجودًا.
  • لا تستخدم المتغيرات للأسماء والمسارات، مما يؤدي إلى تكرار المعلومات وجعل الصيانة صعبة.
  • عدم تنظيف الملفات المؤقتة أو الثنائية قبل عمليات البناء الجديدة، مما قد يؤدي إلى أخطاء مربكة.

تخصيص ملف Makefile الخاص بك للمهام المعقدة

بالإضافة إلى التجميع والتنظيف، يمكنك إنشاء قواعد مخصصة لـ:

  • قم بتجميع الكود في ملف ZIP أو TAR.
  • تشغيل الملف القابل للتنفيذ تلقائيًا بعد التجميع.
  • إنشاء الوثائق (على سبيل المثال، باستخدام Doxygen).
  • تشغيل الاختبارات الآلية وعرض النتائج.
  • قم بإعداد الكود للتوزيع، عن طريق نسخ المصادر والملفات ذات الصلة فقط.

على سبيل المثال، لإنشاء ملف مضغوط:

التوزيع: zip my_project.zip *.c *.h Makefile README.md

مثال شامل لملف Makefile لمشروع كامل

CC=gcc CFLAGS=-Wall -g -I. DEPS=hello.h OBJ=main.o hello.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hello: $(OBJ) $(CC) -o $@ $^ $(CFLAGS) .PHONY: تنظيف التوزيع تنظيف: rm -f hello *.o

حالات واقعية وتوصيات نهائية

على مر السنين، ظلّت أداتا make وMakefile أساسيتين لتجميع المشاريع بكفاءة وتنظيم في لغات C وC++ وغيرها الكثير . ورغم وجود بدائل حديثة مثل CMake، فإن إتقان Makefile يُوفّر أساسًا متينًا يُفيدك في أي بيئة تطوير جادة.

تذكر أن كل مشروع قد يتطلب تعديلات خاصة على ملف Makefile الخاص به . الأهم هو فهم منطق الأهداف والتبعيات والوصفات، ثم الاستفادة من قوة Make لأتمتة أي مهام ضرورية. لا تتردد في دراسة ملفات Makefile لمشاريع أخرى، واستخدام أدوات إدارة التبعيات الآلية، وتجربة أهداف إضافية.

إن تطبيق كل ما تعلمته هنا سوف يساعدك على تقليل الأخطاء البشرية، وتوفير الوقت، وتسهيل الأمر على الآخرين (أو على نفسك في المستقبل) لبناء وصيانة وتوزيع برامجك بشكل احترافي وفعال، بغض النظر عن حجم المشروع.