لغة C++، على غرار C، لها تاريخ طويل ومتنوّع بخصوص سير العمل التصريفي وعمليات البناء. واليوم، لدى لغة C++ العديد من أنظمة البناء التي تُستخدَم لتصريف البرامج لعدة منصات أحيانًا داخل نظام بناء واحد. وسننظر في هذا الدرس في بعض أنظمة البناء ونحلّلها.
إنشاء بيئة بناء باستخدام CMake
ينشئ CMake بيئات البناء للمُصرِّفات أو بيئات التطوير المتكاملة (IDE) انطلاقًا من تعريف مشروع واحد، وسوف توضّح الأمثلة التالية كيفية إضافة ملف CMake إلى برنامج "Hello World" متعدّد المنصات.
تأخذ ملفات CMake دائمًا الاسم "CMakeLists.txt"، ويجب أن تكون موجودة في مجلّد الجذر لكل مشروع، وربما في المجلدات الفرعية أيضا.
هذا مثال على ملف CMakeLists.txt:
cmake_minimum_required(VERSION 2.4) project(HelloWorld) add_executable(HelloWorld main.cpp)
يمكنك رؤية البرنامج يعمل مباشرة من هنا.
هذا الملفُّ يخبر أداةَ CMake باسم المشروع، وإصدار الملف المتوقع، وبعض التعليمات الضرورية لتوليد ملف تنفيذي (executable) يُسمّى "HelloWorld"، والذي يتطلب ملف main.cpp
.
في المثال التالي: أنشئ بيئة بناء للمُصرِّف أو بيئة تطوير متكاملة من سطر الأوامر:
> cmake .
ابن التطبيق بما يلي:
> cmake --build .
تولّد هذه الشيفرة بيئة البناء الافتراضية للنظام وفق نظام التشغيل والأدوات المُثبّتة، حافظ على الشيفرة المصدرية نظيفة من آثار البناء (build artifacts) باستخدام نمط تصميم "خارج المصدر" (out-of-source):
> mkdir build > cd build > cmake .. > cmake --build .
يمكن لـ CMake أيضًا تجريد (abstract) الأوامر الأساسية للصدفة (shell) الخاصة بالمنصة من المثال السابق:
> cmake -E make_directory build > cmake -E chdir build cmake .. > cmake --build build
يتضمّن CMake مولدّات لعدد من أدوات البناء الشائعة وبيئات التطوير المتكاملة، ولإنشاء ملفات makefiles لأجل nmake الخاصة بـ Visual Studio:
> cmake -G "NMake Makefiles" .. > nmake
التصريف باستخدام GNU
GNU Make هو برنامج مخصّص لأتمتة تنفيذ أوامر الصدفة، وينتمي لعائلة برامج Make، ويتمتع Make بشعبية كبيرة لدى مستخدمي أنظمة التشغيل الشبيهة بيونكس وتلك الموافقة لمعايير POSIX، بما في ذلك الأنظمة المشتقة من نواة لينُكس ونظام Mac OSX ونظام BSD، ويتميّز GNU Make بأنّه مرتبط بمشروع جنو الذي يرتبط بدوره بأنظمة التشغيل المبنية على جنو\لينكس الشهير.
هناك إصدارات أخرى من GNU Make متوافقة مع أنظمة تشغيل أخرى، مثل ويندوز و Mac OS X، وهذا البرنامج مستقر للغاية وله أثر تاريخي يبقيه مشهورًا، لهذا يُدرَّس GNU Make إلى جانب لغتي C و C++.
القواعد الأساسية
يجب أن تنشئ ملف Makefile في مجلّد المشروع لأجل التصريف بواسطة make. هذا مثال بسيط على ملف Makefile: سنقسم الملف أثناء سرد المثال للشرح …. Makefile
# إعداد بعض المتغيرات لاستخدامها مع الأمر # g++ أولا، سنحدد المصرف CXX=g++ # وغيرها g++ ثم نصرّف مع التنبيهات الموصى بها في CXXFLAGS=-Wall -Wextra -pedantic # هذا هو ملف الخرج EXE=app SRCS=main.cpp
يُستدَعى هذا الهدف عند استدعاء make
في سطر الأوامر، وتقول (EXE)$
الموجودة على اليمين أن الهدف all
يعتمد على الهدف (EXE)$
. لاحظ أنه بما أن هذا هو الهدف الأول، فسيكون الهدف الافتراضي إن استُدعيَت make
بدون هدف، نتابع المثال …
all: $(EXE) # هذا يكافئ # app: $(SRCS)
(SRCS)$ يمكن فصلها، مما يعني أن هذا الهدف سيعتمد على كل الملفات. لاحظ أن هذا الهدف له متن تابع (method body)، وهو الجزء المزاح بمسافة جدول واحدة (tab) وليس أربع مسافات فارغة. وسينفذ make
الأمر التالي عند بناء هذا الهدف:
g++ -Wall -Wextra -pedantic -o app main.cpp
والذي يعني تصريف main.cpp
مع التحذيرات، والإخراج إلى الملف app/.
، نتابع …
$(EXE): $(SRCS) @$(CXX) $(CXXFLAGS) -o $@ $(SRCS)
هذا الهدف ينبغي له أن يعكس الهدف all
، وإن استدعيْتَ make
مع وسيط مثل make clean
فسيُستَدعى الهدف gets
الموافق له، …
clean: @rm -f $(EXE)
ملاحظة: تأكد من أن الإزاحات البادئة قد تمت بزر الجدول (tab)، ولا تستخدم 4 مسافات بيضاء من زر Space. وإلا فسيُطلق الخطأ Makefile:10: *** missing separator. Stop.
.
لتنفيذ هذه الشيفرة من سطر الأوامر، عليك كتابة ما يلي:
$ cd ~/Path/to/project $ make $ ls app main.cpp Makefile $ ./app Hello World! $ make clean $ ls main.cpp Makefile
عمليات البناء التزايدية التزايدي Incremental builds
تظهر فائدة make عندما تكثر الملفات، فماذا لو عدّلت الملف a.cpp دون الملف b.cpp؟ لن يكون من الحكمة إذًا أن تعيد تصريف b.cpp، لأنّ ذلك سيُهدر الوقت. في المثال أدناه، لنفرض أن لدينا الهيكل التالي لمجلد:
. +-- src | +-- a.cpp | +-- a.hpp | +-- b.cpp | +-- b.hpp +-- Makefile
فسيكون هذا ملف Makefile جيد:
CXX=g++ CXXFLAGS=-Wall -Wextra -pedantic EXE=app SRCS_GLOB=src/*.cpp SRCS=$(wildcard $(SRCS_GLOB)) OBJS=$(SRCS:.cpp=.o) all: $(EXE) $(EXE): $(OBJS) @$(CXX) -o $@ $(OBJS) depend: .depend .depend: $(SRCS) @-rm -f ./.depend @$(CXX) $(CXXFLAGS) -MM $^>>./.depend clean: -rm -f $(EXE) -rm $(OBJS) -rm *~ -rm .depend include .depend
مرّة أخرى، لاحظ إزاحات الجدول البادئة. سيضمن ملفّ Makefile الجديد ألّا تصرّف إلّا الملفات التي عُدِّلت، وهذا سيقلّل وقت التصريف.
التوثيق
للحصول على المزيد من المعلومات عن make، راجع التوثيق الرسمي -بالإنجليزية- من مؤسسة البرمجيات الحرة وإجابة dmckee التفصيلية على موقع stackoverflow.
البناء بواسطة SCons
يمكنك بناء شيفرة "Hello World" متعدّدة المنصات باستخدام أداة Scons، وهي أداة بناء برامج تستخدم لغة بايثون.
أولاً، عليك إنشاء ملف يُسمّى SConstruct
-سيبحث SCons عن هذا الملف افتراضيًا-، وينبغي أن يكون الملف -للآن- في مجلّد مع الملف hello.cpp
. اكتب السطر التالي في الملف الجديد:
Program('hello.cpp')
الآن، من سطر الأوامر، نفّذ الأمر scons
، ينبغي أن ترى شيئًا من هذا القبيل:
$ scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... g++ -o hello.o -c hello.cpp g++ -o hello hello.o scons: done building targets.
رغم أن التفاصيل ستختلف وفقًا لنظام التشغيل والمُصرِّف المُستخدم. سيساعدك الصنفان Environment
و Glob
على إعداد ما يجب بناؤه. على سبيل المثال، سيبني الملف SConstruct
الملف التنفيذي hello
باستخدام جميع ملفات cpp
في مجلد src
، كما أن CPPPATH
الخاص به سيكون /usr/include/boost
، كما يحدّد معيار C++ 11، انظر:
env=Environment(CPPPATH='/usr/include/boost/', CPPDEFINES=[], LIBS=[], SCONS_CXX_STANDARD="c++11" ) env.Program('hello', Glob('src/*.cpp'))
Autotools
هي مجموعة من البرامج التي تُستخدَم لإنشاء نظام بناء جنو (GNU Build System) لأجل حزمة برامج معيّنة، وهي حزمة من الأدوات تعمل معًا لإنتاج العديد من موارد البناء، مثل ملفات Makefile -لتُستخدَم مع برنامج GNU Make، لهذا تُعدُّ هذه الحزمة المولّد المعياري لأنظمة البناء. فيما يلي بعض برامج Autotools الأساسية:
- Autoconf.
-
Automake (لا تخلط بينها وبين
make
).
والهدف من حزمة Autotools بشكل عام، هو توليد برنامج نصّي متوافق مع يونيكس (Unix-compatible script)، إضافة إلى ملفّ Makefile للسماح للأمر التالي بإنجاز عملية البناء -وكذلك تثبيت- معظم الحُزم:
./configure && make && make install
وترتبط حزمة Autotools على هذا النحو أيضًا ببعض مدراء الحزم (package managers)، خاصة تلك المرتبطة بأنظمة التشغيل التي تتوافق مع POSIX.
Ninja
يوصف نظام بناء Ninja في موقعه بأنه "نظام بناء صغير يركّز على السرعة"، وصُمِّم هذا النظام لكي تُبنى ملفاته بواسطة مولدات ملفات بناء النظام (build system file generators)، ويعتمد أسلوبًا منخفض المستوى (low-level) لبناء الأنظمة، وذلك على عكس مدراء أنظمة البناء عالية المستوى مثل CMake أو Meson.
كُتِب نظام Ninja أساسًا بلغة C++ و Python، وقد أُنشِئ كبديل لنظام البناء SCons في مشروع Chromium.
NMAKE (أداة صيانة برامج Microsoft)
NMAKE هي أداة سطر أوامر طوّرتها ميكروسوفت لاستخدامها مع Visual Studio و / أو أدوات سطر أوامر Visual C++. ويُنظر إليها على أنها نظام بناء يندرج ضمن مجموعة عائلة Make لأنظمة البناء، ولكنّه يتميّز ببعض الميزات الخاصّة التي تميّزه عن برامج Make الشبيهة بيونكس الأخرى، مثل دعم صياغة مسارات ملفات Windows (والتي تختلف عن مسارات الملفات في يونيكس).
أخطاء المصرف الشائعة (GCC)
مرجعّ غير معرَّف إلى "***"
تحدث أخطاء الرابط (linker) عندما يعجز عن العثور عن رمز مُستخدم، ويحدث هذا غالبًا عند عدم ربط إحدى المكتبات المستخدمة.
- qmake:
LIBS += nameOfLib
- cmake:
TARGET_LINK_LIBRARIES(target nameOfLib)
- استدعاء g++:
g++ -o main main.cpp -Llibrary/dir -lnameOfLib
قد ينسى المرء أيضًا تصريف وربط جميع ملفّات .cpp
المستخدمة (تحدّد functionModule.cpp الدالّة المطلوبة):
g++ -o binName main.o functionsModule.o
error: '***' was not declared in this scope
يحدث هذا الخطأ في حال استخدام كائن غير معروف.
أخطاء متعلقة بالمتغيّرات
لن تُصرّف الشيفرة التالية، لأن المتغير i
غير موجود في نطاق الدالة main
:
#include <iostream> int main(int argc, char *argv[]) { { int i = 2; } std::cout << i << std::endl; return 0; }
الحل:
#include <iostream> int main(int argc, char *argv[]) { { int i = 2; std::cout << i << std::endl; } return 0; }
أخطاء متعلقة بالدوال
يحدث هذا الخطأ غالبًا في حال عدم تضمين الترويسة المطلوبة (على سبيل المثال استخدام std::cout
دون #include <iostream>
). ففي المثال التالي، لن تُصرّف الشيفرة التالية:
#include <iostream> int main(int argc, char *argv[]) { doCompile(); return 0; } void doCompile() { std::cout << "No!" << std::endl; }
الحلّ:
#include <iostream> void doCompile(); // تصريح لاحق للدالة int main(int argc, char *argv[]) { doCompile(); return 0; } void doCompile() { std::cout << "No!" << std::endl; }
أو:
#include <iostream> void doCompile() // تعريف الدالة قبل استخدامها { std::cout << "No!" << std::endl; } int main(int argc, char *argv[]) { doCompile(); return 0; }
ملاحظة: يفسر المُصرِّف الشيفرة من الأعلى إلى الأسفل (للتبسيط فقط)، لذا يجب التصريح عن كل شيء (أو تعريفه) قبل استخدامه.
fatal error: ***: No such file or directory
يحدث هذا الخطأ عندما يعجز المُصرِّف عن العثور عن ملفّ معيّن (ملف مصدري يستخدم #include "someFile.hpp"
).
- qmake:
INCLUDEPATH += dir / Of / File
- cmake:
include_directories(dir/Of/File)
- استدعاء g++:
g++ -o main main.cpp -Idir/Of/File
عدم التوافقية مع لغة C
سنتحدّث في هذا القسم عن شيفرات C غير المتوافقة مع لغة C++ والتي ستعطل مصرّفاتها.
الكلمات المفتاحية المحجوزة
هناك كلمات مفتاحية لها غرض خاص في C++، فالشيفرة التالية مثلًا جائزة في C، لكن غير جائزة في C++.
int class = 5
وإصلاح هذه الأخطاء سهل، فكل ما عليك هو إعادة تسمية المتغيّر.
المؤشّرات ذات النوعية الضعيفة Weakly Typed Pointers
يمكن تحويل المؤشّرات في C إلى النوع void*
، أما في C++ فستحتاج إلى تحويل صريح explicit cast. انظر المثال التالي حيث تكون الشيفرة غير جائزة في C++، ولكن جائزة في C:
void* ptr; int* intptr = ptr;
إضافة تحويل صريح (explicit cast) ستحل المشكلة لكن قد يسبّب مشاكل أخرى.
goto أو switch
لا يجوز في ++C أن تتخطي التهيئة باستخدام goto
أو switch
. انظر المثال التالي حيث تكون الشيفرة صحيحة في C، وغير صحيحة في C++:
goto foo; int skipped = 1; foo;
قد يتطلب إصلاح هذه الأخطاء إعادة تصميم البرنامج.
هذا الدرس جزء من سلسلة دروس عن C++.
ترجمة -بتصرّف- للفصول
- Chapter 130: Build Systems
- Chapter 139: Common compile/linker errors (GCC)
- Chapter 136: C incompatibilities
من كتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.