dsp-site/wiki/Prog/CMake/CMake управление проектом.md
2019-04-20 11:38:43 +03:00

672 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 Hitchhikers 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 <stdint.h>
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 <iostream>
#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
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/libcmex>)
# Имя выходного файла для цели (параметр 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 <stdint.h>
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 <INSTALL_DIR>/lib/libcmext.a
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -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
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include/cmext>)
```
```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 <iostream>
#include <cmext/cmext.hpp>
#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 <QtCore>
#include <iostream>
#include <cmext/cmext.hpp>
#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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MyMainWindow</class>
<widget class="QMainWindow" name="MyMainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>678</width>
<height>415</height>
</rect>
</property>
<property name="windowTitle">
<string>Main Window</string>
</property>
<widget class="QWidget" name="centralwidget"/>
</widget>
<resources/>
<connections/>
</ui>
```
заголовочный файл `cmex/src/cmex/my_main_window.hpp`:
```cpp
#ifndef CMEX_MY_MAIN_WINDOW_HPP_
#define CMEX_MY_MAIN_WINDOW_HPP_
#include <QWidget>
#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 <QtCore>
#include <QtWidgets>
#include <iostream>
#include <cmext/cmext.hpp>
#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`.