دليل حيوي لـ Qmake
نشرت: 2022-03-11مقدمة
qmake هي أداة بناء نظام يتم شحنها مع مكتبة Qt التي تبسط عملية البناء عبر منصات مختلفة. على عكس CMake و Qbs ، كان qmake جزءًا من Qt منذ البداية ويجب اعتباره أداة "أصلية". وغني عن القول ، إن IDE الافتراضي الخاص بـ Qt - Qt Creator - لديه أفضل دعم لـ qmake خارج الصندوق. نعم ، يمكنك أيضًا اختيار أنظمة إنشاء CMake و Qbs لمشروع جديد هناك ، لكن هذه الأنظمة ليست متكاملة جيدًا. من المحتمل أن يتم تحسين دعم CMake في Qt Creator بمرور الوقت ، وسيكون هذا سببًا جيدًا لإصدار الإصدار الثاني من هذا الدليل ، والذي يستهدف CMake تحديدًا. حتى إذا كنت لا تنوي استخدام Qt Creator ، فقد لا تزال ترغب في اعتبار qmake كنظام بناء ثان في حال كنت تقوم ببناء مكتبات عامة أو مكونات إضافية. تقريبًا جميع المكتبات أو المكونات الإضافية للجهات الخارجية المستندة إلى Qt توفر ملفات qmake المستخدمة للاندماج في المشاريع القائمة على qmake بسلاسة. فقط عدد قليل منهم يوفر تكوينًا مزدوجًا ، على سبيل المثال ، qmake و CMake. قد تفضل استخدام qmake إذا كان ما يلي ينطبق عليك:
- أنت تبني مشروعًا متعدد المنصات قائم على Qt
- أنت تستخدم Qt Creator IDE ومعظم ميزاته
- أنت تقوم ببناء مكتبة / مكون إضافي مستقل لتستخدمه مشاريع qmake الأخرى
يصف هذا الدليل أكثر ميزات qmake فائدة ويوفر أمثلة واقعية لكل منها. يمكن للقراء الجدد في Qt استخدام هذا الدليل كدليل تعليمي لنظام إنشاء Qt. يمكن لمطوري Qt التعامل مع هذا ككتاب طبخ عند بدء مشروع جديد أو يمكنهم بشكل انتقائي تطبيق بعض الميزات على أي من المشاريع الحالية ذات التأثير المنخفض.
استخدام Qmake الأساسي
تتم كتابة مواصفات qmake في ملفات .pro
("مشروع"). هذا مثال على أبسط ملف .pro
ممكن:
SOURCES = hello.cpp
بشكل افتراضي ، سيؤدي هذا إلى إنشاء Makefile
الذي من شأنه إنشاء ملف تنفيذي من ملف التعليمات البرمجية المصدر الفردي hello.cpp
.
لإنشاء الملف الثنائي (القابل للتنفيذ في هذه الحالة) ، تحتاج إلى تشغيل qmake أولاً لإنتاج Makefile ثم إنشاء (أو nmake
، أو mingw32-make
make
على سلسلة الأدوات الخاصة بك) لبناء الهدف.
باختصار ، فإن مواصفات qmake ليست أكثر من قائمة من التعريفات المتغيرة الممزوجة ببيانات تدفق التحكم الاختيارية. كل متغير ، بشكل عام ، يحمل قائمة من السلاسل. تسمح لك عبارات تدفق التحكم بتضمين ملفات مواصفات qmake الأخرى ، والتحكم في الأقسام الشرطية ، وحتى وظائف الاستدعاء.
فهم صيغة المتغيرات
عند تعلم مشاريع qmake الحالية ، قد تندهش من كيفية الإشارة إلى المتغيرات المختلفة: \ (VAR ، \) {VAR} أو $$ (VAR) ...
استخدم ورقة الغش المصغرة هذه أثناء اعتماد القواعد:
-
VAR = value
تعيين قيمة إلى VAR -
VAR += value
إلحاق القيمة بقائمة VAR -
VAR -= value
إزالة القيمة من قائمة VAR - يحصل
$$VAR
أو$${VAR}
على قيمة VAR في وقت تشغيل qmake -
$(VAR)
محتويات VAR البيئة في وقت تشغيل Makefile (وليس qmake) -
$$(VAR)
محتويات VAR البيئة في وقت تشغيل qmake (وليس Makefile)
القوالب المشتركة
يمكن العثور على القائمة الكاملة لمتغيرات qmake في المواصفات: http://doc.qt.io/qt-5/qmake-variable-reference.html
دعنا نراجع بعض القوالب الشائعة للمشاريع:
# Windows application TEMPLATE = app CONFIG += windows # Shared library (.so or .dll) TEMPLATE = lib CONFIG += shared # Static library (.a or .lib) TEMPLATE = lib CONFIG += static # Console application TEMPLATE = app CONFIG += console
ما عليك سوى إضافة SOURCES + = ... و HEADERS + = ... لسرد جميع ملفات التعليمات البرمجية المصدر ، وقد انتهيت.
حتى الآن ، قمنا بمراجعة القوالب الأساسية للغاية. عادة ما تتضمن المشاريع الأكثر تعقيدًا العديد من المشاريع الفرعية التي تعتمد على بعضها البعض. دعونا نرى كيفية إدارة هذا باستخدام qmake.
المشاريع الفرعية
حالة الاستخدام الأكثر شيوعًا هي أحد التطبيقات التي يتم شحنها مع واحدة أو عدة مكتبات ومشروعات اختبار. ضع في اعتبارك الهيكل التالي:
/project ../library ..../include ../library-tests ../application
من الواضح أننا نريد أن نكون قادرين على بناء كل شيء مرة واحدة ، مثل هذا:
cd project qmake && make
لتحقيق هذا الهدف ، نحتاج إلى ملف مشروع qmake ضمن مجلد /project
:
TEMPLATE = subdirs SUBDIRS = library library-tests application library-tests.depends = library application.depends = library
ملاحظة: يعتبر استخدام CONFIG += ordered
ممارسة سيئة - يفضل استخدام .depends
بدلاً من ذلك.
ترشد هذه المواصفات qmake إلى بناء مشروع مكتبة فرعي أولاً لأن الأهداف الأخرى تعتمد عليه. ثم يمكن بناء library-tests
والتطبيق بترتيب تعسفي لأن هذين الأمرين تابعين.
ربط المكتبات
في المثال أعلاه ، لدينا مكتبة تحتاج إلى ربطها بالتطبيق. في C / C ++ ، هذا يعني أننا بحاجة إلى تكوين بعض الأشياء الأخرى:
- حدد
-I
لتقديم مسارات بحث لتوجيهات #include. - حدد
-L
لتوفير مسارات البحث للرابط. - حدد
-l
لتوفير ما تحتاجه المكتبة للربط.
لأننا نريد أن تكون جميع المشاريع الفرعية قابلة للنقل ، لا يمكننا استخدام المسارات المطلقة أو النسبية. على سبيل المثال ، لن نقوم بهذا: INCLUDEPATH + = ../library/include وبالطبع لا يمكننا الإشارة إلى ملف مكتبة ثنائي (ملف .a) من مجلد بناء مؤقت. باتباع مبدأ "فصل الاهتمامات" ، يمكننا أن ندرك بسرعة أن ملف مشروع التطبيق يجب أن يستخلص من تفاصيل المكتبة. بدلاً من ذلك ، تقع على عاتق المكتبة مسؤولية تحديد مكان العثور على ملفات الرأس ، وما إلى ذلك.
دعنا نستفيد من توجيه qmake's include()
لحل هذه المشكلة. في مشروع المكتبة ، سنضيف مواصفات qmake أخرى في ملف جديد بامتداد .pri
(يمكن أن يكون الامتداد أي شيء ، ولكن هنا i
تضمين). لذلك ، سيكون للمكتبة نوعان من المواصفات: library.pro
و library.pri
. يستخدم الأول لبناء المكتبة ، ويستخدم الثاني لتوفير جميع التفاصيل التي يحتاجها مشروع مستهلك.
سيكون محتوى ملف library.pri كما يلي:
LIBTARGET = library BASEDIR = $${PWD} INCLUDEPATH *= $${BASEDIR}/include LIBS += -L$${DESTDIR} -llibrary
يحدد BASEDIR
مجلد مشروع المكتبة (على وجه الدقة ، موقع ملف مواصفات qmake الحالي ، وهو library.pri
في حالتنا). كما قد تتخيل ، سيتم تقييم INCLUDEPATH
إلى /project/library/include
. DESTDIR
هو الدليل الذي يضع فيه نظام الإنشاء عناصر الإخراج ، مثل (ملفات o .a .so .dll أو .exe). يتم تكوين هذا عادةً في IDE الخاص بك ، لذلك لا يجب أبدًا وضع أي افتراضات حول مكان وجود ملفات الإخراج.
في ملف application.pro
أضف فقط include(../library/library.pri)
تكون قد انتهيت.
دعنا نراجع كيف يتم بناء مشروع التطبيق في هذه الحالة:
- Topmost
project.pro
هو مشروع من الباطن. يخبرنا أن مشروع المكتبة يحتاج إلى البناء أولاً. لذلك يدخل qmake إلى مجلد المكتبة ويبنيه باستخدامlibrary.pro
. في هذه المرحلة ، يتم إنتاجlibrary.a
ووضعه في مجلدDESTDIR
. - ثم يدخل qmake المجلد الفرعي للتطبيق ويوزع ملف
application.pro
. يعثر على التوجيهinclude(../library/library.pri)
، الذي يوجه qmake لقراءته وتفسيره على الفور. يضيف هذا تعريفات جديدة لمتغيريINCLUDEPATH
وLIBS
، لذا يعرف المترجم والرابط الآن مكان البحث عن تضمين الملفات وثنائيات المكتبة والمكتبة التي يجب ربطها.
تخطينا بناء مشروع اختبارات المكتبة ، لكنه مطابق لمشروع التطبيق. من الواضح أن مشروعنا الاختباري سيحتاج أيضًا إلى ربط المكتبة التي من المفترض أن تختبرها.

