CMake

معمارية تطبيقات المصادر المفتوحة (المجلد الأول)CMake

معمارية تطبيقات المصادر المفتوحة (المجلد الأول) CMake

Bill Hoffman وKenneth Martin

إذا استمتعت بهذه الكتب، فقد يعجبك أيضًا التصميم البرمجي بالأمثلة في بايثون (Software Design by Example in Python) والتصميم البرمجي بالأمثلة في جافاسكربت (Software Design by Example in JavaScript).

في عام 1999 كلّفت المكتبة الوطنية للطب (National Library of Medicine) شركةً صغيرة اسمها Kitware بتطوير طريقة أفضل لضبط البرمجيات المعقّدة وبنائها ونشرها عبر منصّات مختلفة كثيرة. وقد كان هذا العمل جزءًا من حزمة Insight Segmentation and Registration Toolkit، أو ITK[1]. وكانت Kitware، بوصفها الجهة القيادية الهندسية في المشروع، مكلَّفة بتطوير نظام بناء (build system) يستطيع باحثو ITK ومطوّروه استخدامه. وكان على النظام أن يكون سهل الاستخدام، وأن يتيح أكثر استثمار إنتاجي في وقت برمجة الباحثين. ومن هذا التوجيه انبثقت CMake بوصفها بديلًا لمنهج autoconf/libtool الذي شاخ في بناء البرمجيات. وقد صُمِّمت لمعالجة نقاط ضعف الأدوات القائمة مع الحفاظ على نقاط قوتها.

إلى جانب نظام البناء، تحوّلت CMake على مدى السنين إلى عائلة من أدوات التطوير: CMake وCTest وCPack وCDash. وCMake هي أداة البناء المسؤولة عن بناء البرمجيات. أمّا CTest فهي أداة تشغيل اختبارات (test driver) تُستخدم لتشغيل اختبارات الانحدار (regression tests). وCPack أداة تعبئة تُستخدم لإنشاء مثبّات خاصة بكل منصّة للبرمجيات المبنية بـ CMake. أمّا CDash فهي تطبيق ويب لعرض نتائج الاختبار وأداء اختبارات التكامل المستمر.

5.1. تاريخ CMake ومتطلباته

عند تطوير CMake، كانت الممارسة المعتادة في المشروع أن تضمّ سكربت ضبط (configure script) وملفات Makefile لمنصّات Unix، إضافةً إلى ملفات مشاريع Visual Studio لويندوز. وقد جعل هذا التعدّد في أنظمة البناء التطوير عبر المنصّات مرهقًا إلى حدٍّ كبير لكثير من المشاريع: فإضافة ملف مصدر جديد إلى مشروع كانت بحدّ ذاتها فعلًا مؤلمًا. وكان الهدف الواضح لدى المطوّرين هو توفير نظام بناء موحّد. وقد كان لدى مطوّري CMake خبرة في منهجيين لحل مسألة نظام البناء الموحّد.

كان أحد المنهجين هو نظام بناء VTK لعام 1999. وقد تكوّن ذلك النظام من سكربت ضبط لنظام Unix ومن ملف تنفيذي اسمه pcmaker لويندوز. وكان pcmaker برنامجًا مكتوبًا بلغة C يقرأ ملفات Makefile الخاصة بـ Unix وينشئ ملفات NMake لويندوز. وقد كان الملف التنفيذي الثنائي لـ pcmaker مُودَعًا في مستودع CVS الخاص بـ VTK. وكانت عدّة حالات شائعة، مثل إضافة مكتبة جديدة، تتطلّب تعديل ذلك المصدر وإيداع ملف ثنائي جديد. ورغم أن هذا كان نظامًا موحّدًا من بعض النواحي، إلا أنه اتّسم بعيوب كثيرة.

