@ -0,0 +1,671 @@
---
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 <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` .