
أثناء عملية التطوير ، أود تغيير برنامج التحويل البرمجي وإنشاء صيغ وإصدارات التبعية وإجراء تحليل ثابت وقياس الأداء وجمع التغطية وإنشاء الوثائق وما إلى ذلك. وأنا حقا أحب CMake ، لأنه يتيح لي أن أفعل كل ما أريد.
العديد من تأنيب CMake ، وغالبا ما يكون عادلا ، ولكن إذا نظرت ، ليس كل شيء سيئا للغاية ، ومؤخرا جيدة جدا ، واتجاه التنمية هو إيجابي للغاية.
في هذه المقالة ، أود أن أوضح مدى سهولة تنظيم مكتبة رأس C ++ في نظام CMake للحصول على الوظائف التالية:
- التجمع.
- اختبارات التشغيل التلقائي
- قياس تغطية الرمز ؛
- تركيب.
- توثيق السيارات
- جيل رمل الانترنت.
- تحليل ثابت
يمكن لأي شخص يفهم إيجابيات s-make بالفعل تنزيل قالب المشروع والبدء في استخدامه.
محتوى
- مشروع من الداخل الى الخارج
- هيكل المشروع
- ملف CMake الرئيسي (./CMakeLists.txt)
- معلومات المشروع
- خيارات المشروع
- خيارات التجميع
- الهدف الرئيسي
- تركيب
- اختبارات
- الوثائق
- رمل على الانترنت
- برنامج نصي للاختبارات (اختبار / CMakeLists.txt)
- تجريب
- تغطية
- برنامج نصي للتوثيق (doc / CMakeLists.txt)
- البرنامج النصي لصندوق رمل على الإنترنت (عبر الإنترنت / CMakeLists.txt)
- مشروع خارج
- جمعية
- جيل
- جمعية
- خيارات
- MYLIB_COVERAGE
- MYLIB_TESTING
- MYLIB_DOXYGEN_LANGUAGE
- أهداف الجمعية
- افتراضيا
- mylib وحدة الاختبارات
- تحقق
- تغطية
- وثيقة
- wandbox
- أمثلة
- الأدوات
- تحليل ثابت
- خاتمة
. ├── CMakeLists.txt ├── README.en.md ├── README.md ├── doc │ ├── CMakeLists.txt │ └── Doxyfile.in ├── include │ └── mylib │ └── myfeature.hpp ├── online │ ├── CMakeLists.txt │ ├── mylib-example.cpp │ └── wandbox.py └── test ├── CMakeLists.txt ├── mylib │ └── myfeature.cpp └── test_main.cpp
سنتحدث بشكل أساسي عن كيفية تنظيم البرامج النصية CMake ، لذلك سيتم تحليلها بالتفصيل. يمكن للجميع رؤية بقية الملفات مباشرة على صفحة قالب المشروع .
بادئ ذي بدء ، تحتاج إلى طلب الإصدار الصحيح من نظام CMake. CMake يتطور ، وتوقيعات الفريق ، والسلوك في ظروف مختلفة تتغير. لكي يفهم CMake على الفور ما نريده منه ، نحتاج إلى إصلاح متطلباتنا على الفور له.
cmake_minimum_required(VERSION 3.13)
ثم نقوم بتعيين مشروعنا واسمه وإصداره واللغات المستخدمة ، وما إلى ذلك (راجع project
).
في هذه الحالة ، نحدد لغة CXX
(والتي تعني C ++) بحيث لا يضغط CMake ولا يبحث عن مترجم لغة C (بشكل افتراضي ، يتم تضمين لغتين في CMake: C و C ++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
هنا يمكنك على الفور التحقق مما إذا كان مشروعنا مدرجًا في مشروع آخر كمشروع فرعي. هذا سوف يساعد كثيرا في المستقبل.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
نحن نقدم خيارين.
الخيار الأول - MYLIB_TESTING
- لإيقاف اختبارات الوحدة. قد يكون ذلك ضروريًا إذا كنا متأكدين من أن كل شيء يتوافق مع الاختبارات ، ونريد ، على سبيل المثال ، فقط تثبيت مشروعنا أو حزمه. أو يتم تضمين مشروعنا كمشروع فرعي - في هذه الحالة ، لا يهتم مستخدم مشروعنا بإجراء اختباراتنا. أنت لا تختبر التبعيات التي تستخدمها؟
option(MYLIB_TESTING " " ON)
بالإضافة إلى ذلك ، MYLIB_COVERAGE
خيارًا منفصلاً MYLIB_COVERAGE
لقياس تغطية الشفرة مع الاختبارات ، ولكنه سيتطلب أدوات إضافية ، لذلك ستحتاج إلى تمكينها بشكل صريح.
option(MYLIB_COVERAGE " " OFF)
بالطبع ، نحن رائعون بالإضافة إلى المبرمجين ، لذلك نحن نريد الحد الأقصى لمستوى تشخيص وقت الترجمة من المترجم. لا ماوس واحد سوف تنزلق من خلال.
add_compile_options( -Werror -Wall -Wextra -Wpedantic -Wcast-align -Wcast-qual -Wconversion -Wctor-dtor-privacy -Wenum-compare -Wfloat-equal -Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wsign-conversion -Wsign-promo )
سنقوم أيضًا بتعطيل الامتدادات للامتثال الكامل لمعايير لغة C ++. بشكل افتراضي ، يتم تضمينها في CMake.
if(NOT CMAKE_CXX_EXTENSIONS) set(CMAKE_CXX_EXTENSIONS OFF) endif()
تتكون مكتبتنا فقط من ملفات الرأس ، مما يعني أنه ليس لدينا أي استنفاد في شكل مكتبات ثابتة أو ديناميكية. من ناحية أخرى ، لاستخدام مكتبتنا في الخارج ، تحتاج إلى تثبيته ، يجب أن تكون قادرًا على العثور عليه في النظام وتوصيله بمشروعك ، وفي الوقت نفسه ، يتم ربط هذه الرؤوس نفسها بها ، وكذلك ، ربما ، بعض المعلومات الإضافية خصائص.
لهذا الغرض ، نقوم بإنشاء مكتبة واجهة.
add_library(mylib INTERFACE)
قم بربط الرؤوس بمكتبتنا الأمامية.
الاستخدام الحديث والمألوف والشباب لـ CMake يعني أن الرؤوس والممتلكات ، إلخ. تنتقل من خلال غرض واحد. وبالتالي ، يكفي أن نقول أن target_link_libraries(target PRIVATE dependency)
، وجميع الرؤوس المرتبطة بهدف dependency
ستكون متاحة للمصادر التي تنتمي إلى الهدف target
. ولا يوجد [target_]include_directories
مطلوب. سيتم توضيح ذلك أدناه عند تحليل البرنامج النصي CMake لاختبارات الوحدة .
كما أنه يجدر الانتباه إلى ما يسمى -: $<...>
.
يربط هذا الأمر الرؤوس التي نحتاجها ${CMAKE_CURRENT_SOURCE_DIR}/include
الأمامية ، وإذا كانت المكتبة متصلاً بهدف في نفس التسلسل الهرمي لـ CMake ، فسوف يتم ربط الرؤوس من الدليل ${CMAKE_CURRENT_SOURCE_DIR}/include
بها ، وإذا كانت لدينا مكتبة نظرًا لأن المكتبة مثبتة على النظام ومتصلة بمشروع آخر باستخدام find_package
، find_package
الرؤوس من دليل include
المرتبط بدليل التثبيت.
target_include_directories(mylib INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> )
اضبط معيار اللغة. بالطبع ، الأخير. في الوقت نفسه ، لا نقوم بتضمين المعيار فحسب ، بل نوزعه أيضًا على أولئك الذين سيستخدمون مكتبتنا. يتم تحقيق ذلك نظرًا لحقيقة أن الخاصية المحددة لها فئة INTERFACE
(راجع الأمر target_compile_features ).
target_compile_features(mylib INTERFACE cxx_std_17)
نخلق اسم مستعار لمكتبتنا. علاوة على ذلك ، بالنسبة للجمال ، سيكون في "مساحة اسم" خاصة. سيكون هذا مفيدًا عندما تظهر وحدات مختلفة في مكتبتنا ، ونذهب لربطها بشكل مستقل عن بعضها البعض. كما هو الحال في Boost ، على سبيل المثال .
add_library(Mylib::mylib ALIAS mylib)
تثبيت رؤوسنا في النظام. كل شيء بسيط هنا. نقول أن المجلد الذي يحتوي على جميع الرؤوس يجب أن يكون في دليل include
نسبة إلى موقع التثبيت.
install(DIRECTORY include/mylib DESTINATION include)
بعد ذلك ، نعلم نظام find_package(Mylib)
الذي نريد أن نتمكن من الاتصال به find_package(Mylib)
في مشاريع الجهات الخارجية والحصول على الهدف Mylib::mylib
.
install(TARGETS mylib EXPORT MylibConfig) install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)
يجب فهم الإملاء التالي على النحو التالي. عندما نسمي find_package(Mylib 1.2.3 REQUIRED)
في مشروع تابع لجهة خارجية ، وفي هذه الحالة فإن الإصدار الحقيقي للمكتبة المثبتة لا يتوافق مع الإصدار 1.2.3
، ستقوم CMake تلقائيًا بإنشاء خطأ. وهذا هو ، لن تحتاج إلى متابعة الإصدارات يدويا.
include(CMakePackageConfigHelpers) write_basic_package_version_file("${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) install(FILES "${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" DESTINATION share/Mylib/cmake)
إذا تم إيقاف تشغيل الاختبارات بشكل صريح باستخدام الخيار المناسب أو إذا كان مشروعنا مشروعًا فرعيًا ، أي أنه متصل بمشروع CMake آخر باستخدام add_subdirectory
، فلن نذهب إلى أبعد من ذلك في التسلسل الهرمي ، ولن يبدأ البرنامج النصي الذي يصف أوامر إنشاء الاختبارات وتشغيلها ببساطة .
if(NOT MYLIB_TESTING) message(STATUS " Mylib ") elseif(IS_SUBPROJECT) message(STATUS "Mylib ") else() add_subdirectory(test) endif()
لن يتم أيضًا إنشاء الوثائق في حالة المشروع الفرعي.
if(NOT IS_SUBPROJECT) add_subdirectory(doc) endif()
وبالمثل ، لن يحتوي المشروع الفرعي أيضًا على صناديق رمل على الإنترنت.
if(NOT IS_SUBPROJECT) add_subdirectory(online) endif()
بادئ ذي بدء ، وجدنا الحزمة مع إطار الاختبار المطلوب (استبداله مع المفضلة لديك).
find_package(doctest 2.3.3 REQUIRED)
نخلق ملفنا القابل للتنفيذ مع الاختبارات. عادةً ما أقوم فقط بإضافة الملف الذي ستكون الوظيفة main
فيه مباشرة إلى الملف القابل للتنفيذ.
add_executable(mylib-unit-tests test_main.cpp)
وتضاف الملفات التي تصف الاختبارات نفسها في وقت لاحق. لكن هذا ليس ضروريا.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)
نحن ربط التبعيات. يرجى ملاحظة أننا قمنا فقط target_include_directories
أهداف CMake التي نحتاجها بالثنائي الخاص بنا ، ولم target_include_directories
الأمر target_include_directories
. تم الزحف إلى رؤوس من إطار الاختبار ومن Mylib::mylib
، بالإضافة إلى معلمات الإنشاء (في حالتنا ، هذا هو معيار لغة C ++) مع هذه الأهداف.
target_link_libraries(mylib-unit-tests PRIVATE Mylib::mylib doctest::doctest )
أخيرًا ، قم بإنشاء هدف وهمي ، يكون "التجميع" الخاص به مكافئًا لإجراء الاختبارات ، وأضف هذا الهدف إلى التجميع الافتراضي (السمة ALL
مسؤولة عن هذا). هذا يعني أن التجميع افتراضيًا يبدأ في إطلاق الاختبارات ، أي أننا لن ننسى أبدًا تشغيلها.
add_custom_target(check ALL COMMAND mylib-unit-tests)
بعد ذلك ، يمكننا تمكين قياس تغطية الرمز ، إذا تم تحديد الخيار المقابل. لن أخوض في التفاصيل ، لأنها تتعلق بالأداة لقياس التغطية أكثر من CMake. من المهم فقط ملاحظة أنه بناءً على النتائج ، سيتم إنشاء هدف coverage
، يكون من المناسب البدء في قياس التغطية.
find_program(GCOVR_EXECUTABLE gcovr) if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE) message(STATUS " ") target_compile_options(mylib-unit-tests PRIVATE --coverage) target_link_libraries(mylib-unit-tests PRIVATE gcov) add_custom_target(coverage COMMAND ${GCOVR_EXECUTABLE} --root=${PROJECT_SOURCE_DIR}/include/ --object-directory=${CMAKE_CURRENT_BINARY_DIR} DEPENDS check ) elseif(MYLIB_COVERAGE AND NOT GCOVR_EXECUTABLE) set(MYLIB_COVERAGE OFF) message(WARNING " gcovr") endif()
وجدت Doxygen .
find_package(Doxygen)
بعد ذلك ، نتحقق مما إذا كان المستخدم قد قام بتعيين متغير اللغة. إذا كانت الإجابة بنعم ، ثم لا تلمس ، إن لم يكن ، ثم تأخذ الروسية. ثم قم بتكوين ملفات نظام Doxygen. جميع المتغيرات الضرورية ، بما في ذلك اللغة ، تصل إلى هناك أثناء عملية التكوين (انظر configure_file
).
ثم نقوم بإنشاء هدف doc
، والذي سيبدأ في إنشاء الوثائق. نظرًا لأن توليد الوثائق ليس هو الحاجة الأكبر في عملية التطوير ، فلن يتم تمكين الهدف افتراضيًا ، ويجب البدء به صراحة.
if (Doxygen_FOUND) if (NOT MYLIB_DOXYGEN_LANGUAGE) set(MYLIB_DOXYGEN_LANGUAGE Russian) endif() message(STATUS "Doxygen documentation will be generated in ${MYLIB_DOXYGEN_LANGUAGE}") configure_file(Doxyfile.in Doxyfile) add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) endif ()
سنجد هنا Python الثالث wandbox
هدفًا wandbox
ينشئ طلبًا يطابق واجهة برمجة تطبيقات خدمة Wandbox ويرسلها. ردا على ذلك ، يأتي رابط لصندوق الرمل النهائي.
find_program(PYTHON3_EXECUTABLE python3) if(PYTHON3_EXECUTABLE) set(WANDBOX_URL "https://wandbox.org/api/compile.json") add_custom_target(wandbox COMMAND ${PYTHON3_EXECUTABLE} wandbox.py mylib-example.cpp "${PROJECT_SOURCE_DIR}" include | curl -H "Content-type: application/json" -d @- ${WANDBOX_URL} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS mylib-unit-tests ) else() message(WARNING " - python 3- ") endif()
الآن النظر في كيفية استخدام كل شيء.
يتكون تجميع هذا المشروع ، مثله مثل أي مشروع آخر على نظام تجميع CMake ، من مرحلتين:
cmake -S // -B /// [ ...]
إذا لم يعمل الأمر أعلاه بسبب الإصدار القديم من CMake ، فحاول حذف - -S
:
cmake // -B /// [ ...]
المزيد عن الخيارات .
cmake --build /// [--target target]
اقرأ المزيد عن أهداف التجميع .
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [ ...]
يتضمن هدف coverage
، والذي يمكنك من خلاله بدء قياس تغطية الشفرة مع الاختبارات.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [ ...]
يوفر القدرة على إيقاف تشغيل مجموعة اختبار الوحدة وهدف check
. ونتيجة لذلك ، تم إيقاف MYLIB_COVERAGE
قياس تغطية الشفرة عن طريق الاختبارات (انظر MYLIB_COVERAGE
).
أيضًا ، يتم تعطيل الاختبار تلقائيًا إذا كان المشروع متصلاً بمشروع آخر كمشروع فرعي باستخدام add_subdirectory
.
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [ ...]
تبديل لغة الوثائق التي ينشئها هدف doc
إلى اللغة المحددة. للحصول على قائمة باللغات المتاحة ، راجع موقع ويب نظام Doxygen .
بشكل افتراضي ، يتم تمكين الروسية.
cmake --build path/to/build/directory cmake --build path/to/build/directory --target all
إذا لم يتم تحديد الهدف (أي ما يعادل الهدف all
) ، فجمع كل ما هو ممكن وكذلك استدعاء هدف check
.
cmake --build path/to/build/directory --target mylib-unit-tests
يجمع اختبارات الوحدة. تمكين افتراضيا.
cmake --build /// --target check
يعمل على جمع (يجمع ، إن لم يكن بعد) اختبارات وحدة. تمكين افتراضيا.
انظر أيضًا mylib-unit-tests
.
cmake --build /// --target coverage
يقوم بتحليل (تشغيل ، إن لم يكن حتى الآن) اختبارات وحدة لتغطية الرمز مع الاختبارات باستخدام برنامج gcovr .
سوف طلاء العادم تبدو مثل هذا:
------------------------------------------------------------------------------ GCC Code Coverage Report Directory: /path/to/cmakecpptemplate/include/ ------------------------------------------------------------------------------ File Lines Exec Cover Missing ------------------------------------------------------------------------------ mylib/myfeature.hpp 2 2 100% ------------------------------------------------------------------------------ TOTAL 2 2 100% ------------------------------------------------------------------------------
الهدف متاح فقط عند MYLIB_COVERAGE
.
انظر أيضا check
.
cmake --build /// --target doc
بدء إنشاء وثائق التعليمات البرمجية باستخدام نظام Doxygen .
cmake --build /// --target wandbox
تبدو استجابة الخدمة مثل هذا:
{ "permlink" : "QElvxuMzHgL9fqci", "status" : "0", "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci" }
للقيام بذلك ، استخدم خدمة Wandbox . لا أعرف كيف توجد خوادم مطاطية لديهم ، لكنني أعتقد أن هذه الفرصة يجب ألا يتم استغلالها.
تجميع المشروع في وضع التصحيح مع قياس التغطية
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON cmake --build /// --target coverage --parallel 16
تركيب المشروع دون التجميع الأولي والاختبار
cmake -S // -B /// -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/// cmake --build /// --target install
بناء في وضع الإصدار من قبل المترجم المحدد
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=///// cmake --build /// --parallel 4
جيل الوثائق الإنجليزية
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English cmake --build /// --target doc
CMake 3.13
في الواقع ، CMake 3.13 مطلوب فقط لتشغيل بعض أوامر وحدة التحكم الموضحة في هذه التعليمات. من وجهة نظر بناء جملة البرامج النصية CMake ، الإصدار 3.8 يكفي إذا تم استدعاء الجيل بطرق أخرى.
مكتبة اختبار Doctest
يمكن تعطيل الاختبار (راجع MYLIB_TESTING
).
Doxygen
لتبديل اللغة التي سيتم بها إنشاء الوثائق ، يتم MYLIB_DOXYGEN_LANGUAGE
خيار MYLIB_DOXYGEN_LANGUAGE
.
بيثون 3 مترجم
لتوليد رمل رمل تلقائيًا عبر الإنترنت .
مع CMake واثنين من الأدوات الجيدة ، يمكنك تقديم تحليل ثابت مع الحد الأدنى من حركات الجسم.
Cppcheck
وقد CMake المدمج في دعم لأداة تحليل ثابت Cppcheck .
للقيام بذلك ، استخدم الخيار CMAKE_CXX_CPPCHECK
:
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-I///include"
بعد ذلك ، سيتم بدء التحليل الثابت تلقائيًا في كل مرة أثناء تجميع المصادر وإعادة تجميعها. لا تحتاج إلى القيام بأي شيء إضافي.
قعقع
باستخدام أداة scan-build
الرائعة ، يمكنك أيضًا إجراء تحليل ثابت في حالتين:
scan-build cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug scan-build cmake --build ///
هنا ، على عكس الحالة مع Cppcheck ، تحتاج إلى تشغيل التجميع في كل مرة من خلال scan-build
.
يعد CMake نظامًا قويًا ومرنًا يتيح لك تنفيذ وظائف لكل ذوق ولون. وعلى الرغم من أن بناء الجملة في بعض الأحيان يترك الكثير مما هو مرغوب فيه ، فإن الشيطان لا يزال غير فظيع كما هو مرسوم. استخدام نظام بناء CMake لصالح المجتمع والصحة.
→ تحميل قالب المشروع