أما المنهج الآخر الذي كانت لدى المطوّرين خبرة به فهو نظام بناء لـ TargetJr يعتمد على gmake. وكانت TargetJr بيئة رؤية حاسوبية (computer vision) مكتوبة بلغة C++ طوِّرت أصلًا على أجهزة Sun. وقد استخدمت TargetJr في الأصل نظام imake لإنشاء ملفات Makefile. لكن عند الحاجة في مرحلة ما إلى منفذ لويندوز، أُنشئ نظام gmake. وكان بالإمكان استخدام كلٍّ من مترجمي Unix ومترجمي ويندوز مع نظام gmake هذا. وقد كان النظام يتطلّب ضبط عدّة متغيّرات بيئة قبل تشغيل gmake. ويؤدي غياب البيئة الصحيحة إلى فشل النظام بطرق يصعب تتبّعها، خاصةً للمستخدمين النهائيين.

عانى هذان النظامان من خلل جسيم: فهما يُجبران مطوّري ويندوز على استخدام سطر الأوامر. ويفضّل مطوّرو ويندوز ذوو الخبرة استخدام بيئات التطوير المتكاملة (IDEs). وكان هذا سيُشجّع مطوّري ويندوز على إنشاء ملفات الـ IDE يدويًا وإيداعها في المشروع، مما يُعيد إنشاء نظام البناء المزدوج من جديد. وإلى جانب غياب دعم الـ IDE، جعل النظامان الموصوفان أعلاه دمج مشاريع البرمجيات شديد الصعوبة. فمثلًا، كان لدى VTK وحدات قليلة جدًا لقراءة الصور، في معظمها لأن نظام البناء جعل استخدام مكتبات مثل libtiff وlibjpeg صعبًا للغاية.

قررنا تطوير نظام بناء جديد لـ ITK وبلغة C++ على وجه العموم. وكانت القيود الأساسية لنظام البناء الجديد كالتالي:

  • أن يعتمد على وجود مترجم C++ مثبَّت في النظام فحسب.
  • أن يكون قادرًا على توليد ملفات إدخال لـ Visual Studio IDE.
  • أن يكون إنشاء أهداف نظام البناء الأساسية سهلًا، ومنها المكتبات الثابتة والمكتبات المشتركة والملفات التنفيذية والإضافات (plugins).
  • أن يكون قادرًا على تشغيل مولّدات الشيفرة في وقت البناء.
  • أن يدعم أشجار بناء منفصلة عن شجرة المصدر.
  • أن يكون قادرًا على إجراء استبطان النظام (system introspection)، أي أن يحدّد تلقائيًا ما يستطيع النظام الهدف فعله وما لا يستطيع.
  • أن يقوم بفحص تبعيات ملفات ترويسات C/C++ تلقائيًا.
  • أن تعمل جميع المزايا على نحو متساوٍ وبنفس الجودة على جميع المنصّات المدعومة.

لتجنّب الاعتماد على أي مكتبات أو محلّلات نحوية إضافية، صُمِّمت CMake باعتماد رئيسي واحد فقط هو مترجم C++ (يمكننا أن نفترض بأمان أنه متوفّر لدينا ما دمنا نبني شيفرة C++). وفي ذلك الوقت، كان بناء لغات البرمجة النصية مثل Tcl وتثبيتها صعبًا على كثير من أنظمة UNIX ويندوز الشائعة. ولا يزال الأمر قد يمثّل مشكلة اليوم على الحواسيب الفائق (supercomputers) الحديثة والحواسيب المؤمَّنة (secured computers) التي لا تتّصل بالإنترنت، لذا لا يزال بناء المكتبات الخارجية صعبًا في بعض الأحيان. وبما أن نظام البناء متطلّب أساسي جدًا لأي حزمة، فقد اتُّخذ قرار عدم إدخال أي تبعيات إضافية في CMake. وقد حدَّ هذا الأمرَ من CMake إلى إنشاء لغتها البسيطة الخاصة، وهو خيار لا يزال يدفع بعض الناس إلى عدم الإعجاب بـ CMake. لكن كان آنذاك أكثر اللغات المضمّنة شيوعًا هو Tcl. ولو كانت CMake نظام بناء مبنيًّا على Tcl، فمن المرجّح ألّا يحظى بالشيوع الذي يتمتّع به اليوم.

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

