Использование сторонних библиотек усложняет процесс воспроизводимости сборки. В случае, когда целевая операционная система одна, это не доставляет особых проблем и достаточно простого Makefile
, но если предполагается разработка кросс-платформенного продукта, то возникают неоднозначности:
- какой компилятор используется для сборки (gcc, clang, cl.exe);
- расположение include-файлов библиотек для компиляции;
- рссположение файлов библиотек для компоновки.
По этой причине часто практикуется не распространение Makefile
, написанного для конкретного стека инструментов, а его генерация по декларативному описанию в процессе сборки: ./configure
, qmake
, или cmake
.
Наиболее гибкой системой сборки, и при этом относительно простой, является CMake, которая реализована с поддержкой не только UNIX-подобных систем, но и Windows.
Описание проекта находится в файле CMakeLists.txt
и имеет примерно следующий вид:
# признак того, что это файл для cmake
# номер версии - это минимально требуемый для сборки проекта
# не стоит злоупотребять указанием самой свежей версии
# CMake, поскольку в консервативных Linux-дистрибутивах
# может быть что-то более старое
cmake_minimum_required(VERSION 3.2)
# имя проекта - не обязательно, обычно используется IDE
project(my_great_project)
# команда `set` устанавливает значение переменной
# некоторые переменные (их имена начинаются с CMAKE_)
# имеют специальное значение
# дополнительные опции компилятора Си
set(CMAKE_C_FLAGS "-std=gnu11")
# дополнительные опции компилятора C++
set(CMAKE_CXX_FLAGS "-std=gnu14")
# в переменной SOURCES будет храниться список файлов;
# если файлов не много, то можно этого не делать,
# но некоторым IDE это требуется для навигации по проекту
set(SOURCES
file1.c
file2.cpp
file3.cpp
)
# добавление цели для сборки - бинарного файла;
# синтаксис ${...} означает использование значения
# переменной, которая в данном примере будет раскрыта
# в список файлов, из которых собирается программа
add_executable(my_cool_program ${SOURCES})
Для сборки CMake-проекта необходимо выполнить две стадии:
- Сгенерировать
Makefile
изCMakeLists.txt
- Собрать проект обычнычным инструментом
make
.
Обычно в процессе генерации Makefile
и при сборке проекта создается много временных файлов. По этой причине сборку принято проводить в отдельном каталоге, - чтобы не засорять каталог с исходными текстами.
$ mkdir build # создаем каталог для сборки
$ cd build # переходим в него
$ cmake ../ # генерируем Makefile
# аргумент cmake - это каталог, который
# содержит файл CMakeLists.txt
$ make # запуск компиляции
Для многих OpenSource библиотек в стандартной поставке CMake уже готовы модули поддержки, которые выполняют поиск библиотеки. В случае c UNIX этот поиск осуществляется с помощью запуска команд конфигурации, либо проверки различных вариантов написания имен файлов в /usr/include
и /usr/lib
. Для Windows просматривается системный реестр.
Список поддерживаемых библиотек можно найти в поставке CMake, для Linux это может быть каталог (в разных дистрибутивах они разные) /usr/share/cmake/Modules
. Все файлы модулей имеют название FindИМЯБИБЛИОТЕКИ.cmake
.
Подключение библиотеки, которая поддерживается "из коробки", осуществляется с помощью команды find_package
. В случае, если необходимые файлы присутствуют, то определяются переменные:
ИМЯБИБЛИОТЕКИ_FOUND
- переменная, значение которой устанавливается в1
, если библиотека не отмечена какREQUIRED
;ИМЯБИБЛИОТЕКИ_INCLUDE_DIRS
- список дополнительных каталогов, в которых нужно искать заголовочные файлы (опции компилятора-I...
);ИМЯБИБЛИОТЕКИ_LIBRARIES
- список дополнительных библиотек и каталоги к ним (опции компилятора-l...
и-L...
);
Пример для curl:
# найти библиотеку CURL; опция REQUIRED означает,
# что библиотека является обязательной для сборки проекта,
# и если необходимые файлы не будут найдены, cmake
# завершит работу с ошибкой
find_package(CURL REQUIRED)
add_executable(my_cool_program ${SOURCES})
# добавляет в список каталогов для цели my_cool_program,
# которые превратятся в опции -I компилятора для всех
# каталоги, которые перечислены в переменной CURL_INCLUDE_DIRECTORIES
target_include_directories(my_cool_program ${CURL_INCLUDE_DIRECTORIES})
# для цели my_cool_program указываем библиотеки, с которыми
# программа будет слинкована
target_link_libraries(my_cool_program ${CURL_LIBRARIES})
Если необходимо использовать библиотеку для всех целей проекта, а не для отдельных, то можно использовать команды include_directories
и link_libraries
.
Для многих GNU-библиотек существуют описания их использования, которые можно использовать утилитой pkg-config(1)
. Эти описания можно использовать в проектах CMake в том случае, если для них не реализованы описания CMake, но существуют описания pkg-config. Обычно эти файлы *.pc
располагаются в каталоге /usr/lib[64]/pkgconfig
.
Пример для fuse3:
# подключаем модуль интеграции с pkg-config
find_package(PkgConfig REQUIRED)
pkg_check_modules(
FUSE # имя префикса для названий выходных переменных
REQUIRED # если библиотека является обязательной
fuse3 # имя библиотеки, должен существовать файл fuse3.pc
)
# можно использовать переменные FUSE_INCLUDE_DIRECTORIES
# и FUSE_LIBRARIES
target_include_directories(my_cool_program ${FUSE_INCLUDE_DIRECTORIES})
target_link_libraries(my_cool_program ${FUSE_LIBRARIES})
# дополнительные флаги компиляции, например определения -D...,
# которые не указывают на каталоги для поиска заголовочных файлов,
# перечислены в переменной FUSE_CFLAGS_OTHER
target_compile_options(my_cool_program ${FUSE_CFLAGS_OTHER})
В случае, если для библиотеки не подготовлено никаких описаний, то можно попытаться найти необходимые файлы, используя перебор различных комбинаций имен и стандартных каталогов:
# поиск файла динамической библиотеки
find_library(
SOME_LIBRARY # переменная, в которую будет записан результат
NAMES # перечисляются различные варианты имен для поиска
some
something
somelib0
)
# поиск пути к заголовочным файлам
find_path(
SOME_INCLUDE_DIRECTORY # переменная, в которую будет записан результат
NAMES # имена файлов, которые могут содержаться в каталоге
somelib.h
somelib_common.h
PATH_SUFFIXES # возможные имена подкаталогов в .../include/
somelib
somelib-1.0
)
target_include_directories(my_cool_program ${SOME_INCLUDE_DIRECTORY})
target_link_libraries(my_cool_program ${SOME_LIBRARY})