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

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

MAKEFILE

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

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

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

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

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

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

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

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

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

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

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

على سبيل المثال، لتجميع ملف قابل للتنفيذ يسمى رئيسي من الأرشيف ج الرئيسية y مفيد.:

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

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

  خطأ فيديو فشل TDR | ما هو والحلول الممكنة

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

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

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

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

gcc hello.c -o hello

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

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

MAKEFILE

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

قل مرحبا

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

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

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

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

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

الهدف: التبعيات 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

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

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

مع تزايد تعقيد مشروعك، من المهم فهم كيفية تنظيم التبعيات والكائنات والقواعد حتى تتمكن من إعادة تجميع ما هو ضروري فقط. على سبيل المثال، إذا قمت بتعديل ملف .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: إعادة الاستخدام والتبسيط

أحد أقوى أسلحة Makefiles هي المتغيراتبواسطتها، يمكنك تعريف، على سبيل المثال، المترجم، والأعلام المشتركة، والاسم الثنائي، والكائنات، وما إلى ذلك. بهذه الطريقة، لن تحتاج إلا إلى تغيير شيء ما في سطر واحد، وسيتم تحديث باقي ملف 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) يحتوي على قائمة الكائنات.
  • $@ يتم استبداله باسم الهدف، و $< بالتبعية الأولى.
  ما هي ردود الفعل السريعة التي يمكن لـ Instagram تفعيلها أو إلغاء تنشيطها؟ قم بالتنزيل لنظام Android أو iOS

يتيح لك هذا إنشاء قواعد أكثر عمومية ومرونة. على سبيل المثال، إذا أضفت ملف .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

الآن، إذا قمت بالركض جعل o اصنع كل شيءسيتم تجميع الملف الثنائي. إذا قمت بتشغيل اجعلها نظيفة، تتم إزالة الملفات الثنائية وملفات الكائنات.

هام: إذا كان لديك ملف في الدليل يسمى "clean"، فلن يقوم الأمر "make" بتشغيل الوصفة عند استخدام "make clean" إلا إذا أعلنت الهدف كـ .زائف:

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

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

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

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

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

لذلك، في كل مرة مرحبا.ح التغييرات، تجعل إعادة تجميع الكائنات التابعة.

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

بالنسبة للمشاريع الكبيرة، قد تكون إدارة التبعيات يدويًا أمرًا مرهقًا للغاية.، خاصةً إذا كانت بعض ملفات .h تتضمن ملفات أخرى. هنا حيث توجد أدوات مثل com.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 رئيسي يقوم بالتفويض إلى ملفات Makefiles ثانوية أخرى.على سبيل المثال، يمكنك الحصول على ملف Makefile في الدليل الجذر الذي يستدعي ملف Makefile الخاص بكل وحدة نمطية باستخدام:

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

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

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

إذا كنت تعمل على نظام التشغيل Windows، يوفر Visual Studio الدعم للمشروعات المستندة إلى Makefile. باستخدام أداة NMAKE أو خيار Makefile Projects، يمكنك إنشاء مشروع من قالب Makefile، وتحديد أوامر التجميع والتنظيف والإخراج من المعالج، ودمج Makefile في IDE، والاستفادة من أدوات مثل IntelliSense والتصحيح المتقدم.

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

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

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

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

  وفاة العملية الحرجة - كيفية إصلاح هذا الخطأ في نظام التشغيل Windows 10؟

يُنشئ CMake ملفات Makefiles لك بناءً على وصف دقيق. لمشروع بسيط، يكفي ملف واحد. CMakeLists.txt وبالتالي:

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

ثم شغّل "cmake ." لإنشاء ملفات Makefiles و"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 بأتمتة أي مهام ضرورية. لا تتردد في دراسة ملفات Makefiles الخاصة بمشاريع أخرى، واستخدام أدوات إدارة التبعيات الآلية، وتجربة أهداف إضافية.

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