تحتاج كلّ برامج C/C++ إلى واحد أو أكثر من لبنات البرمجيات الأساسية التالية: الملفات التنفيذية، والمكتبات الثابتة، والمكتبات المشتركة، والإضافات. وكان على CMake توفير القدرة على إنشاء هذه النواتج على جميع المنصّات المدعومة. ورغم أن جميع المنصّات تدعم إنشاء تلك النواتج، فإن رايات المترجم (compiler flags) المستخدمة لإنشائها تتفاوت إلى حدٍّ كبير من مترجم إلى آخر ومن منصّة إلى أخرى. وبإخفاء التعقيد والفروق بين المنصّات خلف أمر واحد بسيط في CMake، يصبح بإمكان المطوّرين إنشاءها على ويندوز ويونكس وMac. وهذه القدرة تتيح للمطوّرين التركيز على المشروع بدلًا من الانشغال بتفاصيل كيفية بناء مكتبة مشتركة.

تضيف مولّدات الشيفرة تعقيدًا إضافيًا إلى نظام البناء. فمنذ البداية وفّر VTK نظامًا يغلّف شيفرة C++ تلقائيًا داخل Tcl وPython وJava عبر تحليل ملفات ترويسات C++، ويولّد تلقائيًا طبقة تغليف (wrapping layer). ويتطلّب ذلك نظام بناء قادرًا على بناء ملف تنفيذي C/C++ (مولّد التغليف)، ثم تشغيل ذلك الملف التنفيذي في وقت البناء لإنشاء مزيد من شيفرة C/C++ (طبقات التغليف للوحدات المحدّدة). ويجب بعد ذلك أن تُصرَّف هذه الشيفرة المولَّدة إلى ملفات تنفيذية أو مكتبات مشتركة. وكل ذلك يجب أن يحدث ضمن بيئات الـ IDE وملفات Makefile المولَّدة.

عند تطوير برمجيات C/C++ مرنة وقابلة للنقل عبر المنصّات، من المهم أن تبرمج على نحو ما وُجدت فيه المنصّة، لا على نحو منصّة بعينها. ولدى Autotools نموذج لإجراء الاستبطان (system introspection) يقوم على صرافة مقتطفات شيفرة صغيرة، ثم فحص نتائج هذه الصرافة وتخزينها. ولأن CMake كان مقصودًا منه أن يكون متعدّد المنصّات، فقد تبنّى أسلوبًا مماثلًا للاستبطان. ويتيح ذلك للمطوّرين أن يبرمجوا على النظام المعياري (canonical system) بدلًا من أنظمة بعينها. وهذا مهم لجعل القابلية للنقل مستقبلًا ممكنة، إذ تتغيّر المترجمات وأنظمة التشغيل مع مرور الزمن. فمثلًا، شيفرة كالتالية:

#ifdef linux
// do some linux stuff
#endif

أكثر هشاشة من شيفرة كالتالية:

#ifdef HAS_FEATURE
// do something with a feature
#endif

ومن متطلبات CMake المبكرة الأخرى التي جاءت من autotools: القدرة على إنشاء أشجار بناء منفصلة عن شجرة المصدر. ويتيح ذلك إجراء أنواع بناء متعدّدة على شجرة المصدر نفسها. كما يمنع ازدحام شجرة المصدر بملفات البناء، وهو ما يشوّش على أنظمة إدارة الإصدارات في غالبَيْن من الأحوال.

يُعدّ القدرة على إدارة التبعيات (dependencies) من أهمّ مزايا نظام البناء. فإذا تغيّر ملف مصدر، وجب إعادة بناء كلّ النواتج التي تستخدم ذلك الملف المصدر. أمّا في شيفرة C/C++، فيجب أيضًا التحقّق من ملفات الترويسات التي يتضمّنها ملف .c أو .cpp كجزء من التبعيات. وقد يستهلك تتبّع المشكلات التي لا يُصرَّف بسببها سوى جزء من الشيفرة التي ينبغي تصريفها فعليًا، بسبب معلومات تبعيات غير صحيحة، وقتًا طويلًا.

