--- title: "CMake: управление проектом" category: Программирование tags: программирование, cmake summary: "" CSS: table-100.css documentclass: extarticle monofont: Pragmata Pro monofontoptions: - Scale=0.7 ... [TOC] ### Полезные ссылки * [Каталог ссылок](https://github.com/onqtam/awesome-cmake) * [CGold: The Hitchhiker’s Guide to the CMake](https://cgold.readthedocs.io/en/latest/index.html) ### Структура каталогов проекта Файлы проекта и результаты компиляции размещаются в каталогах: ``` └── cmex ├── _build │ ├── Debug │ └── Release ├── .git ├── cmake │ ├── cmlib │ ├── etc │ ├── find │ └── generators ├── doc ├── files │ ├── etc │ ├── share │ └── var ├── l10n ├── src │ ├── app │ └── lib ├── thirdparty └── tools ``` Назначение каталогов приведено в таблице. Каталог | Назначение -------------------------|---------------------------------------------- `cmex/_build` | Результаты компиляции `cmex/_build/Debug` | Результаты компиляции в режиме отладки `cmex/_build/Release` | Результаты компиляции в режиме выпуска `cmex/.git` | Репозиторий git `cmex/cmake` | Файлы с дополнительными функциями для CMake `cmex/cmake/cmlib` | Библиотека функций для CMake `cmex/cmake/find` | Модули CMake для поиска внешних программ и библиотек `cmex/cmake/etc` | Файлы настроек, используемые в CMake `cmex/cmake/generators` | Генераторы проектов `cmex/doc` | Документация для проекта `cmake/files` | Каталог для дополнительных файлов `cmake/files/etc` | Каталог для файлов настроек проекта `cmake/files/share` | Каталог для неизменяемых файлов `cmake/files/var` | Каталог для изменяемых файлов `cmex/l10n` | Файлы переводов `cmex/src` | Исходные тексты `cmex/src/app` | Исходные тексты программ `cmex/src/lib` | Исходные тексты библиотек `cmex/thirdparty` | Исходные тексты сторонних проектов `cmex/tools` | Дополнительные утилиты Каталог `_build` создаётся, чтобы избежать попадания получаемых во время сборки файлов в иерархию основного проекта. Запись результатов сборки проекта внутрь иерархии каталогов с исходными текстами приводит к засорению формируемыми на этапе сборки файлами, которые затрудняют разработку, поиск в оригинальных файлах и мешают ориентироваться в проекте. При работе с несколькими типами сборки, например, отладка и выпуск, появляется необходимость корректного полного удаления результатов предыдущего тип сборки. ### Начало проекта В каталоге `cmex` нужно создать файл `CMakeLists.txt`: ```cmake # Минимальная версия Cmake cmake_minimum_required(VERSION 3.3) cmake_policy(VERSION 3.0.2..3.7) # Название и версия проекта и используемые языки программирования project(cmex VERSION 0.2.0 LANGUAGES C CXX) ``` Значение версии следует формировать согласно правилам [семантического версионирования](https://semver.org/lang/ru/). В каталог `cmake/cmlib` установить субмодуль CMLib, содержащий функции для CMake: ``` git submodule add ssh://git@gitlab-server/root/cmlib cmake/cmlib ``` и подключить в файле `CMakeLists.txt`: ```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) ``` В файле `cmake/etc/organization.txt` записать название организации, которой принадлежит проект: ``` ORG, Inc. ``` В файле `cmake/etc/cpack_ignore.txt` перечислить шаблоны для исключения из создаваемого целью `dist` архива. Например: ``` cmake/lib/.git$ .git$ files/var CMakeLists.txt.user ~$ \\\\..*\\\\.bak$ \\\\..*\\\\.tmp$ \\\\..*\\\\.swp$ ``` Чтобы проверить корректность файла `CMakeLists.txt`, нужно создать каталог `_build` в каталоге `cmex`, перейти в него и выполнить команды: ```sh cmake .. make ``` ### Поиск системных библиотек Системные библиотеки можно искать с помощью программы `pkgconfig`, которая хранит базу данных параметров, включающую пути к заголовочным файлами и перечни библиотек, необходимых для компоновки. Сначала производится наличие модуля `PkgConfig`, в котором определена функция `pkg_check_modules`, которая и осуществляет поиск. Например, для поиска библиотек `gsl`, `fftw3` и `udev` можно написать: ```cmake # Поиск библиотек с помощью pkgconfig find_package(PkgConfig) pkg_check_modules(GSL REQUIRED gsl) pkg_check_modules(FFTW3 REQUIRED fftw3) pkg_check_modules(UDEV udev) ``` Если системная библиотека поставляется без файла описания для `pkgconfig`, то для её поиска может быть написан специальный модуль для `CMake`, который вызывается функцией `find_package`. Кроме того функция `find_package` может возвращать дополнительные значения, например, пути к исполняемым файлам. ```cmake # Поиск с помощью функции find_package find_package(LibXml2) find_package(CURL) ``` Если для библиотеки нет модуля, выполняющего её поиск, то можно произвести поиск с помощью функции `find_library`. Например, ```cmake # Поиск библиотеки с помощью функции find_library find_library(MATHGL mgl PATHS /usr/lib /usr/lib/x86_64-linux-gnu) find_library(MATHGLQT5 mgl-qt5 PATHS /usr/lib /usr/lib/x86_64-linux-gnu) ``` ### Автоматически генерируемый заголовочный файл На этапе конфигурирования проекта можно создать файл, в который будут записаны параметры, полученные на данной стадии. В библиотеке CMLib присутствует функция `cmlib_config_hpp_generate()`, создающая файл `${CMAKE_BUILD_DIR}/include/config.hpp`, в который записывается информация о имени и версии проекта, дате и типе сборки. ```cmake # Автоматически генерируемый заголовочный файл cmlib_config_hpp_generate() ``` ### Базовая библиотека В файле `cmex/CMakeLists.txt` должна быть строка, включающая поиск файла `CMakeLists.txt` в подкаталоге `src/lib`: ```cmake add_subdirectory(src/libcmex) ``` В каталоге `cmex/src/libcmex` нужно создать файл `cmex.hpp`: ```c #ifndef LIBCMEX_CMEX_HPP_ #define LIBCMEX_CMEX_HPP_ #include int32_t cmex_init(int32_t i); #endif // LIBCMEX_CMEX_HPP_ ``` файл `cmex.cpp`: ```c #include "cmex.hpp" int32_t cmex_init(int32_t i = 0) { return i; } ``` и файл `CMakeLists.txt`: ```cmake # Название основной цели и имя библиотеки в текущем каталоге set(current_target cmex) # Список файлов исходных текстов set(current_target_sources cmex.cpp ) # Список заголовочных файлов (используется для установки) set(current_target_headers cmex.hpp ) add_common_library(TARGET ${current_target} SOURCES ${current_target_sources}) common_target_properties(${current_target}) # Цель, используемая только для установки заголовочных файлов, без компиляции проекта add_custom_target(${current_target}-install-headers COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=headers -P "${CMAKE_BINARY_DIR}/cmake_install.cmake" ) # Правила для установки install(TARGETS ${current_target}_static ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(BUILD_SHARED_LIBS) install(TARGETS ${current_target}_shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() install(FILES ${CMAKE_BINARY_DIR}/include/config.hpp ${current_target_headers} COMPONENT headers DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${current_target}) ``` ### Базовое приложение В файле `cmex/CMakeLists.txt` должна быть строка, включающая поиск файла `CMakeLists.txt` в подкаталоге `src/cmex`: ```cmake add_subdirectory(src/cmex) ``` В каталоге `cmex/src/cmex` нужно создать файл `main.cpp`: ```cpp #include "compiler_features.hpp" #include "config.hpp" #include #include "cmex.hpp" int main(int argc, char **argv) { std::cout << CMEX_COMPILER_VERSION_MAJOR << std::endl; // Значение из compiler_features.hpp std::cout << BUILD_TYPE << std::endl; // Значение из config.hpp std::cout << CMEX_VERSION_STR << std::endl; // Значение из config.hpp std::cout << cmex_init(4) << std::endl; // Функция из внутренней библиотеки return 0; } ``` и файл `CMakeLists.txt`: ```cmake # Название основной цели в текущем каталоге set(current_target cmex_app) # Список файлов исходных текстов set(current_target_sources main.cpp ) # Цель для создания исполняемого файла add_executable(${current_target} ${current_target_sources}) common_target_properties(${current_target}) # Зависимость от библиотеки из текущего проекта add_dependencies(${current_target} cmex) # Добавление внутреннего каталога src/libcmex к списку путей для поиска заголовочных файлов target_include_directories(${current_target} PUBLIC $) # Имя выходного файла для цели (параметр OUTPUT_NAME) set_target_properties(${current_target} PROPERTIES OUTPUT_NAME cmex RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR} ) # Путь поиска библиотек внутри проекта link_directories(${CMAKE_INSTALL_LIBDIR}) # Сначала внутренние статические библиотеки target_link_libraries(${current_target} cmex_static) # Правила для установки install(TARGETS ${current_target} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) ``` ### Подключение внешнего проекта В каталоге `cmex/thirdparty` нужно создать каталог `cmext` с проектом, состоящим из файлов `cmext.hpp`: ```c #ifndef CMEXT_CMEXT_HPP_ #define CMEXT_CMEXT_HPP_ #include int32_t cmext_init(int32_t i); #endif ``` `cmext.cpp`: ```c #include "cmext.hpp" int32_t cmext_init(int32_t i = 0) { return i; } ``` и `CMakeLists.txt`: ```cmake cmake_minimum_required(VERSION 3.3) project(cmext) include(GNUInstallDirs) add_library(cmext cmext.cpp) install(TARGETS cmext ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(FILES cmext.hpp COMPONENT headers DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME}) ``` В файле `cmex/CMakeLists.txt` нужно подключить стандартный модуль `ExternalProject` и описать правила для его загрузки, настройки, компиляции и установки для сопряжения с текущим проектом: ```cmake # Подключение внешних проектов include(ExternalProject) ExternalProject_Add(cmext EXCLUDE_FROM_ALL TRUE SOURCE_DIR ${CMAKE_SOURCE_DIR}/thirdparty/libcmext INSTALL_DIR ${CMAKE_BINARY_DIR} DOWNLOAD_COMMAND "" BUILD_BYPRODUCTS /lib/libcmext.a CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_BUILD_TYPE=Release ) ``` Вызовы этих функций нужно сделать до функций `add_subdirectories`, чтобы в подключенных подкаталогах можно было использовать цель `cmext` для определения зависимостей. В файле `cmex/src/cmex/CMakeLists.txt` нужно подключить внешний проект `cmext`: ```cmake # Зависимость от библиотеки из внешнего проекта проекта add_dependencies(${current_target} cmext) ``` ```cmake # Добавление каталога, в который устанавливаются заголовочные файлы от внешнего # проекта cmext, к списку путей для поиска заголовочных файлов target_include_directories(${current_target} PUBLIC $) ``` ```cmake # Библиотека из внешнего проекта cmext target_link_libraries(${current_target} ${CMAKE_BINARY_DIR}/lib/libcmext.a) ``` Для проверки работоспособности в файле `cmex/src/cmex/main.cpp` нужно вызвать функцию `cmext_init` из библиотеки, предоставляемой внешним проектом. Например: ```cpp #include "compiler_features.hpp" #include "config.hpp" #include #include #include "cmex.hpp" int main(int argc, char **argv) { std::cout << CMEX_COMPILER_VERSION_MAJOR << std::endl; // Значение из compiler_features.hpp std::cout << BUILD_TYPE << std::endl; // Значение из config.hpp std::cout << CMEX_VERSION_STR << std::endl; // Значение из config.hpp std::cout << cmex_init(4) << std::endl; // Функция из внутренней библиотеки std::cout << cmext_init(9) << std::endl; // Функция из внешней библиотеки return 0; } ``` ### Qt5 Для поиска необходимых компонентов Qt5 нужно в файл `cmex/CMakeLists.txt` добавить строки: ```cmake find_package(Qt5 COMPONENTS Core Network Gui Widgets DBus Concurrent Sql REQUIRED) ``` Библиотека CMLib автоматически подключает вызов препроцессора `moc` и компилятора ресурсов `rcc`, если цель использует модуль `Core`, и вызывает компилятор файлов описания интерфейса, если цель использует модуль `Widgets`. ### Консольное приложение В файл `cmex/src/cmex/CMakeLists.txt` добавить строки: ```cmake # Qt5 qt_translation(TARGET ${current_target} TS_DIR ${CMAKE_SOURCE_DIR}/l10n LANGUAGES ru_RU) target_include_directories(${current_target} SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS}) target_compile_options(${current_target} PUBLIC "${Qt5Core_EXECUTABLE_COMPILE_FLAGS}") target_link_libraries(${current_target} Qt5::Core) ``` Для проверки работоспособности подключения Qt5 файл `cmex/src/cmex/main.cpp` нужно заменить на: ```cpp #include "compiler_features.hpp" #include "config.hpp" #include #include #include #include "cmex.hpp" int main(int argc, char **argv) { QCoreApplication app(argc, argv); QTranslator translator; if (translator.load(QLocale(), "cmex_app", QLatin1String("_"), QLatin1String(":/qm"))) { app.installTranslator(&translator); } std::cout << QObject::tr("Compiler version: ").toStdString() << CMEX_COMPILER_VERSION_MAJOR << std::endl; // Значение из compiler_features.hpp std::cout << QObject::tr("Project version: ").toStdString() << CMEX_VERSION_STR << std::endl; // Значение из config.hpp std::cout << QObject::tr("Build type: ").toStdString() << BUILD_TYPE << std::endl; // Значение из config.hpp std::cout << QObject::tr("libcmex function call: ").toStdString() << cmex_init(4) << std::endl; // Функция из внутренней библиотеки std::cout << QObject::tr("libcmext function call: ").toStdString() << cmext_init(9) << std::endl; // Функция из внешней библиотеки return 0; } ``` После сборки проекта в каталоге `cmex/l10n` появится файл `cmex_app_ru_RU.ts`, в котором нужно отредактировать переводы с помощью программы `linguist`. После сохранения переводов проект нужно пересобрать, файл переводов в скопилированном виде будет встроен в исполняемый файл `cmex`, а доступ к нему будет осуществляться с помощью кода: ```cpp if (translator.load(QLocale(), "cmex_app", QLatin1String("_"), QLatin1String(":/qm"))) { app.installTranslator(&translator); } ``` ### Графическое приложение Для создания минимального графического приложения нужно создать файл описания интерфейса `cmex/src/cmex/my_main_window.ui`: ```xml MyMainWindow 0 0 678 415 Main Window ``` заголовочный файл `cmex/src/cmex/my_main_window.hpp`: ```cpp #ifndef CMEX_MY_MAIN_WINDOW_HPP_ #define CMEX_MY_MAIN_WINDOW_HPP_ #include #include "ui_my_main_window.h" class MyMainWindow : public QWidget, private Ui::MyMainWindow { Q_OBJECT public: MyMainWindow(QWidget* parent = 0); virtual ~MyMainWindow(); }; #endif /* CMEX_MY_MAIN_WINDOW_HPP_ */ ``` и файл с реализацией конструктора и деструктора `cmex/src/cmex/my_main_window.cpp`: ```cpp #include "my_main_window.hpp" MyMainWindow::MyMainWindow(QWidget* parent) { } MyMainWindow::~MyMainWindow() { } ``` Для отображения графического окна нужно заменить файл `cmex/src/cmex/main.cpp` на: ```cpp #include "compiler_features.hpp" #include "config.hpp" #include #include #include #include #include "cmex.hpp" #include "my_main_window.hpp" int main(int argc, char **argv) { QApplication app(argc, argv); QTranslator translator; if (translator.load(QLocale(), "cmex_app", QLatin1String("_"), QLatin1String(":/qm"))) { app.installTranslator(&translator); } std::cout << QObject::tr("Compiler version: ").toStdString() << CMEX_COMPILER_VERSION_MAJOR << std::endl; // Значение из compiler_features.hpp std::cout << QObject::tr("Project version: ").toStdString() << CMEX_VERSION_STR << std::endl; // Значение из config.hpp std::cout << QObject::tr("Build type: ").toStdString() << BUILD_TYPE << std::endl; // Значение из config.hpp std::cout << QObject::tr("libcmex function call: ").toStdString() << cmex_init(4) << std::endl; // Функция из внутренней библиотеки std::cout << QObject::tr("libcmext function call: ").toStdString() << cmext_init(9) << std::endl; // Функция из внешней библиотеки MyMainWindow* mmw = new MyMainWindow(); mmw->show(); return app.exec(); } ``` В файле `cmex/src/cmex/CMakeLists.txt` добавить новые файлы к списку файлов, используемых для компиляции: ```cmake set(current_target_sources main.cpp my_main_window.cpp ) set(current_target_uis my_main_window.ui ) ``` ```cmake # Цель для создания исполняемого файла add_executable(${current_target} ${current_target_sources} ${current_target_uis}) ``` и добавить строки для подключения графических библиотек Qt5 и соответствующих им заголовочных файлов: ```cmake target_include_directories(${current_target} SYSTEM PUBLIC ${Qt5Gui_INCLUDE_DIRS}) target_include_directories(${current_target} SYSTEM PUBLIC ${Qt5Widgets_INCLUDE_DIRS}) target_link_libraries(${current_target} Qt5::Gui) target_link_libraries(${current_target} Qt5::Widgets) ``` Во время сборки проекта в файл переводов `cmex/l10n/cmex_app_ru_RU.ts` будут добавлены повые строки, их нужно перевести с помощью `linguist` и снова скомпилировать проект. ### Удаление установленных файлов В библиотеку CMLib добавлена цель `uninstall`, позволяющая удалить файлы, перечисленные в файле `${CMAKE_BUILD_DIR}/install_manifest.txt`. ### Архивирование проекта Стандарный модуль `CPack` осуществляет архивирование проекта. В файле `cproj/cmake/etc/cpack_ignore.txt` определён список типовых масок файлов для исключения из архива: ``` .git$ files/var CMakeLists.txt.user ~$ \\\\..*\\\\.bak$ \\\\..*\\\\.tmp$ \\\\..*\\\\.swp$ ``` По умолчанию цель для упаковки проекта называется `package_source`. В библиотеке CMLib определены значения основных параметров, а также дополнительная цель `dist`.