باستخدام هذا الإعداد ، يمكنك بسهولة نقل مشروع المكتبة إلى مشروع qmake آخر وتضمينه ، وبالتالي الرجوع إلى ملف .pri
. هذا هو بالضبط كيف يتم توزيع مكتبات الطرف الثالث من قبل المجتمع.
config.pri
من الشائع جدًا أن يكون لمشروع معقد بعض معلمات التكوين المشتركة التي تستخدمها العديد من المشاريع الفرعية. لتجنب الازدواجية ، يمكنك الاستفادة مرة أخرى من التوجيه include()
وإنشاء config.pri
في مجلد المستوى الأعلى. قد يكون لديك أيضًا "أدوات مساعدة" مشتركة qmake تتم مشاركتها مع مشاريعك الفرعية ، على غرار ما نناقشه بعد ذلك في هذا الدليل.
نسخ القطع الأثرية إلى DESTDIR
غالبًا ما تحتوي المشاريع على بعض الملفات "الأخرى" التي يجب توزيعها مع مكتبة أو تطبيق. نحتاج فقط إلى أن نكون قادرين على نسخ كل هذه الملفات إلى DESTDIR
أثناء عملية الإنشاء. ضع في اعتبارك المقتطف التالي:
defineTest(copyToDestDir) { files = $$1 for(FILE, files) { DDIR = $$DESTDIR FILE = $$absolute_path($$FILE) # Replace slashes in paths with backslashes for Windows win32:FILE ~= s,/,\\,g win32:DDIR ~= s,/,\\,g QMAKE_POST_LINK += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR) $$escape_expand(\\n\\t) } export(QMAKE_POST_LINK) }
ملاحظة: باستخدام هذا النمط ، يمكنك تحديد الوظائف التي يمكن إعادة استخدامها والتي تعمل على الملفات.
ضع هذا الرمز في /project/copyToDestDir.pri
بحيث يمكنك include()
في طلب المشاريع الفرعية على النحو التالي:
include(../copyToDestDir.pri) MYFILES += \ parameters.conf \ testdata.db ## this is copying all files listed in MYFILES variable copyToDestDir($$MYFILES) ## this is copying a single file, a required DLL in this example copyToDestDir($${3RDPARTY}/openssl/bin/crypto.dll)
ملاحظة: تم تقديم DISTFILES للغرض نفسه ، لكنها تعمل فقط في نظام Unix.
رمز الجيل
مثال رائع على إنشاء الكود كخطوة مسبقة الصنع هو عندما يستخدم مشروع C ++ Google protobuf. دعونا نرى كيف يمكننا حقن التنفيذ protoc
في عملية البناء.
يمكنك بسهولة البحث في Google عن حل مناسب ، ولكن عليك أن تكون على دراية بحالة ركنية مهمة. تخيل أن لديك عقدين ، حيث يشير "أ" إلى "ب".
A.proto <= B.proto
إذا قمنا بإنشاء رمز لـ A.proto
أولاً (لإنتاج A.pb.h
و A.pb.cxx
) وإطعامه للمترجم ، فسوف يفشل فقط لأن التبعية B.pb.h
غير موجودة بعد. لحل هذه المشكلة ، نحتاج إلى اجتياز جميع مراحل إنشاء الكود الأولي قبل إنشاء الكود المصدري الناتج.
لقد وجدت مقتطفًا رائعًا لهذه المهمة هنا: https://github.com/jmesmon/qmake-protobuf-example/blob/master/protobuf.pri
إنه نص برمجي كبير إلى حد ما ، لكن يجب أن تعرف بالفعل كيفية استخدامه:
PROTOS = A.proto B.proto include(protobuf.pri)
عند النظر في protobuf.pri
، قد تلاحظ النمط العام الذي يمكن تطبيقه بسهولة على أي تجميع مخصص أو إنشاء رمز:
my_custom_compiler.name = my custom compiler name my_custom_compiler.input = input variable (list) my_custom_compiler.output = output file path + pattern my_custom_compiler.commands = custom compilation command my_custom_compiler.variable_out = output variable (list) QMAKE_EXTRA_COMPILERS += my_custom_compiler
النطاقات والشروط
غالبًا ما نحتاج إلى تعريف الإعلانات خصيصًا لمنصة معينة ، مثل Windows أو MacOS. يقدم Qmake ثلاثة مؤشرات محددة مسبقًا للنظام الأساسي: win32 و macx و unix. هنا بناء الجملة:
win32 { # add Windows application icon, not applicable to unix/macx platform RC_ICONS += icon.ico }
يمكن أن تكون النطاقات متداخلة ، ويمكن استخدام عوامل التشغيل !
، |
وحتى أحرف البدل:
macx:debug { # include only on Mac and only for debug build HEADERS += debugging.h } win32|macx { HEADERS += windows_or_macx.h } win32-msvc* { # same as win32-msvc|win32-mscv.net }
ملاحظة: تم تعريف Unix على نظام التشغيل Mac OS! إذا كنت ترغب في اختبار نظام التشغيل Mac OS (وليس نظام Unix العام) ، فاستخدم حالة unix:!macx
.
في Qt Creator ، لا تعمل شروط النطاق ، debug
release
كما هو متوقع. لجعلها تعمل بشكل صحيح ، استخدم النمط التالي:
CONFIG(debug, debug|release) { LIBS += ... } CONFIG(release, debug|release) { LIBS += ... }
وظائف مفيدة
يحتوي Qmake على عدد من الوظائف المضمنة التي تضيف المزيد من الأتمتة.
المثال الأول هو وظيفة files()
. بافتراض أن لديك خطوة إنشاء رمز تنتج عددًا متغيرًا من الملفات المصدر. إليك كيفية تضمينها جميعًا في SOURCES
:
SOURCES += $$files(generated/*.c)
سيجد هذا جميع الملفات ذات الامتداد .c
في المجلد الفرعي الذي تم generated
وإضافتها إلى متغير SOURCES
.
المثال الثاني مشابه للمثال السابق ، ولكن الآن أنتج إنشاء الكود ملفًا نصيًا يحتوي على أسماء ملفات الإخراج (قائمة الملفات):
SOURCES += $$cat(generated/filelist, lines)
سيؤدي هذا فقط إلى قراءة محتوى الملف والتعامل مع كل سطر على أنه إدخال SOURCES
.
ملاحظة: يمكن العثور على القائمة الكاملة للوظائف المضمنة هنا: http://doc.qt.io/qt-5/qmake-function-reference.html
التعامل مع التحذيرات على أنها أخطاء
يستخدم المقتطف التالي ميزة النطاق الشرطي الموضحة مسبقًا:
*g++*: QMAKE_CXXFLAGS += -Werror *msvc*: QMAKE_CXXFLAGS += /WX
سبب هذا التعقيد هو أن MSVC لها علامة مختلفة لتمكين هذا الخيار.
توليد نسخة Git
يكون المقتطف التالي مفيدًا عندما تحتاج إلى إنشاء تعريف معالج مسبق يحتوي على إصدار SW الحالي الذي تم الحصول عليه من Git:
DEFINES += SW_VERSION=\\\"$$system(git describe --always --abbrev=0)\\\"
يعمل هذا على أي نظام أساسي طالما أن الأمر git
متاح. إذا كنت تستخدم علامات Git ، فسيؤدي ذلك إلى إلقاء نظرة خاطفة على أحدث علامة ، على الرغم من المضي قدمًا في الفرع. قم بتعديل الأمر git describe
للحصول على مخرجات من اختيارك.
خاتمة
Qmake هي أداة رائعة تركز على بناء مشاريعك المستندة إلى Qt عبر الأنظمة الأساسية. في هذا الدليل ، راجعنا استخدام الأداة الأساسية والأنماط الأكثر استخدامًا والتي ستحافظ على مرونة هيكل مشروعك وتبني المواصفات سهلة القراءة والصيانة.
هل تريد معرفة كيفية جعل تطبيق Qt الخاص بك يبدو أفضل؟ جرب: كيفية الحصول على أشكال زوايا مستديرة في C ++ باستخدام Bezier Curves و QPainter: دليل خطوة بخطوة