كان على جميع متطلبات نظام البناء الجديد ومزاياه أن تعمل على نحو متساوٍ وعلى جميع المنصّات المدعومة. ويحتاج CMake إلى توفير واجهة برمجية بسيطة (API) تمكّن المطوّرين من إنشاء أنظمة برمجية معقّدة دون الحاجة إلى فهم تفاصيل المنصّة. وبعبارة أخرى، تُفوّض البرمجيات التي تستخدم CMake تعقيدات البناء إلى فريق CMake. وبعد أن رُسمت رؤيا أداة البناء بالمجموعة الأساسية من المتطلبات، كان على التنفيذ أن يسير بطريقة رشيقة (agile). وكانت ITK بحاجة إلى نظام بناء منذ اليوم الأول تقريبًا. ورغم أن الإصدارات الأولى من CMake لم تستوفِ جميع المتطلبات المذكورة في الرؤيا، إلا أنها سمحت بالبناء على ويندوز ويونكس.

5.2. كيف نُفِّذت CMake

كما ذُكر، فإن لغتَي تطوير CMake هما C وC++. ولشرح تفاصيلها الداخلية، سيصف هذا القسم أولًا عملية CMake من منظور المستخدم، ثم يفحص بنيها.

5.2.1. عملية CMake

للـ CMake مرحلتان رئيسيتان. الأولى هي خطوة «الضبط» (configure)، وفيها تعالج CMake كلّ المدخلات المُعطاة إليها وتبني تمثيلًا داخليًا لعملية البناء المراد تنفيذها. أمّا المرحلة التالية فهي خطوة «التوليد» (generate)، وفي هذه المرحلة تُنشأ ملفات البناء الفعلية.

متغيّرات البيئة (أو لا متغيّرات)

في كثير من أنظمة البناء عام 1999، وحتى اليوم، تُستخدم متغيّرات بيئة على مستوى الصدفة (shell) أثناء بناء المشروع. ومن المعتاد أن يملك المشروع متغيّر بيئة اسمه PROJECT_ROOT يشير إلى موضع جذر شجرة المصدر. كما تُستخدم متغيّرات البيئة للإشارة إلى حزم اختيارية أو خارجية. والمشكلة في هذا الأسلوب أن كلّ هذه المتغيّرات الخارجية يجب ضبطها في كلّ مرّة يُجرى فيها بناء كي ينجح البناء. ولمعالجة هذه المشكلة، تمتلك CMake ملف ذاكرة مؤقتة (cache) يخزّن فيه كلّ المتغيّرات اللازمة للبناء في مكان واحد. وهذه ليست متغيّرات صدفة أو بيئة، بل متغيّرات CMake. وفي أول مرّة تُشغَّل فيها CMake لشجرة بناء معيّنة، تنشئ ملفًا اسمه CMakeCache.txt يخزّن كلّ المتغيّرات الدائمة لذلك البناء. ولأن الملف جزء من شجرة البناء، فإن هذه المتغيّرات تبقى متاحة لـ CMake في كل تشغيل.

خطوة الضبط

أثناء خطوة الضبط، تقرأ CMake أولًا ملف CMakeCache.txt إن كان موجودًا من تشغيل سابق. ثم تقرأ CMakeLists.txt الموجود في جذر شجرة المصدر المُعطاة إلى CMake. وأثناء خطوة الضبط، يحلّل محلّل لغة CMake ملفات CMakeLists.txt. وينفّذ كلّ أمر من أوامر CMake الموجود في الملف كائنُ نمطِ أمر (command pattern object). ويمكن تحليل ملفات CMakeLists.txt إضافية أثناء هذه الخطوة عبر أمرَي CMake include وadd_subdirectory. ولكل أمر يمكن استخدامه في لغة CMake كائن C++ خاص به. ومن أمثلة الأوامر: add_library وif وadd_executable وadd_subdirectory وinclude. وبعبارة أخرى، تُنفَّذ لغة CMake بأكملها بوصفها نداءات إلى أوامر؛ فالمحلّل يحوّل ملفات إدخال CMake ببساطة إلى نداءات أوامر وقوائم نصوص تكون معاملات لهذه الأوامر.

