Чтение переменных в cmake из другого cmakelists.txt

Сборка проектов с CMake. Введение

Для автоматизации сборки проектов традиционно используют системы сборки, такие как make на Unix подобных системах и nmake для компилятора Microsoft. Также традиционно написание файлов для сборки проекта под эти системы является задачей нетривиальной. Конечно в пользуясь только Mictosoft Visual Studio можно даже не подозревать о существовании этих файлов, так как интегрированная среда разработки достаточно удобно скрывает всю кухню, оставляя снаружи несколько диалоговых окон и кнопку Build. Но для сложных проектов использующих массу сторонних библиотек и кроссплатформенных проектов такой подход часто оказывается неприемлемым.

Кратко говоря make-файл представляет из себя описания последовательности действий необходимых для того чтобы достичь какой либо цели, например скомпилировать программу. На псевдокоде это может выглядеть примерно так:

Что делать когда компилятор закончит работать?

Взять получившийся в результате работы компилятора объектный файлы hello_world.o и запустить линковщик передав ему этот файл.

Все.

Причем каждый подпроект может иметь свой файл сборки, а главный make-файл сможет пройти рекурсивно по подпроектам, выполнив его файл сборки, а потом собрать проект из этих компонентов.

Выглядит все просто, проблемы возникают дальше и проблем несколько:

  1. Разрешение зависимостей возникающих между частями проекта
  2. Синтаксическая сложность и неоднозначность классических make-файлов
  3. Привязка к конкретной утилите автоматической сборки и как следствие непереносимость на другие платформы

