CMake و C ++ هما أخوان إلى الأبد

CMake و C ++ هما أخوان إلى الأبد

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

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

في هذه الملاحظة ، أريد أن أخبرك بمدى سهولة تنظيم مكتبة رأس C ++ في نظام CMake للحصول على الوظائف التالية:

  1. حَشد؛
  2. اختبارات التشغيل التلقائي
  3. قياس تغطية الكود ؛
  4. تثبيت؛
  5. التوثيق التلقائي
  6. جيل رمل على الإنترنت ؛
  7. التحليل الساكن.

من يفهم بالفعل الإيجابيات و si-make يمكنه فقط تحميل قالب المشروع وابدأ في استخدامه.


محتوى

  1. مشروع من الداخل
    1. هيكل المشروع
    2. ملف CMake الرئيسي (./CMakeLists.txt)
      1. معلومات المشروع
      2. خيارات المشروع
      3. خيارات الترجمة
      4. الهدف الرئيسي
      5. تركيب
      6. اختبارات
      7. توثيق
      8. وضع الحماية على الإنترنت
    3. البرنامج النصي للاختبارات (test / CMakeLists.txt)
      1. تجريب
      2. تغطية
    4. نص التوثيق (doc / CMakeLists.txt)
    5. البرنامج النصي لوضع الحماية عبر الإنترنت (عبر الإنترنت / CMakeLists.txt)
  2. مشروع خارج
    1. جمعية
      1. جيل
      2. جمعية
    2. خيارات
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. أهداف التجمع
      1. بشكل افتراضي
      2. اختبارات وحدة mylib
      3. التحقق
      4. تغطية
      5. الوثيقة
      6. wandbox
    4. أمثلة
  3. أدوات
  4. التحليل الساكن
  5. خاتمة

مشروع من الداخل

هيكل المشروع

.
├── 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 الرئيسي (./CMakeLists.txt)

معلومات المشروع

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

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

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 ، فسيتم ربط الرؤوس من الدليل بها ${CMAKE_CURRENT_SOURCE_DIR}/include، وإذا كانت مكتبتنا مثبتة على النظام ومتصلة بمشروع آخر باستخدام الأمر 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)

نحصل على اسم مستعار لمكتبتنا. وبالنسبة للجمال ، سيكون في "مساحة اسم" خاصة. سيكون هذا مفيدًا عندما تظهر وحدات مختلفة في مكتبتنا ، ونذهب لربطها بشكل مستقل عن بعضها البعض. كما في بوستا ، على سبيل المثال.

add_library(Mylib::mylib ALIAS mylib)

تركيب

تثبيت رؤوسنا في النظام. كل شيء بسيط هنا. نقول أن المجلد الذي يحتوي على جميع الرؤوس يجب أن يقع في الدليل include بخصوص موقع التثبيت.

install(DIRECTORY include/mylib DESTINATION include)

بعد ذلك ، نخبر نظام الإنشاء أننا نريد أن نكون قادرين على استدعاء الأمر في مشاريع الجهات الخارجية 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()

البرنامج النصي للاختبارات (test / CMakeLists.txt)

تجريب

بادئ ذي بدء ، نجد حزمة مع إطار الاختبار المطلوب (استبدلها بإطار العمل المفضل لديك).

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)

نحن نربط التبعيات. يرجى ملاحظة أننا قمنا بإرفاق أهداف CMake التي نحتاجها بالثنائي الخاص بنا فقط ، ولم نستدعي الأمر 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()

نص التوثيق (doc / CMakeLists.txt)

وجدت 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 ()

البرنامج النصي لوضع الحماية عبر الإنترنت (عبر الإنترنت / CMakeLists.txt)

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

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 build ، من مرحلتين:

جيل

cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

إذا لم يعمل الأمر أعلاه بسبب إصدار أقدم من CMake ، فحاول حذفه -S:

cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

المزيد حول الخيارات.

بناء المشروع

cmake --build путь/к/сборочной/директории [--target target]

المزيد حول أهداف التجميع.

خيارات

MYLIB_COVERAGE

cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]

يشمل الهدف coverage، والتي يمكنك من خلالها البدء في قياس تغطية الكود بالاختبارات.

MYLIB_TESTING

cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]

يوفر خيار إيقاف تشغيل بناء اختبار الوحدة والهدف check. نتيجة لذلك ، يتم إيقاف قياس تغطية الشفرة بالاختبارات (انظر MYLIB_COVERAGE).

أيضًا ، يتم تعطيل الاختبار تلقائيًا إذا كان المشروع متصلاً بمشروع آخر كمشروع فرعي باستخدام الأمر add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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.

اختبارات وحدة mylib

cmake --build path/to/build/directory --target mylib-unit-tests

يجمع اختبارات الوحدة. ممكن بشكل افتراضي.

التحقق

cmake --build путь/к/сборочной/директории --target check

يدير اختبارات الوحدة المبنية (يبني إن لم يكن بالفعل). ممكن بشكل افتراضي.

انظر أيضا mylib-unit-tests.

تغطية

cmake --build путь/к/сборочной/директории --target coverage

التحليلات الجارية (تعمل ، إن لم يكن بعد) اختبارات الوحدة لتغطية الكود عن طريق الاختبارات باستخدام البرنامج com.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.

wandbox

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

أدوات

  1. CMake 3.13

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

  2. مكتبة الاختبار دوكت

    يمكن تعطيل الاختبار (انظر опцию MYLIB_TESTING).

  3. Doxygen

    لتبديل اللغة التي سيتم إنشاء الوثائق بها ، هناك خيار MYLIB_DOXYGEN_LANGUAGE.

  4. مترجم PL بيثون 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 build لصالح المجتمع والصحة.

تنزيل نموذج المشروع

المصدر: www.habr.com

إضافة تعليق