تعمل خطوة الضبط في جوهرها على «تشغيل» شيفرة CMake التي قدّمها المستخدم. وبعد تنفيذ كلّ الشيفرة وحساب قيم كلّ متغيّرات الذاكرة المؤقتة، تمتلك CMake تمثيلًا في الذاكرة للمشروع المراد بناؤه. وسيشمل ذلك كلّ المكتبات والملفات التنفيذية والأوامر المخصّصة وكلّ المعلومات الأخرى اللازمة لإنشاء ملفات البناء النهائية للمولّد المختار. وعند هذه النقطة يُحفظ ملف CMakeCache.txt على القرص لاستخدامه في التشغيلات اللاحقة لـ CMake.

تمثيل المشروع في الذاكرة هو مجموعة من الأهداف (targets)، وهي ببساطة أشياء يمكن بناؤها مثل المكتبات والملفات التنفيذية. وتدعم CMake أيضًا الأهداف المخصّصة: إذ يمكن للمستخدمين تعريف مدخلاتها ومخرجاتها، وتوفير ملفات تنفيذية أو سكربتات مخصّصة تُشغَّل في وقت البناء. وتخزّن CMake كلّ هدف في كائن cmTarget. وتُخزَّن هذه الكائنات بدورها في كائن cmMakefile، وهو في جوهره مكان تخزين لكلّ الأهداف الموجودة في مجلد معيّن من شجرة المصدر. والنتيجة النهائية هي شجرة من كائنات cmMakefile تحتوي خرائط لكائنات cmTarget.

خطوة التوليد

بعد اكتمال خطوة الضبط، يمكن أن تتم خطوة التوليد. وفي خطوة التوليد تنشئ CMake ملفات البناء لأداة البناء الهدف التي اختارها المستخدم. وعند هذه النقطة يُحوَّل التمثيل الداخلي للأهداف (المكتبات والملفات التنفيذية والأهداف المخصّصة) إمّا إلى ملف إدخال لأداة بناء من نوع IDE مثل Visual Studio، وإمّا إلى مجموعة من ملفات Makefile ينفّذها make. ويكون التمثيل الداخلي لـ CMake بعد خطوة الضبط عامًّا قدر الإمكان، بحيث تتشارك أكبر قدر ممكن من الشيفرة وبنى البيانات بين أدوات البناء المختلفة.

يمكن رؤية نظرة عامّة على العملية في الشكل 5.1.

نظرة عامة على عملية CMake

الشكل 5.1: نظرة عامة على عملية CMake

5.2.2. CMake: الشيفرة

كائنات CMake

CMake نظام موجَّه نحو الكائنات (object-oriented) يستخدم الوراثة وأنماط التصميم (design patterns) والتغليف (encapsulation). ويمكن رؤية الكائنات الرئيسية في C++ وعلاقاتها في الشكل 5.2.

كائنات CMake

الشكل 5.2: كائنات CMake

تُخزَّن نتائج تحليل كلّ ملف CMakeLists.txt في كائن cmMakefile. وإلى جانب تخزين معلومات المجلد، يتحكّم كائن cmMakefile في تحليل ملف CMakeLists.txt. وتستدعي دالة التحليل كائنًا يستخدم محلّلًا قائمًا على lex/yacc للغة CMake. وبما أن صيغة لغة CMake نادرًا ما تتغيّر، ولا تتوفّر lex وyacc دائمًا على الأنظمة التي يُبنى عليها CMake، فإن ملفات مخرجات lex وyacc تُعالَج وتُخزَّن في مجلد Source ضمن التحكّم بالإصدارات إلى جانب بقية الملفات المكتوبة يدويًا.

