= CMake: управление проектом :title-separator: {sp}| :category: Программирование :tags: программирование, cmake, qt :toc: include::{l10ndir}/{lang}.adoc[] == Полезные ссылки * https://github.com/onqtam/awesome-cmake[Каталог ссылок] * https://cgold.readthedocs.io/en/latest/index.html[CGold: The Hitchhiker’s Guide to the CMake] == Структура каталогов проекта Файлы проекта и результаты компиляции размещаются в каталогах: .... └── project ├── _build │ ├── debug │ │ ├── bin │ │ ├── etc │ │ ├── files │ │ │ ├── data │ │ │ ├── lib │ │ │ └── log │ │ ├── include │ │ └── lib │ └── release ├── .git ├── .gitlab-ci ├── cmake │ ├── cmlib │ ├── doc │ ├── etc │ │ └── uncrustify │ ├── find │ └── generators ├── doc ├── files │ ├── data │ ├── etc │ ├── lib │ └── log ├── l10n ├── src │ ├── app │ └── lib ├── thirdparty └── tools .... Назначение каталогов приведено в таблице. .Назначение каталогов [cols="2,4",options="header",] |=== |Каталог | Назначение |`_build` | Результаты компиляции |`_build/debug` | Результаты компиляции в режиме отладки |`_build/debug/bin` | Исполняемые файлы |`_build/debug/etc` | Символическая ссылка на каталог `cmex/files/etc` |`_build/debug/files/data` | Символическая ссылка на каталог `cmex/files/data` |`_build/debug/files/lib` | Символическая ссылка на каталог `cmex/files/lib` |`_build/debug/files/log` | Символическая ссылка на каталог `cmex/files/log` |`_build/debug/include` | Заголовочные файлы копируемые и генерируемые во время сборки |`_build/debug/lib` | Статические и динамические библиотеки |`_build/release` | Результаты компиляции в режиме выпуска (иерархия аналогична `debug`) |`.git` | Системные файлы репозитория git |`.gitlab.ci` | Шаблон правил для автоматической сборки на сервере Gitlab |`cmake` | Файлы с дополнительными функциями для CMake |`cmake/cmlib` | Библиотека функций для CMake |`cmake/doc` | Правила для автоматической генерации документации |`cmake/etc` | Файлы настроек, используемые в CMake |`cmake/etc/uncrustify` | Файл настройки для программы автоматического форматирования исходных текстов |`cmake/find` | Модули CMake для поиска внешних программ и библиотек |`cmake/generators` | Генераторы проектов |`doc` | Документация для проекта |`files` | Каталог для дополнительных файлов |`files/etc` | Каталог для файлов настроек проекта |`files/data` | Каталог для неизменяемых файлов |`files/lib` | Каталог для изменяемых файлов |`files/log` | Каталог для журналов |`l10n` | Файлы переводов |`src` | Исходные тексты |`src/app` | Исходные тексты программы |`src/lib` | Исходные тексты библиотеки |`thirdparty` | Исходные тексты дополнительных и сторонних проектов |`tools` | Дополнительные утилиты |=== Каталог `_build` создаётся, чтобы избежать попадания создаваемых во время сборки файлов в иерархию основного проекта. Запись результатов сборки проекта внутрь иерархии каталогов с исходными текстами приводит к засорению формируемыми на этапе сборки файлами, которые затрудняют разработку, поиск в оригинальных файлах и мешают ориентироваться в проекте. При работе с несколькими типами сборки, например, отладка и выпуск, появляется необходимость корректного полного удаления результатов предыдущего тип сборки. [[base-project]] == Базовый проект Проект, в котором выполнены приведённые в данном разделе действия, можно посмотреть https://git.246060.ru/f1x1t/cmlib-example-base[здесь] или сделать его копию командой: [source,sh] ---- git clone --recursive https://git.246060.ru/f1x1t/cmlib-example-base ---- === Инициализация подмодулей Для начала нужно создать каталог для проекта, перейти в него и инициализировать репозиторий git: [source,sh] ---- mkdir cmlib-example-base cd cmlib-example-base git init ---- Для подключения основных подмодулей, содержащих дополнительные функции для работы с проектом, и фиксации произведённого изменения нужно выполнить: [source,sh] ---- git submodule add https://git.246060.ru/f1x1t/cmlib.git cmake/cmlib git submodule add https://git.246060.ru/f1x1t/cmake-find.git cmake/find git submodule add https://git.246060.ru/f1x1t/cmake-generators.git cmake/generators git submodule add https://git.246060.ru/f1x1t/cmake-doc.git cmake/doc git submodule add https://git.246060.ru/f1x1t/uncrustify-config.git cmake/etc/uncrustify git commit -a -m "Начало проекта" ---- Отправить изменения в проекте на сервер и сделать ветку `master` основной (можно пропустить): [source,sh] ---- git remote add origin АДРЕС_РЕПОЗИТОРИЯ_НА_СЕРВЕРЕ git push -u origin master ---- Загрузить шаблоны для автоматической сборки проекта в разных вариантах программных окружений и зафиксировать изменения: [source,sh] ---- mkdir .gitlab-ci wget -O .gitlab-ci/scheduled.yml https://git.246060.ru/f1x1t/gitlab-ci/raw/branch/master/.gitlab-ci/scheduled.yml wget -O .gitlab-ci.yml https://git.246060.ru/f1x1t/gitlab-ci/raw/branch/master/.gitlab-ci.yml git add .gitlab-ci.yml .gitlab-ci/scheduled.yml git commit -m "Настройка автосборки" ---- Загрузить файл настройки для анализатора Clang-Tidy: [source,sh] ---- wget https://git.246060.ru/f1x1t/clang-tidy-config/raw/branch/master/.clang-tidy git add .clang-tidy git commit -m "Настройка Clang-Tidy" ---- Создать стандартные файлы и каталоги: [source,sh] ---- mkdir -p doc/breathe touch doc/breathe/index.md.in mkdir -p files/etc touch files/etc/.keep-directory mkdir -p files/data touch files/data/.keep-directory mkdir -p files/lib touch files/lib/.keep-directory mkdir -p files/log touch files/log/.keep-directory git add doc files git commit -m "Стандартные файлы и каталоги" ---- Создать файл `.gitignore` для исключения каталогов и файлов из-под контроля git: [source,sh] ---- wget https://git.246060.ru/f1x1t/cmlib-gitignore/raw/branch/master/.gitignore git add .gitignore git commit -m "Шаблон для игнорирования каталогов и файлов" ---- === Базовые инструкции в CMake В корневом каталоге проекта нужно создать файл `CMakeLists.txt`: [source,cmake] ---- # Минимальная версия CMake cmake_minimum_required(VERSION 3.3) # Предпочтительно следовать стандартам принятым в указанном диапазоне версий cmake_policy(VERSION 3.0.2..3.7) # Название и версия проекта и используемые языки программирования project(cmlib-example-base VERSION 0.2.0 LANGUAGES C CXX) ---- Значение версии следует формировать согласно правилам https://semver.org/lang/ru/[семантического версионирования]. Для подключения функций для CMake из библиотеки CMLib, нужно добавить в файл `CMakeLists.txt` строки: [source,cmake] ---- # В каталоге cmake/cmlib находятся файлы с библиотечными функциями if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/cmake/cmlib) list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/cmlib) else() message(FATAL_ERROR "CMake library directory does not exist") endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/find) include(CMLibCommon) ---- [[variables-cmake]] В каталоге `cmake/etc` требуется создать файл `Variables.cmake`, в котором должны быть определены переменные, используемые библиотекой CMLib для архивирования исходных текстов, автоматического создания пакетов, генерации документации: [source,cmake] ---- set(ORGANIZATION_NAME "org") set(AUTHOR_NAME "John Doe") set(DOXYGEN_PROJECT_TITLE "Пример проекта (начало)") set(DOXYGEN_GENERATE_LATEX YES) set(DOXYGEN_GENERATE_HTML YES) set(CPACK_GENERATOR "TXZ;DEB") set(CPACK_PACKAGE_CONTACT "John Doe ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "CMake project example") set(CPACK_DEBIAN_PACKAGE_SECTION "misc") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_SOURCE_IGNORE_FILES "${CMAKE_BINARY_DIR}" "/\\\\.git/" "/\\\\.gitlab-ci/" "^${CMAKE_SOURCE_DIR}/.?build.?/" "^${CMAKE_SOURCE_DIR}/.?output.?/" "^${CMAKE_SOURCE_DIR}/files/lib" "^${CMAKE_SOURCE_DIR}/files/log" "\\\\.clang-tidy$" "\\\\.cmake-format$" "\\\\.gitignore$" "\\\\.gitattributes$" "\\\\.gitmodules$" "\\\\.gitlab-ci.yml" "CMakeLists.txt.user.*" "~$" "\\\\.swp$") ---- <<< Произведённые изменения можно зафиксировать: [source,sh] ---- git add cmake/etc/Variables.cmake CMakeLists.txt git commit -m "Подключение библиотеки CMLib" ---- Чтобы проверить корректность подключения CMLib, можно выполнить команду: [source,sh] ---- (mkdir -p _build && cd _build && cmake .. && make && echo OK) ---- Если последней строкой вывода будет `OK`, то настройка завершена верно. == Поиск системных библиотек Поиск программ, библиотек и заголовочных файлов, установленных в системе, можно выполнять с помощью программы https://en.wikipedia.org/wiki/Pkg-config[`pkg-config`] или функции CMake `find_package`. В любом случае для указания того, что наличие искомого объекта обязательно для сборки, используется параметр `REQUIRED`. === Поиск с помощью программы `pkg-config` Программа `pkg-config` хранит базу данных параметров (обычно в каталогах `/usr/share/pkgconfig`, `/usr/lib/pkgconfig` и `/usr/lib/x86_64-linux-gnu/pkgconfig`), содержащую флаги компиляции для поиска заголовочных файлов и компоновки библиотек, установленных в систему. Для использования в CMake сначала необходимо выполнить проверку наличия программы `pkg-config` в системе и подключить определённую в модуле `PkgConfig` функцию `pkg_check_modules`. Например, для поиска библиотек `gsl`, `fftw3` и `udev` можно написать в файле `CMakeLists.txt`: [source,cmake] ---- # Поиск библиотек с помощью pkgconfig find_package(PkgConfig REQUIRED) pkg_check_modules(GSL REQUIRED gsl) pkg_check_modules(FFTW3 REQUIRED fftw3) pkg_check_modules(UDEV udev) ---- === Поиск с помощью функции `find_package` Если системная библиотека поставляется без файла описания для `pkg-config` или необходимо произвести более сложный поиск, например, включающий поиск исполняемого файла, то может быть написан специальный модуль для `CMake`, который вызывается функцией `find_package`. Примеры вызова функции: [source,cmake] ---- # Поиск с помощью функции find_package find_package(LibXml2) find_package(CURL REQUIRED) ---- == Автоматически генерируемый заголовочный файл На этапе конфигурирования проекта можно сгенерировать файл, в который будут записаны собранные значения параметров. В библиотеке CMLib присутствует функция `cmlib_config_hpp_generate()`, создающая файл `${CMAKE_BINARY_DIR}/include/cmlib_private_config.hpp`, в который записывается информация о имени и версии проекта, дате и типе сборки. [source,cmake] ---- # Автоматически генерируемый заголовочный файл cmlib_config_hpp_generate() ---- == Удаление установленных файлов В библиотеку CMLib добавлена цель `uninstall`, позволяющая удалить файлы, которые могут быть установлены в результате выполнения цели `install`: [source,sh] ---- cd _build/debug make install make uninstall ---- == Архивирование проекта и создание пакетов Стандартный модуль `CPack` предназначен для архивирования исходных текстов проекта и создания пакетов для установки в целевую систему. Необходимые переменные устанавливаются в файле `cmake/etc/Variables.cmake` <>. Устанавливаемые файлы делятся на две группы `MAIN` и `DEV` с помощью параметра `COMPONENT` функции `install`. В группу `MAIN` необходимо помещать файлы для установки на целевую систему (исполняемые файлы, файлы настроек, файлы данных, разделяемые библиотеки), а в группу `DEV` --- для установки на систему для разработки (заголовочные файлы, статические библиотеки). По умолчанию цель для упаковки исходных текстов называется `package_source`. Бинарные пакеты создаются программой `cpack`. Пример: [source,sh] ---- cd _build/debug make make package_source cpack ---- == Примеры библиотек и приложений [[base-lib-project]] === Базовая библиотека Проект с базовой библиотекой реализован на основе <>. Исходные тексты содержат комментарии, объясняющие назначение используемых функций. Проект можно посмотреть https://git.246060.ru/f1x1t/cmlib-example-library[здесь] или сделать его копию командой: [source,sh] ---- git clone --recursive https://git.246060.ru/f1x1t/cmlib-example-library ---- В файл `CMakeLists.txt`, находящийся в корневом каталоге проекта, нужно добавить: [source,cmake] ---- # Поиск библиотеки Boost set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_MULTITHREADED OFF) set(Boost_USE_STATIC_RUNTIME ON) find_package(Boost 1.55.0 REQUIRED COMPONENTS headers) # Автоматически генерируемый заголовочный файл cmlib_config_hpp_generate() # Каталог с исходными текстами библиотеки add_subdirectory(src/cmlib-example) # Документация add_subdirectory(cmake/doc) # Создание вспомогательных символических ссылок add_dependencies(cmlib-example create_auxilary_symlinks) ---- <<< В подкаталоге `src/cmlib-example` нужно создать файл `CMakeLists.txt`: [source,cmake] ---- # Название основной цели и имя библиотеки в текущем каталоге set(TRGT cmlib-example) # Список файлов исходных текстов set(TRGT_cpp ${CMAKE_CURRENT_SOURCE_DIR}/init.cpp) # Список заголовочных файлов (используется для установки) set(TRGT_hpp ${CMAKE_CURRENT_SOURCE_DIR}/init.hpp) # Функция для создания цели, результатом которой будет сборка библиотеки add_common_library(${TRGT} SOURCES ${TRGT_cpp}) common_target_properties(${TRGT}) # Добавление к пути поиска заголовочных файлов target_include_directories(${TRGT} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) # Цель, используемая только для установки # заголовочных файлов без компиляции проекта add_custom_target(${TRGT}-install-headers COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=DEV -P "${CMAKE_BINARY_DIR}/cmake_install.cmake") # Установка статической библиотеки install(TARGETS ${TRGT}_static COMPONENT DEV ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) # Установка динамической библиотеки if(BUILD_SHARED_LIBS) install(TARGETS ${TRGT}_shared COMPONENT DEV LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() # Установка заголовочных файлов install(FILES ${TRGT_hpp} COMPONENT DEV DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${TRGT}) # Установка файла для pkg-config install(FILES ${CMAKE_BINARY_DIR}/${TRGT}.pc COMPONENT DEV DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) ---- <<< файл `init.hpp`: [source,cpp] ---- #ifndef CMLIB_EXAMPLE_HPP_ #define CMLIB_EXAMPLE_HPP_ #include int32_t cmlib_example_init(int32_t i); #endif // CMLIB_EXAMPLE_HPP_ ---- и файл `init.cpp`: [source,cpp] ---- #include "init.hpp" #include int32_t cmlib_example_init(int32_t i = 0) { int32_t s = 0; for ( auto r : boost::counting_range( 1, i ) ) { s += r; } return s; } ---- [[base-app-project]] === Базовое приложение Проект с базовым приложением реализован на основе <>. Исходные тексты содержат комментарии, объясняющие назначение используемых функций. Проект можно посмотреть https://git.246060.ru/f1x1t/cmlib-example-app[здесь] или сделать его копию командой: [source,sh] ---- git clone --recursive https://git.246060.ru/f1x1t/cmlib-example-app ---- <<< В файл `CMakeLists.txt`, находящийся в корневом каталоге проекта, нужно добавить: [source,cmake] ---- # Boost set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_MULTITHREADED OFF) set(Boost_USE_STATIC_RUNTIME ON) find_package(Boost 1.55.0 REQUIRED COMPONENTS headers) # Автоматически генерируемый заголовочный файл cmlib_config_hpp_generate() # Приложение add_subdirectory(src/cmlib-example) # Документация add_subdirectory(cmake/doc) # Создание вспомогательных символических ссылок add_dependencies(cmlib-example create_auxilary_symlinks) ---- В подкаталоге `src/cmlib-example` нужно создать файл `CMakeLists.txt`: [source,cmake] ---- # Название основной цели и имя библиотеки в текущем каталоге set(TRGT cmlib-example) # Список файлов исходных текстов set(TRGT_cpp ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) # Функция для создания цели, результатом которой будет сборка приложения add_executable(${TRGT} ${TRGT_cpp}) common_target_properties(${TRGT}) # Добавление к пути поиска заголовочных файлов target_include_directories(${TRGT} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) # Имя целевого каталога и выходного файла для цели set_target_properties(${TRGT} PROPERTIES OUTPUT_NAME ${TRGT} RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR} ) # Правила для установки install(TARGETS ${TRGT} COMPONENT MAIN RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) ---- <<< и файл `main.cpp`: [source,cpp] ---- #include "compiler_features.hpp" #include "cmlib_private_config.hpp" #include #include int32_t nsum(int32_t i = 0) { int32_t s = 0; for ( auto r : boost::counting_range( 1, i ) ) { s += r; } return s; } int main(int argc, char* argv[]) { // Значение из compiler_features.hpp std::cout << CMLIB_EXAMPLE_APP_COMPILER_VERSION_MAJOR << std::endl; // Значение из cmlib_private_config.hpp std::cout << CMLIB_BUILD_TYPE << std::endl; // Значение из cmlib_private_config.hpp std::cout << CMLIB_BUILD_DATE << std::endl; auto s = nsum( argc ); std::cout << s << std::endl; return ( s ); } ---- === Подключение внешнего проекта Проект, использующий для сборки внешний проект, реализован на основе проектов <> и <>. Исходные тексты содержат комментарии, объясняющие назначение используемых функций. Проект можно посмотреть https://git.246060.ru/f1x1t/cmlib-example-app-ext[здесь] или сделать его копию командой: [source,sh] ---- git clone --recursive https://git.246060.ru/f1x1t/cmlib-example-app-ext ---- Для подключения проекта базовой библиотеки нужно выполнить: [source,sh] ---- git submodule add https://git.246060.ru/f1x1t/cmlib-example-library thirdparty/cmlib-example-library git submodule update --init --recursive ---- В файл `CMakeLists.txt`, находящийся в корневом каталоге проекта, перед функциями `add_subdirectories` нужно добавить: [source,cmake] ---- # Подключение внешних проектов include(ExternalProject) ExternalProject_Add(ext-lib EXCLUDE_FROM_ALL TRUE SOURCE_DIR ${CMAKE_SOURCE_DIR}/thirdparty/cmlib-example-library INSTALL_DIR ${CMAKE_BINARY_DIR} DOWNLOAD_COMMAND "" BUILD_BYPRODUCTS /lib/libcmlib-example.a CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_BUILD_TYPE=Release ) ---- В результате будет создана цель `ext-lib`, являющаяся результатом сборки подключённой библиотеки. Все функции `ExternalProject_Add` необходимо располагать перед функциям `add_subdirectories`, чтобы в указанных подкаталогах можно было использовать добавленные цели для определения зависимостей. В файле `src/cmlib-example/CMakeLists.txt` после создания цели `${TRGT}` нужно подключить внешний проект `ext-lib`: [source,cmake] ---- # Зависимость от библиотеки из внешнего проекта проекта add_dependencies(${TRGT} ext-lib) # Добавление каталога, в который устанавливаются заголовочные файлы # от внешнего проекта, к списку путей для поиска target_include_directories(${TRGT} PUBLIC $) # Компоновка с библиотекой из внешнего проекта target_link_libraries(${TRGT} ${CMAKE_BINARY_DIR}/lib/libcmlib-example.a) ---- <<< Для проверки работоспособности в файле `src/cmlib-example/main.cpp` нужно вызвать функцию `cmlib_example_init` из библиотеки, предоставляемой внешним проектом. Например, можно заменить его содержимое на: [source,cpp] ---- #include #include int main(int argc, char* argv[]) { auto s = cmlib_example_init( argc ); std::cout << s << std::endl; return ( s ); } ---- === Qt5 В данном разделе будут приведены примеры создания консольного и графического приложений, а также подключения локализации, вызовы препроцессоров `moc`, `uic` и `rcc`. [[qt5-con]] ==== Консольное приложение и локализация Пример консольного приложения на Qt5 с поддержкой локализации основан на проекте <> и библиотеке https://git.246060.ru/f1x1t/myxlib[MyXLib]. Исходные тексты содержат комментарии, объясняющие назначение используемых функций. Проект можно посмотреть https://git.246060.ru/f1x1t/cmlib-example-app-qt5-con[здесь] или сделать его копию командой: [source,sh] ---- git clone --recursive https://git.246060.ru/f1x1t/cmlib-example-app-qt5-con ---- Для подключения проекта библиотеки https://git.246060.ru/f1x1t/myxlib[MyXLib] нужно выполнить: [source,sh] ---- git submodule add https://git.246060.ru/f1x1t/myxlib thirdparty/myxlib git submodule update --init --recursive ---- В файлах `CMakeLists.txt` и `src/cmlib-example/CMakeLists.txt` нужно заменить все строки `cmlib-example` на `cmlib-example-app-qt5-con`. <<< В файл `CMakeLists.txt`, находящийся в корневом каталоге проекта, перед функциями `add_subdirectories` нужно добавить: [source,cmake] ---- # Подключение внешних проектов include(ExternalProject) ExternalProject_Add( myxlib SOURCE_DIR ${CMAKE_SOURCE_DIR}/thirdparty/myxlib INSTALL_DIR ${CMAKE_BINARY_DIR} DOWNLOAD_COMMAND "" CONFIGURE_COMMAND ${CMAKE_COMMAND} -"G${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} BUILD_COMMAND true) ---- В результате будет создана цель `myxlib`, являющаяся результатом сборки подключённой библиотеки. Все функции `ExternalProject_Add` необходимо располагать перед функциям `add_subdirectories`, чтобы в указанных подкаталогах можно было использовать добавленные цели для определения зависимостей. В файле `src/cmlib-example/CMakeLists.txt` после создания цели `${TRGT}` нужно подключить внешний проект `myxlib`: [source,cmake] ---- # Зависимость от библиотеки из внешнего проекта проекта add_dependencies(${TRGT} myxlib) # Добавление каталога, в который устанавливаются заголовочные файлы # от внешнего проекта, к списку путей для поиска target_include_directories(${TRGT} PUBLIC $) # Компоновка с библиотеками из внешнего проекта target_link_libraries(${TRGT} myx-qt myx-filesystem myx-base) ---- Для поиска необходимых компонентов Qt5 нужно в файле `CMakeLists.txt`, находящемся в корневом каталоге проекта, перед вызовом функции `cmlib_config_hpp_generate()` добавить строку: [source,cmake] ---- # Используемые компоненты Qt5 find_package(Qt5 COMPONENTS Core REQUIRED) ---- <<< В файл `src/cmlib-example/CMakeLists.txt` перед вызовом функции `add_executable` добавить строки: [source,cmake] ---- # Правила для создания файла ресурсов с вложенными файлами переводов qt5_translation( TRGT_qrc OUTPUT_DIR ${CMAKE_SOURCE_DIR}/l10n BASE_NAME ${TRGT} SOURCES ${TRGT_cpp} LANGUAGES ru_RU) # Путь поиска библиотек созданных при компиляции проекта, # включая библиотеки из подключённых внешних проектов, например MyXLib # Функция link_directories обязательно должна находиться перед # функцией add_executable, иначе компоновка не может быть выполнена link_directories(${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) ---- В вызове `add_executable` подключить использование файла ресурсов с переводами: [source,cmake] ---- add_executable(${TRGT} ${TRGT_cpp} ${TRGT_qrc}) ---- После чего добавить подключение Qt5 и MyXLib: [source,cmake] ---- # Qt5: подключение заголовочных файлов target_include_directories(${TRGT} SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS}) # Qt5: подключение библиотек target_link_libraries(${TRGT} Qt5::Core) # Добавление к пути поиска заголовочных файлов target_include_directories(${TRGT} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) # Зависимость от библиотеки из внешнего проекта проекта add_dependencies(${TRGT} myxlib) # Добавление каталога, в который устанавливаются заголовочные файлы # от внешнего проекта, к списку путей для поиска target_include_directories(${TRGT} PUBLIC $) # Компоновка с библиотеками из внешнего проекта target_link_libraries(${TRGT} myx-qt myx-filesystem myx-base) ---- <<< Для проверки работоспособности подключения Qt5 файл `src/cmlib-example/main.cpp` нужно заменить на: [source,cpp] ---- #include "cmlib_private_config.hpp" #include #include #include namespace MQ = myx::qt; int main( int argc, char** argv ) { QCoreApplication app( argc, argv ); MQ::QTranslatorsList tl; qDebug() << QObject::tr( "No" ); MQ::append_translators( tl, QStringLiteral( CMLIB_PROJECT_NAME ) ); qDebug() << QObject::tr( "Yes" ); return( 0 ); } ---- Для сбора списка строк из файлов исходных кодов и описаний интерфейса, подлежащих переводу, создаётся цель `l10n`. В результате выполнения в каталоге сборки команды `make l10n` в каталоге `l10n`, находящемся в корне проекта, появится файл `cmlib-example-app-qt5-con_ru_RU.ts`, в котором нужно отредактировать переводы с помощью программы `linguist`. После сохранения файла переводов проект нужно пересобрать, файл переводов в скомпилированном виде будет встроен в исполняемый файл `cmlib-example-app-qt5-con`, а доступ к нему будет осуществляться с помощью кода: [source,cpp] ---- MQ::QTranslatorsList tl; MQ::append_translators( tl, QStringLiteral( CMLIB_PROJECT_NAME ) ); ---- [[qt5-gui]] ==== Графическое приложение, файлы описания ресурсов и интерфейсов Пример приложения на Qt5 с использованием графического интерфейса основан на проекте <>. Исходные тексты содержат комментарии, объясняющие назначение используемых функций. Проект можно посмотреть https://git.246060.ru/f1x1t/cmlib-example-app-qt5-gui[здесь] или сделать его копию командой: [source,sh] ---- git clone --recursive https://git.246060.ru/f1x1t/cmlib-example-app-qt5-gui ---- В каталоге `files/data` создать файл описания включаемых ресурсов `icon.qrc`: [source,xml] ---- icon.png ---- и загрузить файл иконки: [source,sh] ---- wget https://git.246060.ru/f1x1t/cmlib-example-app-qt5-gui/raw/branch/master/files/data/icon.png ---- Для графического приложения нужно создать файл описания интерфейса `src/cmlib-example/test_window.ui`: [source,xml] ---- TestWindow 00413253 Test Window 170308026 Press me ---- <<< заголовочный файл `src/cmlib-example/test_window.hpp`: [source,cpp] ---- #ifndef TEST_WINDOW_HPP_ #define TEST_WINDOW_HPP_ #pragma once #include "ui_test_window.h" #include class TestWindow : public QMainWindow, private Ui::TestWindow { Q_OBJECT public: TestWindow(QMainWindow *parent = nullptr); virtual ~TestWindow(); }; #endif /* TEST_WINDOW_HPP_ */ ---- и файл с реализацией конструктора, в котором проводится инициализация графических элементов, `src/cmlib-example/test_window.cpp`: [source,cpp] ---- #include "test_window.hpp" TestWindow::TestWindow(QMainWindow* parent) : QMainWindow(parent), Ui::TestWindow() { setupUi(this); } TestWindow::~TestWindow() = default; ---- <<< Для отображения графического окна нужно заменить файл `src/cmlib-examples/main.cpp` на: [source,cpp] ---- #include "cmlib_private_config.hpp" #include "test_window.hpp" #include #include #include #include namespace MQ = myx::qt; int main( int argc, char** argv ) { QApplication app( argc, argv ); qDebug() << QObject::tr( "No" ); // Подключение переводов MQ::QTranslatorsList tl; MQ::append_translators( tl, QStringLiteral( CMLIB_PROJECT_NAME ) ); qDebug() << QObject::tr( "Yes" ); // Установка иконки для программы QApplication::setWindowIcon( QIcon( ":/icon/icon.png" ) ); // Создание и отображение главного окна auto* w = new TestWindow(); w->show(); return( QApplication::exec() ); } ---- В файлах `CMakeLists.txt` и `src/cmlib-example/CMakeLists.txt` нужно заменить все строки `cmlib-example-app-qt5-con` на `cmlib-example-app-qt5-gui`. Для поиска необходимых компонентов Qt5 нужно в файле `CMakeLists.txt`, находящемся в корневом каталоге проекта, добавить поиск компонентов `Gui` и `Widgets`: [source,cmake] ---- # Используемые компоненты Qt5 find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED) ---- В файле `src/cmake-example/CMakeLists.txt` добавить новые файлы к списку файлов, используемых для компиляции: [source,cmake] ---- ### # Списки файлов проекта ### # Исходные коды set(TRGT_cpp ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_window.cpp) # Заголовочные файлы, для которых необходима обработка препроцессором moc # (содержат класс, унаследованный от QObject, использующий сигналы и/или слоты) set(TRGT_moc_hpp ${CMAKE_CURRENT_SOURCE_DIR}/test_window.hpp) # Другие заголовочные файлы set(TRGT_hpp) # Файлы с описанием графического интерфейса для Qt set(TRGT_ui ${CMAKE_CURRENT_SOURCE_DIR}/test_window.ui) # Файлы описания ресурсов, включаемых в исполняемый файл set(TRGT_qrc ${CMAKE_SOURCE_DIR}/files/data/icon.qrc) ### # Конец списков файлов ### ---- Для обеспечения работы препроцессоров Qt необходимо создать правила преобразования файлов: [source,cmake] ---- # Правило для автоматической генерации препроцессором uic qt5_wrap_ui(TRGT_ui_h ${TRGT_ui}) # Правило для автоматической генерации препроцессором moc qt5_wrap_cpp(TRGT_moc_cpp ${TRGT_moc_hpp}) # Правила для создания файла ресурсов с вложенными файлами переводов qt5_translation( TRGT_qrc_cpp OUTPUT_DIR ${CMAKE_SOURCE_DIR}/l10n BASE_NAME ${TRGT} SOURCES ${TRGT_cpp} ${TRGT_ui} LANGUAGES ru_RU) # Правило для автоматической генерации препроцессором qrc # (обязательно после вызова функции qt5_translation, если она есть, # так как она добавляет свои файлы к списку ресурсов) qt5_add_resources(TRTG_qrc_cpp ${TRGT_qrc}) ---- Цель для создания исполняемого файла нужно изменить таким образом, чтобы она зависела от файлов с исходными кодами и файлов, генерируемых препроцессорами: [source,cmake] ---- add_executable(${TRGT} ${TRGT_ui_h} ${TRGT_moc_cpp} ${TRGT_qrc_cpp} ${TRGT_cpp}) ---- Подключение заголовочных файлов и библиотек Qt должно выглядеть так: [source,cmake] ---- # Qt5: подключение заголовочных файлов target_include_directories(${TRGT} SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS}) target_include_directories(${TRGT} SYSTEM PUBLIC ${Qt5Gui_INCLUDE_DIRS}) target_include_directories(${TRGT} SYSTEM PUBLIC ${Qt5Widgets_INCLUDE_DIRS}) # Qt5: подключение библиотек target_link_libraries(${TRGT} Qt5::Core Qt5::Gui Qt5::Widgets) ---- В результате выполнения в каталоге сборки команды `make l10n` в каталоге `l10n`, находящемся в корне проекта, появится файл `cmlib-example-app-qt5-gui_ru_RU.ts`, в котором нужно отредактировать переводы с помощью программы `linguist`. После сохранения файла переводов проект нужно пересобрать, файл переводов в скомпилированном виде будет встроен в исполняемый файл. == Дополнительные возможности Библиотека CMLib содержит шаблонные функции для использования в программных проектах. Пример проекта с примерами использования функций основан на проекте <>. Исходные тексты содержат комментарии, объясняющие назначение используемых функций. Проект можно посмотреть https://git.246060.ru/f1x1t/cmlib-example-app-features[здесь] или сделать его копию командой: [source,sh] ---- git clone --recursive https://git.246060.ru/f1x1t/cmlib-example-app-features ---- === Форматирование исходных текстов Функция `add_format_sources` генерирует цель для форматирования файлов проекта в едином стандарте, для её использования требуются установленные программы `dos2unix` и `uncrustify`. Утилита `dos2unix` приводит переводы строк в файлах к стандарту, принятому в Unix. Утилита `uncrustify` форматирует файлы с исходными кодами на языке C{plus}{plus} в соответствии с правилами, перечисленными в файле `cmake/etc/uncrustify/default.cfg`. IMPORTANT: Настройка правил форматирования помогает другим разработчикам придерживаться вашего стиля программирования и отправлять изменения в ваш проект в формате, который удобен вам. Проявите заботу о своих коллегах и своём проекте! Пример использования: [source,cmake] ---- # Создание цели format-sources для автоматического форматирования кода add_format_sources(${TRGT} ${TRGT_sources} ${TRGT_headers}) ---- === Статический анализ исходных кодов Для работы с программами на языке C{plus}{plus} используются утилиты, выполняющие статический анализ кода и генерирующие отчёты, помогающие программисту находить и устранять ошибки. Эти программы применяют методы, позволяющие в синтаксически корректном коде находить недостатки или ошибки, которые пропускает компилятор, ценой продолжительного анализа исходных текстов. Библиотека CMLib поддерживает анализаторы https://github.com/KDE/clazy[clazy], https://clang.llvm.org/extra/clang-tidy[Clang Tidy], https://clang-analyzer.llvm.org[Clang Static Analyzer] и https://www.viva64.com/ru/pvs-studio[PVS-Studio]. ==== clazy Функция `add_clazy_check` создаёт цели, которые используются для проверки исходных текстов анализатором `clang`. Пример использования: [source,cmake] ---- # Создание цели clazy-check для проверки утилитой clazy add_clazy_check(${TRGT} ${TRGT_cpp} ${TRGT_hpp} ${TRGT_moc_hpp}) ---- ==== Clang Tidy Функция `add_clang_tidy_check` создаёт цели, которые используются для проверки исходных текстов анализатором `clang-tidy`. Пример использования: [source,cmake] ---- # Создание цели clang-tidy-check для проверки утилитой clang-tidy add_clang_tidy_check(${TRGT} ${TRGT_cpp} ${TRGT_hpp} ${TRGT_moc_hpp}) ---- ==== Clang Static Analyzer Функция `add_clang_analyze_check` создаёт цели, которые используются для проверки исходных текстов анализатором `clang-analyze`. Пример использования: [source,cmake] ---- # Создание цели clang-analyze-check для проверки утилитой clang-analyze add_clang_analyze_check(${TRGT} ${TRGT_cpp} ${TRGT_hpp} ${TRGT_moc_hpp}) ---- ==== PVS-Studio Функция `add_pvs_check` создаёт цели, которые используются для проверки исходных текстов анализатором `pvs-studio-analyzer`. Пример использования: [source,cmake] ---- # Создание цели pvs-check для проверки утилитой pvs-studio-analyzer add_pvs_check(${TRGT}) ---- === Динамический анализ программы Динамический анализ программы позволяет ценой значительного замедления скорости работы получить дополнительную информацию о ходе её выполнения. Современные компиляторы делают вставку инструкций в определённые точки программы, во время работы программы в них собирается необходимая информация, а по её завершению предоставляется отчёт. Основная информация о работе таких анализаторов находится https://github.com/google/sanitizers/wiki[здесь]. Для обеспечения возможности подключения динамического анализа к проекту нужно выполнить функцию (обязательно после подключения всех библиотек): [source,cmake] ---- # Подключение настроек для динамического анализа программы add_sanitizers(${TRGT}) ---- Подключение анализатора осуществляется включением опций при запуске CMake для генерации сборочных файлов. Некоторые из опций между собой несовместимы, в случае попытки совместного использования будет выведено сообщение об ошибке. .Назначение опций для динамического анализа [cols="2m,7",options="header"] |=== | Опция | Назначение | SANITIZE_ADDRESS | Определение ошибок при работе с памятью: использование после освобождения, использование за пределами области видимости, переполнения буферов в стеке, на куче, в общей памяти, утечки памяти, нарушение порядка инициализации | SANITIZE_CFI | Определение нарушений путей исполнения инструкций программы | SANITIZE_LEAK | Определение утечек памяти | SANITIZE_LINK_STATIC | Статическая компоновка анализатора с программой | SANITIZE_MEMORY | Определение попыток доступа к неинициализированным областям памяти | SANITIZE_SS | Определение переполнения буфера стека | SANITIZE_THREAD | Определение состояние гонок | SANITIZE_UNDEFINED | Определение невыровненных и нулевых указателей, переполнения знаковых целых, преобразования типов с плавающей точкой, ведущих к переполнению результирующей переменной |=== === Анализ покрытия кода Для сбора информации о точном количестве исполнений для каждого оператора в программе используется программа https://gcc.gnu.org/onlinedocs/gcc/Gcov.html[Gcov], входящая в состав компилятора https://gcc.gnu.org[GCC]. Для обеспечения возможности подключения анализа покрытия кода к проекту нужно выполнить функцию (обязательно после подключения всех библиотек): [source,cmake] ---- # Подключение возможности использования утилиты Gcov # для исследования покрытия кода add_code_coverage(${TRGT}) ---- Подключение осуществляется включением опции `ENABLE_CODE_COVERAGE` при запуске CMake для генерации сборочных файлов. В результате будут созданы две дополнительные цели `coverage-${TRGT}` для сбора статистики после работы программы и `coverage-report-${TRGT}` для её вывода в виде HTML-страниц. Пример анализа покрытия кода на примере проекта `cmlib-example-app-features`: [source,sh] ---- mkdir -p _build/debug cd _build/debug cmake ../.. -DENABLE_CODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug make bin/cmlib-example-app-features make coverage-cmlib-example-app-features make coverage-report-cmlib-example-app-features ---- После выполнения этих команд в каталоге `report-cmlib-example-app-features` будет сформирован отчёт в виде HTML-страниц. === Профилирование кода Библиотека CMLib предоставляет вариант сборки для профилирования кода, для которого можно сгенерировать сборочные файлы, присвоив переменной `CMAKE_BUILD_TYPE` значение `Profile`: [source,sh] ---- mkdir -p _build/profile cd _build/profile cmake ../.. -DCMAKE_BUILD_TYPE=Profile ---- По окончании работы исполняемого файла будет сгенерирован файл `gmon.out`, по данным из которого можно строить отчёты утилитой `gprof`. Например: [source,sh] ---- ./cmlib-example-app-features gprof -b cmlib-example-app-features gmon.out > analysis-tree.txt gprof -b -p cmlib-example-app-features gmon.out > analysis-flat.txt ---- === Ускорение компиляции Для ускорения компиляции используется сторонний модуль https://github.com/sakra/cotire[cotire], который автоматизирует использование предварительно откомпилированных заголовков и организует пакетный режим обработки исходных файлов в генератора. Аналогичные функции встроены в CMake, начиная с версии 3.16. Для обеспечения возможностей, предоставляемых модулем cotire, нужно выполнить функцию (обязательно после подключения всех библиотек): [source,cmake] ---- # Подключение возможности включения пакетного режима обработки # исходных файлов в генераторах для ускорения сборки cotire(${TRGT}) ---- В результате будут созданы цели с суффиксом `_unity`, при сборки которых будут применяться приведённые выше методы ускорения. Пример использования cotire для ускорения сборки на примере проекта `cmlib-example-app-features`: [source,sh] ---- mkdir -p _build/debug cd _build/debug cmake ../.. make all_unity ----