Для решения части этих проблем или всех сразу были созданы следующие инструменты: Automake (http://sourceware.org/automake/) , CMake (http://www.cmake.org/), SCons (http://www.scons.org/). Список далеко не полный.

Я предлагаю рассмотреть CMake, как простой для изучения, понятный, мощный и кроссплатформенный инструмент, который решает сразу все три проблемы. Подробности ниже.

7th Январь 201020:52

Определение правил

Быстрый старт

Правило описывает, когда и как создаются определенные файлы ( цели правил). Он также может служить для обновления целевого файла, если любой из файлов, необходимых для его создания ( предварительные требования к цели), новее, чем цель.

Правила следуют синтаксису ниже: (Обратите внимание, что команды, следующие за правилом, отступаются от вкладки )

где цели и предварительные условия — это имена файлов или специальные зарезервированные имена и команды (если они есть), выполняются оболочкой для создания / перестройки целей , устаревших.

Чтобы выполнить правило, можно просто запустить команду в терминале из того же каталога, где находится Makefile . Запуск без указания цели, выполнит первое правило, определенное в Makefile . По соглашению первое правило в Makefile часто называется всем или по умолчанию , обычно перечисляя все допустимые цели сборки в качестве предварительных условий.

выполняет только правило, если цель устарела, то есть либо она не существует, либо время ее модификации старше любого из ее предварительных условий. Если список предварительных условий пуст, правило будет выполняться только при первом вызове для создания целей. Однако, когда правило не создает файл, а цель — фиктивная переменная, правило всегда будет выполняться.

GNU make

Правила шаблонов

Правила шаблонов используются для указания нескольких целей и создания имен требуемых имен из целевых имен. Они более общие и более мощные по сравнению с обычными правилами, поскольку каждая цель может иметь свои собственные предпосылки. В правилах шаблонов связь между целью и предварительным условием строится на основе префиксов, включая имена и суффиксы путей, или и то, и другое.

Представьте, что мы хотим создать цели и , скомпилировав C-скрипты, и , соответственно. Это можно сделать, используя обычные правила:

где автоматическая переменная

Однако, поскольку цели имеют один и тот же суффикс, приведенные выше два правила теперь могут быть заменены следующим шаблоном:

Неявные правила

Неявные правила говорят о как использовать обычные методы для создания определенных типов целевых файлов, которые используются очень часто. использует имя целевого файла, чтобы определить, какое неявное правило вызывается.

Пример правила шаблона, который мы видели в предыдущем разделе, на самом деле не должен быть объявлен в Makefile, поскольку имеет неявное правило для компиляции C. Таким образом, в следующем правиле предварительные условия и будут построены с использованием неявного правила для компиляции C, прежде чем строить .

Make-файл

Программа make выполняет команды согласно правилам, указанным в специальном файле. Этот файл называется make-файл (makefile, мейкфайл). Как правило, make-файл описывает, каким образом нужно компилировать и компоновать программу.

make-файл состоит из правил и переменных. Правила имеют следующий синтаксис:

цель1 цель2 ...: реквизит1 реквизит2 ...
    	команда1
    	команда2
    	...

Правило представляет собой набор команд, выполнение которых приведёт к сборке файлов-целей из файлов-реквизитов.

Правило сообщает make, что файлы, получаемые в результате работы команд (цели) являются зависимыми от соответствующих файлов-реквизитов. make никак не проверяет и не использует содержимое файлов-реквизитов, однако, указание списка файлов-реквизитов требуется только для того, чтобы make убедилась в наличии этих файлов перед началом выполнения команд и для отслеживания зависимостей между файлами.

Обычно цель представляет собой имя файла, который генерируется в результате работы указанных команд. Целью также может служить название некоторого действия, которое будет выполнено в результате выполнения команд (например, цель clean в make-файлах для компиляции программ обычно удаляет все файлы, созданные в процессе компиляции).

Строки, в которых записаны команды, должны начинаться с символа табуляции.

Рассмотрим несложную программу на Си. Пусть программа program состоит из пары файлов кода — main.c и lib.c, а также из одного заголовочного файла — defines.h, который подключён в обоих файлах кода. Поэтому, для создания program необходимо из пар (main.c defines.h) и (lib.c defines.h) создать объектные файлы main.o и lib.o, а затем скомпоновать их в program. При сборке вручную требуется исполнить следующие команды:

cc -c main.c
cc -c lib.c
cc -o program main.o lib.o

Если в процессе разработки программы в файл defines.h будут внесены изменения, потребуется перекомпиляция обоих файлов и линковка, а если изменим lib.c, то повторную компиляцию main.c можно не выполнять.

Таким образом, для каждого файла, который мы должны получить в процессе компиляции, нужно указать, на основе каких файлов и с помощью какой команды он создаётся. Программа make на основе этих данных выполняет следующее:

  • собирает из этой информации правильную последовательность команд для получения требуемых результирующих файлов;
  • и инициирует создание требуемого файла только в случае, если такого файла не существует, или он старше, чем файлы, от которых он зависит.

Если при запуске make явно не указать цель, то будет обрабатываться первая цель в make-файле, имя которой не начинается с символа «.».

Для программы program достаточно написать следующий make-файл:

program: main.o lib.o
        cc -o program main.o lib.o
main.o lib.o: defines.h

Стоит отметить ряд особенностей. В имени второй цели указаны два файла и для этой же цели не указана команда компиляции. Кроме того, нигде явно не указана зависимость объектных файлов от «*.c»-файлов. Дело в том, что программа make имеет предопределённые правила для получения файлов с определёнными расширениями. Так, для цели-объектного файла (расширение «.o») при обнаружении соответствующего файла с расширением «.c» будет вызван компилятор «сс -с» с указанием в параметрах этого «.c»-файла и всех файлов-зависимостей.

Синтаксис для определения переменных:

переменная = значение

Значением может являться произвольная последовательность символов, включая пробелы и обращения к значениям других переменных. С учётом сказанного, можно модифицировать наш make-файл следующим образом:

OBJ = main.o lib.o
program: $(OBJ)
        cc -o program $(OBJ)
$(OBJ): defines.h

Нужно отметить, что вычисление значений переменных происходит только в момент использования (используется так называемое ленивое вычисление). Например, при сборке цели all из следующего make-файла на экран будет выведена строка «Huh?».

foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
        echo $(foo)

Предположим, что к проекту добавился второй заголовочный файл lib.h, который включается только в lib.c. Тогда make-файл увеличится ещё на одну строчку:

lib.o: lib.h

Таким образом, один целевой файл может указываться в нескольких целях. При этом полный список зависимостей для файла будет составлен из списков зависимостей всех целей, в которых он участвует, создание файла будет производиться только один раз.

Утилита make

Утилита автоматически определяет, какие части крупного
проекта были изменены и должны быть перекомпилированы, а также выполняет действия, необходимые для
этого. Но, на самом деле, область применения make не ограничивается только сборкой программ, так её можно
использовать и для решения других задач, где одни файлы должны автоматически обновляться при изменении
других файлов.

Утилита доступна для разных ОС, и из-за особенностей
выполнения наряду с «родной» реализацией во многих ОС присутствует GNU реализация
, и поведение этих реализаций в некоторых ОС, например,
Solaris может существенно отличаться. Поэтому в сценариях сборки рекомендуется указывать имя конкретной
утилиты. В ОС Linux эти два имени являются синонимами, реализованными через символическую ссылку, как
показано ниже:

$ ls -l /usr/bin/*make
lrwxrwxrwx 1 root root      4 Окт 28  2008 /usr/bin/gmake -> make
-rwxr-xr-x 1 root root 162652 Май 25  2008 /usr/bin/make
...
$ make --version
GNU Make 3.81
...

По умолчанию имя файла сценария сборки — Makefile. Утилита
обеспечивает полную сборку указанной цели, присутствующей в сценарии, например:

$ make
$ make clean

Если цель не указана явно, то выполняется первая последовательная цель в файле
сценария. Также можно указать и любой другой сценарный файл, который будет использоваться для сборки:

$ make -f Makefile.my

Простейший файл Makefile состоит из синтаксических конструкций двух типов: целей и
макроопределений. Описание цели состоит из трех частей: имени цели, списка зависимостей и списка команд
интерпретатора оболочки, требуемых для построения цели. Имя цели — непустой список файлов, которые
предполагается создать. Список зависимостей — список файлов, в зависимости от которых строится цель. Имя
цели и список зависимостей составляют заголовок цели, записываются в одну строку и разделяются двоеточием
(‘:’). Список команд записывается со следующей строки, причем все команды начинаются с
обязательного символа табуляции. Многие текстовые редакторы могут быть настроены таким образом,
чтобы заменять символы табуляции пробелами. Этот факт стоит учесть и проверить, что редактор, в котором
редактируется Makefile, не замещает табуляции пробелами, так как подобная проблема
встречается довольно часто. Любая строка в последовательности списка команд, не начинающаяся с
табуляции (ещё одна команда) или символа ‘#’ (комментарий) — считается завершением
текущей цели и началом новой.

Утилита имеет много внутренних параметров со
значениями по умолчанию, важнейшими из которых являются правила обработки суффиксов, а также
определения внутренних переменных окружения. Эти данные называются базой данных
и могут быть просмотрены следующим образом:

$ make -p >make.suffix
make: *** Не заданы цели и не найден make-файл.  Останов.
$ cat make.suffix
# GNU Make 3.81
# Copyright (C) 2006  Free Software Foundation, Inc.
...
# База данных Make, напечатана Thu Apr 14 14:48:51 2011
...
CC = cc
LD = ld
AR = ar
CXX = g++
COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
COMPILE.C = $(COMPILE.cc)
...
SUFFIXES := .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l .s .S .mod .sym \ 
            .def .h .info .dvi .tex .texinfo .texi .txinfo .w .ch...
# Implicit Rules
...
%.o: %.c
#  команды, которые следует выполнить (встроенные):
        $(COMPILE.c) $(OUTPUT_OPTION) $

Значения всех этих переменных: ,
, ,
, … могут использоваться файлом сценария как
неявные определения со значениями по умолчанию. Кроме этого, можно определить и собственные
правила обработки по умолчанию для выбранных суффиксов (расширений
файловых имён), как это показано на примере выше для исходных файлов кода на языке С: %.c.

Большинство интегрированных сред разработки (IDE) или пакетов для создания переносимых
инсталляций (например, automake или autoconf) ставят своей задачей создание файла Makefile
для утилиты .

Использование

make   ...

Файл ищется в текущем каталоге. Если ключ -f не указан, используется имя по умолчанию для make-файла — Makefile (однако, в разных реализациях make кроме этого могут проверяться и другие файлы, например GNUmakefile).

make открывает make-файл, считывает правила и выполняет команды, необходимые для создания указанной цели.

Стандартные цели для сборки дистрибутивов GNU:

  • all — выполнить сборку пакета;
  • install — установить пакет из дистрибутива (производит копирование исполняемых файлов, библиотек и документации в системные каталоги);
  • uninstall — удалить пакет (производит удаление исполняемых файлов и библиотек из системных каталогов);
  • clean — очистить дистрибутив (удалить из дистрибутива объектные и исполняемые файлы, созданные в процессе компиляции);
  • distclean — очистить все созданные при компиляции файлы и все вспомогательные файлы, созданные утилитой ./configure в процессе настройки параметров компиляции дистрибутива.

По умолчанию make использует самую первую цель в make-файле.

В процессе сборки приложений BSD часто применяют:

depend — выполнить компиляцию/выстраивание зависимостей.

Терминология

  • Файл служит скриптом (рецептом, сценарием) сборки проекта. Обычно один такой файл собирает все исходники в своём каталоге и в подкаталогах, при этом подкаталоги могут содержать, а могут не содержать дочерние файлы . С точки зрения IDE, таких как CLion или Visual Studio, файл также служит проектом, с которым работает программист внутри IDE.
  • В cmake есть “цель” (“target”) — компонент, который следует собрать. Компонент может быть исполняемым файлом, так и статической либо динамической библиотекой.
  • В cmake есть “проект” (“project”) — это набор компонентов, по смыслу похожий на Solution в Visual Studio.
  • В cmake есть “флаги” (flags) — это аргументы командной строки для компилятора, компоновщика и других утилит, вызываемых при сборке.
  • В cmake есть переменные, и в процессе интерпретации файла система сборки cmake вычисляет ряд встроенных переменных для каждой цели, тем самым получая флаги. Затем cmake создаёт вторичный скрипт сборки, который будет напрямую вызывать компилятор, компоновщик и другие утилиты с вычисленными флагами.

замечания

CMake — это инструмент для определения и управления сборками кода, прежде всего для C ++.

CMake — это кросс-платформенный инструмент; идея состоит в том, чтобы иметь единое определение того, как строится проект, — который переводится в конкретные определения построения для любой поддерживаемой платформы.

Это достигается путем сопряжения с различными платформами, специфичными для платформы; CMake — это промежуточный шаг, который генерирует ввод данных для разных конкретных платформ. В Linux CMake генерирует Makefiles; в Windows он может создавать проекты Visual Studio и т. д.

Поведение сборки определяется в файлах — по одному в каждом каталоге исходного кода. Файл каждого каталога определяет, что должна делать система сборки в этом конкретном каталоге. Он также определяет, какие подкаталоги должны обрабатывать CMake.

Типичные действия включают:

  • Создайте библиотеку или исполняемый файл из некоторых исходных файлов в этом каталоге.
  • Добавьте путь к пути include-path, который используется во время сборки.
  • Определите переменные, которые будет использовать buildsystem в этом каталоге, и в его подкаталогах.
  • Создайте файл на основе конкретной конфигурации сборки.
  • Найдите библиотеку, которая находится где-то в исходном дереве.

Окончательные файлы могут быть очень четкими и понятными, поскольку каждый из них настолько ограничен по объему. Каждый из них обрабатывает столько же сборки, сколько присутствует в текущем каталоге.

Для официальных ресурсов на CMake см. Документацию и учебник CMake.

Примеры

edit main.o kbd.o command.o display.o 
    cc -o edit main.o kbd.o command.o display.o
     
main.o main.c defs.h
    cc -c main.c
kbd.o kbd.c defs.h command.h
    cc -c kbd.c
command.o command.c defs.h command.h
    cc -c command.c
display.o display.c defs.h
    cc -c display.c

clean
     rm edit main.o kbd.o command.o display.o

2. Использование действий по умолчанию.

#default target - file edit 
edit  main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o
         cc -o edit main.o kbd.o command.o display.o \ 
                    insert.o search.o files.o utils.o 

main.o  main.c defs.h
        cc -c main.c 
kbd.o  kbd.c defs.h command.h
        cc -c kbd.c
command.o  command.c defs.h command.h 
        cc -c command.c 
display.o  display.c defs.h buffer.h 
        cc -c display.c
insert.o  insert.c defs.h buffer.h 
        cc -c insert.c 
search.o  search.c defs.h buffer.h 
        cc -c search.c 
files.o  files.c defs.h buffer.h command.h 
        cc -c files.c 
utils.o  utils.c defs.h 
        cc -c utils.c
clean  
       rm edit main.o kbd.o command.o display.o \ 
          insert.o search.o files.o utils.o

По умолчанию, make начинает с первого правила (не считая правил, имена целей у которых начинаются с ‘.’). Это называется главной целью по умолчанию. В нашем случае это правило edit. Если файл edit новее чем объектные файлы, от которых он зависит, то ничего не произойдет. В противном случае, прежде чем make сможет полностью обработать это правило, он должен рекурсивно обработать правила для файлов, от которых зависит edit. Каждый из этих файлов обрабатывается в соответствии со своим собственным правилом. Перекомпеляция должна быть проведена, если исходный файл или любой из заголовочных файлов, упомянутых среди зависимостей, обновлен позднее, чем объектный файл, или если объектный файл не существует.
Правилу clean не соответствует никакого создаваемого файла и, соответственно, clean ни от чего не зависит и само не входит в список зависимостей. При запуске по умолчанию clean вызываться не будет. Для его выполнения необходимо явно указать цель при запуске .

3. Для сокращения записи можно использовать переменные и действия по умолчанию (неявные правила)

objects = main.o kbd.o command.o display.o \ 
          insert.o search.o files.o utils.o 

edit  $(objects) 
        cc -o edit $(objects) 
main.o  defs.h 
kbd.o  defs.h command.h 
command.o  defs.h command.h 
display.o  defs.h buffer.h
insert.o  defs.h buffer.h
search.o  defs.h buffer.h
files.o  defs.h buffer.h command.h
utils.o  defs.h 
.PHONY  clean 
clean 
        -rm edit $(objects)

Переменная objects позволила использовать единожды написанный список объектных файлов, а для объектных файлов в make встроено неявное правило по умолчанию

file.c file.o   cc -c file.c

4. Специальная цель .PHONY является встроенной в make и определяет свои зависимости как цели-имена, которым нет соответствия в виде файлов. Если данное правило пропустить, то создание в текущем каталоге файла с именем clean заблокирует выполнение make clean.
Использование правил по умолчанию позволяет изменить стиль записей зависимостей:

objects = main.o kbd.o command.o display.o \ 
          insert.o search.o files.o utils.o 

edit  $(objects) 
       cc -o edit $(objects) 

$(objects)  defs.h 
kbd.o command.o files.o  command.h 
display.o insert.o search.o files.o  buffer.h

Данная запись указывает, что все объектные файлы зависят от заголовочного файла defs.h, но для некоторых из них проверяются дополнительные зависимости.

Где должен находится CMakeLists.txt?

Очень удобно в корне каждого проекта иметь директорию build с файлами сборки проекта, причем имя файла во всех директориях должно называться одинаково (название по умолчанию CMakeLists.txt отлично подойдет). Это позволит собирать сложные проекты рекурсивно подключая директории с подпроектами (о подключении подпроектов к проекту можно прочесть в моей заметке).

Создавать отдельную директорию, а не хранить файл в корне удобно, как из соображений четко организованной структуры проекта, так и потому, что CMake при работе создает ряд файлов, которые удобно хранить отдельно, чтобы не захламлять проект.

Загрузка и установка Qt

Загрузка и запуск Qt Installer

Проскрольте открывшуюся страницу вниз. В правой части экрана внизу вы увидите надпись «Downloads for open source users», а под ней — кнопку Go open source. Жмите на неё.

Далее вас перенаправит на страницу загрузки онлайн-установщика Qt. Найдите на ней кнопку Download и нажмите на неё

Подождите немного, пока в нижней части экрана не появится сообщение

Теперь смело можете нажать на кнопку Выполнить.

Установка Qt

Перед вами окно приветствия онлайн-установщика Qt. Здесь вы можете ознакомиться с условиями использования Qt, узнать какую лучше выбрать лицензию для вашей программы на Qt, а также ознакомиться с информацией по дополнительным платным услугам и возможностях аккаунта Qt. Если вам это не интересно, сразу жмите кнопку Next.

Создание аккаунта Qt

В следующем окне вам предложат создать аккаунт Qt. Процедура стандартная. Просто введите свой адрес электронной почты, придумайте произвольный пароль и подтверждение. На указанный адрес будет направлено письмо для верификации аккаунта. Если вы планируете связать свою дальнейшую жизнь с Qt, то я настоятельно рекомендую это сделать. Потому как это даст вам возможность общаться с другими Qt-разработчиками, в том числе и создателями самого Qt. Qt как любая достаточно сложная программная система не лишена недостатков и особенностей. Поэтому всегда хорошо иметь возможность пообщаться с опытными специалистами, чтобы сэкономить время в попытках самостоятельно разобраться с возникшей у вас проблемой.

Вы также можете пропустить (Skip) этот шаг сейчас, а затем при необходимости можете сделать это самостоятельно, зайдя на страницу Qt-форума. При попытке войти на него вас попросят ввести данные аккаунта или зарегистрироваться (кнопка Sign in). К тому же вы можете воспользоваться этой ссылкой для регистрации нового аккаунта.

Разрешение на сбор анонимной статистики


Теперь Qt Installer запрашивает ваше разрешение на сбор анонимной статистики для улучшения работы Qt в следующих версиях. Я выбираю опцию Help us make Qt Creator even better, потому как заинтересован в улучшении данного инструмента разработки и жму кнопку Далее.

На данном этапе вы должны указать путь установки Qt. На вашем диске должно быть как минимум 10 GB свободного места, потому как в дальнейшем потребуется устанавливать дополнительные компоненты, необходимые для работы с Android. И ещё один момент. Если вы используете SSD диски совместно со стандартными магнитными накопителями, то будет гораздо лучше, если вы выберете именно SSD носитель для хранения файлов Qt и Android, особенно если вы планируете в дальнейшем заняться разработкой больших проектов, которые как правило собираются достаточно долго. Использование SSD диска позволит вам ускорить процесс компиляции вашего проекта и существенно сократить по времени основной цикл вашего рабочего процесса. В дальнейшем я расскажу вам какие ещё методы можно использовать, чтобы сделать это.

Выбор компонентов для установки. Архитектуры Android

Следующим этапом нам предстоит выбрать набор компонент Qt для установки. Так как мы хотим разрабатывать Android-приложения на Qt нам необходимо выбрать все пункты содержащие в заголовке слово Android. На текущий момент доступны 3 версии архитектур Android:

Более подробно о каждой из архитектур можно прочесть на официальном сайте для Android-разработчиков пользуясь этой ссылкой. Следует отметить, что при необходимости компоненты можно будет добавить в любой момент через утилиту Qt Installer, которую будет можно найти на Панели управления Windows в пункте Программы и компоненты, так что особо не заморачивайтесь сейчас по этому поводу и жмите Далее.

Подтверждение лицензионного соглашения

Итак осталось подтвердить ваше согласие с лицензией Qt (отметьте галочку I have read) и начать процесс установки (жмём кнопки Далее, Далее, Установить). Процесс установки может занять около 10 — 15 минут, так что это повод отвлечься от компьютера и заняться чем-нибудь более полезным. Например, помечтать о светлом будущем или обнять любимого человека.

Ссылка на основную публикацию