من أصناف CMake المهمة الأخرى صنف cmCommand، وهو الصنف الأساس لتنفيذ جميع أوامر لغة CMake. ولا يقدّم كل صنف فرعي تنفيذ الأمر فحسب، بل يقدّم توثيقه أيضًا. ومثال على ذلك دوال التوثيق في الصنف cmUnsetCommand:

virtual const char* GetTerseDocumentation()
{
    return "Unset a variable, cache variable, or environment variable.";
}

/**
 * More documentation.
 */

virtual const char* GetFullDocumentation()
{
    return
      "  unset(<variable> [CACHE])\n"
      "Removes the specified variable causing it to become undefined.  "
      "If CACHE is present then the variable is removed from the cache "
      "instead of the current scope.\n"
      "<variable> can be an environment variable such as:\n"
      "  unset(ENV{LD_LIBRARY_PATH})\n"
      "in which case the variable will be removed from the current "
      "environment.";
}

تحليل التبعيات

تمتلك CMake إمكانات مدمجة وقويّة لتحليل التبعيات لملفات المصدر الفردية بلغة Fortran وC وC++. وبما أن بيئات التطوير المتكاملة (IDEs) تدعم معلومات تبعيات الملفات وتحافظ عليها، فإن CMake تتخطّى هذه الخطوة لتلك أنظمة البناء. وفي حالة البناء عبر الـ IDE، تنشئ CMake ملف إدخال أصلي للـ IDE، وتترك للـ IDE معالجة معلومات تبعيات مستوى الملف. أمّا معلومات تبعيات مستوى الهدف فتُترجَم إلى صيغة الـ IDE المستخدَمة لتحديد معلومات التبعيات.

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

رغم أن المستخدمين لا يحتاجون إلى معرفة كيف تقوم CMake بهذا العمل، فقد يكون من المفيد النظر في ملفات معلومات التبعيات الخاصة بالمشروع. وتُخزَّن هذه المعلومات لكلّ هدف في أربعة ملفات هي depend.make وflags.make وbuild.make وDependInfo.cmake. فيخزّن depend.make معلومات تبعيات كلّ ملفات الكائنات (object files) في المجلد. ويحتوي flags.make على رايات التصريف (compile flags) المستخدمة لملفات مصدر ذلك الهدف؛ فإذا تغيّرت أُعيد تصريف الملفات. ويُستخدم DependInfo.cmake لإبقاء معلومات التبعيات محدَّثة، ويحتوي على معلومات عمّا إذا كانت الملفات جزءًا من المشروع وما اللغات التي كُتبت بها. وأخيرًا تُخزَّن قواعد بناء التبعيات في build.make. فإذا كانت إحدى تبعيات الهدف قديمة، تُعاد حساب معلومات التبعيات لذلك الهدف، ما يبقيها محدَّثة. ويُجرى ذلك لأن أي تغيير في ملف ‎.h قد يضيف تبعية جديدة.

CTest وCPack

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

يُستخدم الملف التنفيذي ctest لتشغيل اختبارات الانحدار (regression tests). ويمكن للمشروع بسهولة إنشاء اختبارات تشغّلها CTest عبر الأمر add_test. ويمكن تشغيل هذه الاختبارات بواسطة CTest، التي تُستخدم أيضًا لإرسال نتائج الاختبار إلى تطبيق CDash لعرضها على الويب. وتُشبه CTest وCDash معًا أداة الاختبار Hudson إلى حدٍّ كبير. غير أنهما تختلفان في مجال رئيسي واحد: فقد صُمِّمت CTest لتتيح بيئة اختبار أكثر توزّعًا بكثير. إذ يمكن إعداد العملاء (clients) لسحب المصدر من نظام إدارة الإصدارات، وتشغيل الاختبارات، وإرسال النتائج إلى CDash. أمّا مع Hudson، فيجب أن تمنح أجهزة العملاء لـ Hudson صلاحية ssh للوصول إلى الجهاز كي يمكن تشغيل الاختبارات.

