= CMake: управление проектом
:title-separator: {sp}|
:category: Программирование
:tags: программирование, cmake, qt
:toc:

== Полезные ссылки

* 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 <box@mail.domain>")
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`
<<variables-cmake,см. выше>>.

Устанавливаемые файлы делятся на две группы `MAIN` и `DEV` с помощью
параметра `COMPONENT` функции `install`. В группу `MAIN` необходимо
помещать файлы для установки на целевую систему (исполняемые файлы,
файлы настроек, файлы данных, разделяемые библиотеки), а в группу `DEV` ---
для установки на систему для разработки (заголовочные файлы, статические
библиотеки).

По умолчанию цель для упаковки исходных текстов называется `package_source`.
Бинарные пакеты создаются программой `cpack`. Пример:

[source,sh]
----
cd _build/debug
make
make package_source
cpack
----


== Примеры библиотек и приложений

[[base-lib-project]]
=== Базовая библиотека

Проект с базовой библиотекой реализован на основе <<base-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 <stdint.h>

int32_t cmlib_example_init(int32_t i);

#endif // CMLIB_EXAMPLE_HPP_
----

и файл `init.cpp`:

[source,cpp]
----
#include "init.hpp"

#include <boost/range/counting_range.hpp>

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]]
=== Базовое приложение

Проект с базовым приложением реализован на основе <<base-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 <iostream>
#include <boost/range/counting_range.hpp>

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 );
}
----


=== Подключение внешнего проекта

Проект, использующий для сборки внешний проект, реализован на основе проектов
<<base-lib-project,базовой библиотеки>> и <<base-app-project,базового приложения>>.
Исходные тексты содержат комментарии, объясняющие назначение используемых функций.
Проект можно посмотреть 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  <INSTALL_DIR>/lib/libcmlib-example.a
  CMAKE_ARGS        -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -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 $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>)

# Компоновка с библиотекой из внешнего проекта
target_link_libraries(${TRGT} ${CMAKE_BINARY_DIR}/lib/libcmlib-example.a)
----

<<<

Для проверки работоспособности в файле `src/cmlib-example/main.cpp` нужно
вызвать функцию `cmlib_example_init` из библиотеки, предоставляемой внешним
проектом. Например, можно заменить его содержимое на:

[source,cpp]
----
#include <cmlib-example/init.hpp>

#include <iostream>

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 с поддержкой локализации основан
на проекте <<base-app-project,базового приложения>> и библиотеке
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} <SOURCE_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 $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>)

# Компоновка с библиотеками из внешнего проекта
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 $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>)

# Компоновка с библиотеками из внешнего проекта
target_link_libraries(${TRGT} myx-qt myx-filesystem myx-base)
----

<<<

Для проверки работоспособности подключения Qt5 файл
`src/cmlib-example/main.cpp` нужно заменить на:

[source,cpp]
----
#include "cmlib_private_config.hpp"

#include <myx/qt/translators.hpp>

#include <QCoreApplication>
#include <QDebug>

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 с использованием графического интерфейса основан
на проекте <<qt5-con,консольного приложения для 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]
----
<RCC>
  <qresource prefix="/icon">
    <file alias="icon.png">icon.png</file>
  </qresource>
</RCC>
----

и загрузить файл иконки:

[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]
----
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>TestWindow</class>
 <widget class="QMainWindow" name="TestWindow">
  <property name="geometry">
   <rect><x>0</x><y>0</y><width>413</width><height>253</height></rect>
  </property>
  <property name="windowTitle">
   <string>Test Window</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="exitButton">
    <property name="geometry">
     <rect><x>170</x><y>30</y><width>80</width><height>26</height></rect>
    </property>
    <property name="text">
     <string>Press me</string>
    </property>
   </widget>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>
----

<<<

заголовочный файл `src/cmlib-example/test_window.hpp`:

[source,cpp]
----
#ifndef TEST_WINDOW_HPP_
#define TEST_WINDOW_HPP_

#pragma once

#include "ui_test_window.h"

#include <QMainWindow>

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 <myx/qt/translators.hpp>

#include <QApplication>
#include <QIcon>
#include <QDebug>

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 содержит шаблонные функции для использования
в программных проектах. Пример проекта с примерами использования
функций основан на проекте <<qt5-gui,графического приложения для Qt5>>.
Исходные тексты содержат комментарии, объясняющие назначение используемых функций.
Проект можно посмотреть 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`. Пример использования:

[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
----
