dsp-site/wiki/Prog/Development/CMake управление проектом.adoc
2020-04-19 12:05:06 +03:00

1447 lines
60 KiB
Plaintext
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.

= CMake: управление проектом
:title-separator: {sp}|
:category: Программирование
:tags: программирование, cmake, qt
:toc:
include::{l10ndir}/{lang}.adoc[]
== Полезные ссылки
* https://github.com/onqtam/awesome-cmake[Каталог ссылок]
* https://cgold.readthedocs.io/en/latest/index.html[CGold: The Hitchhikers 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 "Стандартные файлы и каталоги"
----
IMPORTANT: Файлы `.keep-directory` позволяют защитить каталоги от удаления
(будет выводиться дополнительное предупреждение, что каталог не пуст) и
обеспечивают возможность помещения каталогов с систему контроля версий git,
в которой пустые каталоги недопустимы (это правильно!).
Создать файл `.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`.
IMPORTANT: Настройка правил форматирования помогает другим разработчикам
придерживаться вашего стиля программирования и отправлять изменения в ваш
проект в формате, который удобен вам. Проявите заботу о своих коллегах и
своём проекте!
Пример использования:
[source,cmake]
----
# Создание цели format-sources для автоматического форматирования кода
add_format_sources(${TRGT} ${TRGT_sources} ${TRGT_headers})
----
=== Статический анализ исходных кодов
Для работы с программами на языке C{plus}{plus} используются утилиты,
выполняющие статический анализ кода и генерирующие отчёты, помогающие
программисту находить и устранять ошибки. Эти программы применяют методы,
позволяющие в синтаксически корректном коде находить недостатки или ошибки,
которые пропускает компилятор, ценой продолжительного анализа исходных текстов.
Библиотека CMLib поддерживает анализаторы https://github.com/KDE/clazy[clazy],
https://clang.llvm.org/extra/clang-tidy[Clang Tidy],
https://clang-analyzer.llvm.org[Clang Static Analyzer] и
https://www.viva64.com/ru/pvs-studio[PVS-Studio].
[[clazy-check]]
==== 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`.
Правила проверок задаются в файле `.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})
----
=== Автоматическое исправление кода
IMPORTANT: Редактирование кода в автоматическом режиме может приводить
к его неработоспособности, хотя это и маловероятно. Перед выполнением
действий, приведённых в данном раздела, желательно фиксировать текущее
состояние в репозитории или делать резервную копию.
==== clazy
Программа clazy может преобразовывать в программах, использующих Qt,
подключения сигналов и слотов старого типа, производить замену старых ключевых
слов, подставлять оптимизированные способы для инициализации строк,
исправлять циклы и передачу аргументов в функции для избежания лишних копирований.
Для использования данной возможности необходимо установить пакеты:
[source,sh]
----
sudo apt-get install clazy clang-tools
----
Для включения автоматического исправления нужно в настройках сборки проекта
menu:Проекты[Настройки сборки] выбрать цель `clazy-check`:
[.text-center]
.Выбор цели
image::cmake-fixes/clazy1.png[clazyfix1,pdfwidth=90%,scaledwidth=90%,align="center"]
{empty} +
Затем в перечне опций включить `CMLIB_CLAZY_FIX` и нажать кнопку
btn:[Применить изменения]:
[.text-center]
.Разрешение автозамены
image::cmake-fixes/clazy2.png[clazyfix2,pdfwidth=90%,scaledwidth=90%,align="center"]
{empty} +
Пример проекта, в котором показаны возможности clazy, можно посмотреть
https://git.246060.ru/f1x1t/cmlib-example-clazy-fix[здесь]. Содержание
изменений, произведённых автоматически, можно увидеть
https://git.246060.ru/f1x1t/cmlib-example-clazy-fix/commit/81ed1e72b14f17bac0a39ab41bc3ba0ba2bdcb8e?style=split[здесь].
Можно сделать копию репозитория и выполнить правки в автоматическом режиме
самостоятельно:
[source,sh]
----
git clone --recursive https://git.246060.ru/f1x1t/cmlib-example-clazy-fix
----
==== Clang-Tidy
Анализатор Clang-Tidy предоставляет более широкие возможности по
автоматической правке кода. В проектах, использующих Qt, желательно
использовать Clang-Tidy после clazy. Программу можно установить командой:
[source,sh]
----
sudo apt-get install clang-tools clang-tidy
----
Для включения автоматического исправления нужно в настройках сборки проекта
menu:Проекты[Настройки сборки] выбрать цель `clang-tidy-check`:
[.text-center]
.Выбор цели
image::cmake-fixes/clang-tidy1.png[clangtidyfix1,pdfwidth=90%,scaledwidth=90%,align="center"]
{empty} +
Затем в перечне опций включить `CMLIB_CLANG_TIDY_FIX` и нажать кнопку
btn:[Применить изменения]:
[.text-center]
.Разрешение автозамены
image::cmake-fixes/clang-tidy2.png[clangtidyfix2,pdfwidth=90%,scaledwidth=90%,align="center"]
{empty} +
=== Динамический анализ программы
Динамический анализ программы позволяет ценой значительного замедления
скорости работы получить дополнительную информацию о ходе её выполнения.
Современные компиляторы делают вставку инструкций в определённые точки
программы, во время работы программы в них собирается необходимая информация,
а по её завершению предоставляется отчёт. Основная информация о работе
таких анализаторов находится 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
----