يُستخدم الملف التنفيذي cpack لإنشاء المثبّات للمشاريع. ويعمل CPack على نحو يشبه جزئية البناء في CMake فهو يتعامل مع أدوات تعبئة أخرى. فمثلًا، على ويندوز تُستخدم أداة التعبئة NSIS لإنشاء مثبّات تنفيذية من المشروع. ويشغّل CPack قواعد التثبيت في المشروع لإنشاء شجرة التثبيت، ثم تُسلَّم هذه الشجرة إلى برنامج تثبيت مثل NSIS. كما يدعم CPack إنشاء حزم RPM وملفات Debian من نوع .deb وملفات .tar و.tar.gz وملفات tar ذاتية الاستخراج.

5.2.3. الواجهات الرسومية

أوّل ما يطّلع عليه كثير من المستخدمين في CMake هو إحدى برامج واجهتها. ولكلـ CMake برنامجا واجهة رئيسيان: تطبيق نافذ قائم على Qt، وتطبيق رسومي قائم على curses يعمل من سطر الأوامر. وهذه الواجهات الرسومية (GUIs) محرّرات رسومية لملف CMakeCache.txt. وهي واجهات بسيطة نسبيًا فيها زرّان — الضبط والتوليد — يُستخدَمان لإطلاق المرحلتين الرئيسيتين في عملية CMake. والواجهة القائمة على curses متاحة على منصّات Unix من نوع TTY وعلى Cygwin. أمّا واجهة Qt فمتاحة على جميع المنصّات. ويمكن رؤية الواجهتين في الشكل 5.3 والشكل 5.4.

واجهة سطر الأوامر

الشكل 5.3: واجهة سطر الأوامر

واجهة قائمة على الرسوميات

الشكل 5.4: واجهة قائمة على الرسوميات

تحتوي كلتا الواجهتين على أسماء متغيّرات الذاكرة المؤقتة في اليسار وقيمها في اليمين. ويمكن للمستخدم تغيير القيم الموجودة في اليمين إلى قيم مناسبة للبناء. وهناك نوعان من المتغيّرات: عادية ومتقدّمة. وبالافتراضي تُعرض المتغيّرات العادية للمستخدم. ويمكن للمشروع تحديد أيّ المتغيّرات متقدّمة داخل ملفات CMakeLists.txt الخاصة به. ويتيح ذلك عرض أقل عدد ممكن من الخيارات الضروري للبناء أمام المستخدم.

ولأن قيم الذاكرة المؤقتة يمكن تعديلها أثناء تنفيذ الأوامر، فإن عملية التقارب على بناء نهائي قد تكون تكرارية (iterative). فمثلًا، قد يكشف تفعيل خيار خيارات إضافية. ولهذا السبب تُعطِّل الواجهة الرسومية زرّ «التوليد» حتى يتمكّن المستخدم من رؤية جميع الخيارات مرة واحدة على الأقل. وفي كلّ مرّة يُضغط فيها زرّ الضبط، تُعرض متغيّرات الذاكرة المؤقتة الجديدة التي لم تُعرض على المستخدم من قبل باللون الأحمر. وفي حال لم يُنشأ أي متغيّر ذاكرة مؤقتة جديد أثناء تشغيل الضبط، يُفعَّل زرّ التوليد.

5.2.4. اختبار CMake

يبدأ أي مطوّر جديد في CMake بالتعرّف على عملية الاختبار المستخدمة في تطوير CMake. وتستند هذه العملية إلى عائلة أدوات CMake (CMake وCTest وCPack وCDash). ومع تطوير الشيفرة وإيداعها في نظام إدارة الإصدارات، تبني آلات اختبار التكامل المستمر شيفرة CMake الجديدة وتختبرها آليًا باستخدام CTest. وتُرسل النتائج إلى خادم CDash الذي يُنبّه المطوّرين عبر البريد الإلكتروني عند وجود أي أخطاء في البناء أو تحذيرات من المترجم أو إخفاقات في الاختبارات.

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

فمثلًا، إذا أراد مطوّر جديد إضافة دعم لمنصّة جديدة، فإن أول سؤال يُطرح عليه هو ما إذا كان بإمكانه توفير عميل لوحة-معلومات ليلية (nightly dashboard client) لذلك النظام. وبدون اختبار مستمر، من المحتّم أن تتوقّف الأنظمة الجديدة عن العمل بعد مرور بعض الوقت.

5.3. الدروس المستفادة

نجحت CMake في بناء ITK منذ اليوم الأول، وهذا كان أهمّ أجزاء المشروع. ولو أمكننا إعادة تنفيذ تطوير CMake، فلن يتغيّر الكثير. غير أن هناك دائمًا أمور كان يمكن إنجازها على نحو أفضل.

5.3.1. التوافق مع الإصدارات السابقة

يُعدّ الحفاظ على التوافق مع الإصدارات السابقة (backwards compatibility) أمرًا مهمًا لفريق تطوير CMake. والهدف الرئيسي للمشروع هو جعل بناء البرمجيات أسهل. فحين يختار مشروع أو مطوّر CMake كأداة بناء، من المهم احترام هذا الاختيار وبذل أقصى جهد لكسر ذلك البناء في الإصدارات المستقبلية من CMake. وقد نفّذت CMake 2.6 نظام سياسات (policy system) تُصدر فيه التغييرات التي تكسر السلوك القائم تحذيرًا مع تنفيذ السلوك القديم على أي حال. ويُطلب من كلّ ملف CMakeLists.txt أن يحدّد إصدار CMake الذي يتوقّع استخدامه. وقد تُصدر الإصدارات الأحدث من CMake تحذيرات، لكنها تظل تبني المشروع كما فعلت الإصدارات الأقدم.

5.3.2. اللغة، اللغة، اللغة

المقصود بلغة CMake أن تكون شديدة البساطة. غير أنها إحدى العوائق الرئيسية أمام تبنّيها عندما يفكّر مشروع جديد في استخدام CMake. وبسبب نموّها العضوي، فإن للغة CMake بعض الطرائف (quirks). فلم يكن المحلّل الأول لهذه اللغة قائمًا على lex/yacc أصلًا، بل كان محلّل نصوص بسيطًا. ولو أتيحت لنا فرصة لإعادة صياغة اللغة، لكنا أمضينا بعض الوقت في البحث عن لغة مضمّنة جيّدة موجودة سلفًا. وLua هي أفضل خيار كان يمكن أن ينجح؛ فهي صغيرة جدًا ونقيّة. وحتى لو لم نستخدم لغة خارجية مثل Lua، لكنت قد أوليتُ اعتمادًا أكبر للغة القائمة منذ البداية.

5.3.3. الإضافات لم تنجح

لتوفير القدرة على توسيع لغة CMake من قِبل المشاريع، تمتلك CMake صنفًا للإضافات (plugins). ويتيح ذلك للمشروع إنشاء أوامر CMake جديدة بلغة C. وقد بدا ذلك فكرة جيّدة في حينه، عُرِّفت الواجهة باللغة C كي يتيسّر استخدام مترجمات مختلفة. غير أن ظهور أنظمة واجهات برمجية (API) متعدّدة مثل ويندوز ولينكس 32/64 بت جعل الحفاظ على توافق الإضافات صعبًا. ورغم أن توسيع CMake بلغة CMake أقلّ قوة، فهو يمنع CMake من الانهيار أو من عدم القدرة على بناء مشروع بسبب إضافة فشلت في البناء أو التحميل.

5.3.4. تقليص واجهات البرمجة المكشوفة

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

الحواشي

  1. http://www.itk.org/

  1. http://www.itk.org/ ↩︎