Здравствуйте, уважаемые читатели!
Предисловие от автора сайта.
Данная статья представляет собой более-менее “читабельный” перевод раздела 4.6 “Build system” ESP-IDF Programming Guide на декабрь 2024 г.
Рекомендуется к прочтению всем, кто хочет создавать приложения (прошивки) для ESP32 и ESP-IDF, в том числе и с помощью PlatformIO. Из данного раздела вам станет ясно-понятно:
- как работает система сборки проектов ESP-IDF
- как создать и настроить новый проект ESP-IDF
- что за файлы CMakeList.txt, для чего они нужны, и как правильно заполняются
- как подключить сторонние библиотеки (в терминах ESP-IDF – компоненты) к проекту
- как подключить к проекту или компоненту двоичные или текстовые файлы (например PEM)
- как правильно настроить зависимости библиотек друг от друга
- и многое другое
В этом документе объясняется реализация системы сборки ESP-IDF и концепция «components». Вам следует прочесть этот документ, если хотите узнать, как создать и настроить новый проект или компонент ESP-IDF.
Обзор
Проект ESP-IDF можно рассматривать как объединение ряда отдельных составляющих. Например, для веб-сервера на ESP32, который показывает в вашем браузере текущую влажность, это могут быть:
- Базовые библиотеки ESP-IDF (libc, привязки к GPIO, таймеры и т. д.)
- Драйвер Wi-Fi
- Стек TCP/IP
- Операционная система FreeRTOS
- Веб-сервер HTTP SERVER
- Драйвер для датчика влажности
- Основной прикладной код, связывающий все это воедино
ESP-IDF делает все эти компоненты явными и настраиваемыми. При компиляции проекта система сборки будет искать необходимые компоненты в каталогах ESP-IDF, каталогах проекта и (опционально) в дополнительных пользовательских каталогах компонентов. Кроме этого, она позволяет пользователю настраивать проект ESP-IDF, включая каждый используемый компонент, с помощью текстовой системы меню. После настройки компонентов в проекте система сборки скомпилирует проект.
Концепции
- “Проект (Project)” — это каталог, содержащий все файлы и параметры для создания одного «приложения» (исполняемого файла), а также дополнительные файлы, такие как таблица разделов, разделы данных/файловой системы и загрузчик.
- “Конфигурация проекта (Project configuration)” хранится в одном файле, который называется sdkconfig и находится в корневом каталоге проекта. Этот файл конфигурации должен изменяться не “вручную”, а с помощью команды (утилиты) для настройки конфигурации проекта –
idf.py menuconfig
(pio run -t menuconfig
для PlatformIO). Один проект может содержать только одну конфигурацию проекта. - “Приложение (App)” — это исполняемый файл, созданный с применением ESP-IDF. Один проект обычно создает сразу два приложения — «приложение проекта» (основной исполняемый файл, т. е. ваша кастомная прошивка) и «приложение загрузчика» (начальная программа загрузчика, которая запускает приложение проекта).
- “Компоненты (Components)” — это модульные части автономного кода, которые компилируются в статические библиотеки (файлы .a) и связываются с приложением. То есть другими словами – библиотеки. Некоторые из них предоставляются самим ESP-IDF, другие могут быть получены от сторонних авторов или созданы Вами.
- «Цель (Target)» — это оборудование (серия чипа ESP32 – примечание перев.), для которого было создано Ваше приложение. Полный список поддерживаемых серий в вашей версии ESP-IDF можно увидеть, запустив команду
idf.py --list-targets
.
Некоторые объекты не являются частью проекта:
- Фреймворк ESP-IDF не является частью вашего проекта. Он является автономным набором компонентов и связан с проектом только через переменную окружения
IDF_PATH
, которая содержит путь к каталогу ESP-IDF. Фреймворк ESP-IDF предоставляет для вашего проекта набор общих компонент и API. - Набор инструментов toolchain для компиляции также не является частью проекта. Набор инструментов должен быть предварительно установлен до сборки, как правило это делается вместе с установкой ESP-IDF.
Использование системы сборки
Основным компонентом для сборки проекта является python-утилита idf.py
для командной строки, которая предоставляет интерфейс для легкого управления сборкой проекта. В свою очередь, она управляет следующими инструментами:
- Система сборки CMake, которая настраивает проект для сборки
- Небольшая система сборки Ninja, которая собирает проект
- Скрипт esptool.py для прошивки микроконтроллера.
Подробнее о настройке системы сборки можно прочитать здесь.
Использование CMake напрямую
Скрипт idf.py
— это скрипт-оболочка вокруг CMake, созданная для Вашего удобства. Однако Вы также можете вызывать CMake и напрямую, если желаете.
Когда idf.py
что-то выполняет, он выводит каждую команду, которую он запускает, в терминал для наглядности. Например, idf.py build
будет представлять собой последовательность команд:
mkdir -p build cd build cmake .. -G Ninja # or 'Unix Makefiles' ninja
В приведенном выше списке команда cmake настраивает проект и генерирует файлы сборки для использования с финальным инструментом сборки ninja, запуск ninja фактически собирает проект.
Не обязательно запускать cmake больше одного раза. После первой сборки вам нужно будет каждый раз обязательно запускать только ninja. Ninja автоматически повторно вызовет cmake, если Вашему проекту потребуется перенастройка.
Если использовать CMake с ninja или make, Вам так же будут доступны большинство idf.py
подкоманд. Например, команды make menuconfig
или ninja menuconfig
в каталоге проекта будет работать так же, как и idf.py menuconfig
.
Если вы уже знакомы с CMake, система сборки на основе ESP-IDF CMake может показаться вам необычной, поскольку она скрывает большую часть функциональности CMake на основе своего шаблонного кода. См. раздел “Написание чистых компонентов CMake” данной статьи для получения дополнительной информации о создании компонентов в стиле CMake.
Прошивка ESP с помощью Ninja или Make
Можно собрать проект и прошить напрямую из ninja или make, запустив соответствующую команду, например:
ninja flash
или
make app-flash
Доступные опции: flash
, app-flash
(только приложение), bootloader-flash
(только загрузчик).
При прошивке таким способом, необходимо предварительно установить переменные среды ESPPORT
и ESPBAUD
для указания последовательного порта и скорости передачи данных. Вы можете установить переменные среды в вашей операционной системе или проекте IDE. Или установите их непосредственно в командной строке:
ESPPORT=/dev/ttyUSB0 ninja flash
Установка переменных окружения в начале команды, как это представлено в примере выше, является синтаксисом оболочки Bash. Это будет работать только в Linux и macOS. Это не будет работать при использовании командной строки Windows, но это будет работать при использовании оболочек, подобных Bash, и в Windows.
или
make -j3 app-flash ESPPORT=COM4 ESPBAUD=2000000
Предоставление переменных в конце командной строки является make
синтаксисом и работает с make
на всех платформах.
Использование CMake в IDE
Вы также можете использовать любую удобную вам IDE с интеграцией CMake. Для работы с проектом из IDE ей потребуется “знать” путь к файлу CMakeLists.txt проекта. IDE с интеграцией CMake часто предоставляют собственные инструменты сборки (CMake называет их «генераторами») для сборки исходных файлов.
При добавлении в IDE пользовательских команд, не связанных непосредственно со сборкой, таких как, например, «flash», рекомендуется выполнить эти «специальные» команды через утилиту idf.py
.
Более подробную информацию об интеграции ESP-IDF с CMake в IDE см. в разделе “Метаданные системы сборки” данной статьи.
Настройка интерпретатора Python
ESP-IDF хорошо работает с Python версии 3.8+.
idf.py
и другие скрипты Python будут работать с интерпретатором Python по умолчанию, т. е. псевдонимом python
. Вы можете переключиться на другой псевдоним, например python3 $IDF_PATH/tools/idf.py ...
, или настроить псевдоним оболочки или другой скрипт для упрощения команды.
При непосредственном использовании CMake запуск cmake -D PYTHON=python3 ...
приведет к тому, что CMake переопределит интерпретатор Python по умолчанию. При использовании IDE с CMake установка значения PYTHON
в качестве переопределения в пользовательском интерфейсе IDE приведет к переопределению интерпретатора Python по умолчанию.
Для более общего управления версией Python через командную строку, ознакомьтесь с инструментами pyenv или virtualenv . Они позволяют изменить версию Python по умолчанию.
Пример проекта
Пример дерева каталогов и файлов проекта может выглядеть следующим образом (каталоги оканчиваются на / – прим. переводчика):
- myProject/ - CMakeLists.txt - sdkconfig - bootloader_components/ - boot_component/ - CMakeLists.txt - Kconfig - src1.c - components/ - component1/ - CMakeLists.txt - Kconfig - src1.c - component2/ - CMakeLists.txt - Kconfig - src1.c - include/ - component2.h - main/ - CMakeLists.txt - src1.c - src2.c - build/
В этом примере «myProject» содержатся следующие элементы:
- Файл проекта верхнего уровня CMakeLists.txt. Это основной файл, который CMake использует для понимания того, как собирать проект; в нем можно задавать переменные CMake для всего проекта. Он включает файл /tools/cmake/project.cmake , который реализует остальную часть системы сборки. Наконец, он устанавливает имя проекта и определяет сам проект.
- Файл конфигурации проекта sdkconfig. Этот файл создается или обновляется при запуске команды
idf.py menuconfig
и содержит конфигурацию для всех компонентов проекта, включая сам ESP-IDF и пользовательские настройки (опционально). Файл sdkconfig может быть добавлен в систему управления исходным кодом проекта. Не стоит изменять этот файл “вручную” с помощью текстового редактора или редактора кода. - Необязательный каталог bootloader_components содержит компоненты, которые необходимо скомпилировать и связать внутри проекта загрузчика. Проект не обязательно должен содержать кастомный загрузчик и пользовательские компоненты загрузчика такого рода; но он может быть полезен в случае, если загрузчик необходимо модифицировать для внедрения новых функций.
- Необязательный каталог components содержит локальные компоненты (библиотеки), которые являются частью проекта. Проект не обязательно должен содержать пользовательские компоненты, но он может быть полезен для структурирования повторно используемого кода или включения сторонних компонентов, которые не являются частью ESP-IDF. В качестве альтернативы можно использовать параметр
EXTRA_COMPONENT_DIRS
в CMakeLists.txt верхнего уровня для поиска сторонних компонентов в других каталогах. - Каталог main — это специальный компонент, содержащий исходный код для самого проекта. “main” — это имя по умолчанию, определенное в переменной
COMPONENT_DIRS
, но вы можете переопределить эту переменную. Для получения дополнительной информации см. ниже раздел “Переименования main компонента”. Если в вашем проекте много исходных файлов, мы рекомендуем сгруппировать большинство из них в компоненты, а не помещать их все в “main”. - Каталог build — это каталог, где создаются результирующие файлы при сборке проекта. Этот каталог создается утилитой
idf.py
автоматически, если он еще не существует на момент её запуска. CMake настраивает проект и генерирует все промежуточные файлы сборки в этом каталоге. Затем, после запуска основного процесса сборки, этот каталог также будет содержать промежуточные объектные файлы и библиотеки, а также окончательные двоичные файлы прошивки. Этот каталог не стоит добавлять в систему управления исходным кодом (например git) и не распространять с исходным кодом проекта.
Каждый каталог компонента также содержит файл компонента CMakeLists.txt. Этот файл содержит определения переменных для управления процессом сборки компонента и его интеграцией в общий проект. Подробнее см. ниже в разделе “Файлы CMakeLists компонентов”.
Каждый компонент может также (но не обязательно) включать файл Kconfig, определяющий параметры конфигурации данного компонента, которые можно задать через общее меню конфигурации menuconfig. Некоторые компоненты могут также включать Kconfig.projbuild и project_include.cmake файлы, которые являются специальными файлами для переопределения некоторых частей проекта.
Файл CMakeLists проекта
Каждый проект имеет один файл CMakeLists.txt верхнего уровня, который содержит настройки сборки для всего проекта в целом. По умолчанию CMakeLists проекта может быть довольно небольшим.
Минимальный пример CMakeLists проекта
Минимальный файл CMakeLists.txt проекта выглядит следующим образом:
cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(myProject)
Включение этих трех строк в указанном выше порядке необходимо для каждого проекта:
cmake_minimum_required(VERSION 3.16)
сообщает CMake минимальную версию, необходимую для сборки проекта. ESP-IDF предназначен для работы с CMake 3.16 или более новой версии. Эта строка должна быть первой в файле CMakeLists.txt.include($ENV{IDF_PATH}/tools/cmake/project.cmake)
подключает основную функциональность CMake для настройки проекта, обнаружения всех стандартных компонентов ESP-IDF и т. д.project(myProject)
создает сам проект и указывает имя проекта. Имя проекта используется для конечных двоичных выходных файлов приложения – т.е. myProject.elf, myProject.bin. Для одного файла CMakeLists может быть определен только один проект.
Дополнительные переменные проекта
Кроме перечисленных переменных имеются и некоторые другие. Все указанные ниже переменные имеют значения по умолчанию, которые можно переопределить для изменения поведения при сборке. Более детально узнать об этом можно по ссылке /tools/cmake/project.cmake.
COMPONENT_DIRS
: Каталоги для поиска компонентов. По умолчанию они включают IDF_PATH/components, PROJECT_DIR/components, иEXTRA_COMPONENT_DIRS
. Переопределите эту переменную, если вы не хотите искать компоненты в этих каталогах.EXTRA_COMPONENT_DIRS
: Необязательный список дополнительных каталогов для поиска компонентов. Пути могут быть абсолютными или относительно каталога проекта.COMPONENTS
: Список имен компонентов для сборки в проект. По умолчанию это будут все компоненты, найденные вCOMPONENT_DIRS
каталогах. Используйте эту переменную, чтобы «удалить» лишние компоненты из проекта для ускорения сборки. Обратите внимание, что любой компонент, который «требует» другой компонент через аргументыREQUIRES
илиPRIV_REQUIRES
при регистрации компонента, автоматически добавится в этот список, поэтому заданный списокCOMPONENTS
может быть очень небольшим.BOOTLOADER_IGNORE_EXTRA_COMPONENT
: Список компонентов, помещенных в bootloader_components/, которые должны игнорироваться компиляцией загрузчика. Используйте эту переменную, если какой-либо компонент загрузчика должен быть условно включен в проект.
Любые пути в этих переменных могут быть абсолютными или устанавливаться относительно каталога проекта.
Для установки этих переменных используйте команду cmake set, например set(VARIABLE "VALUE")
. Команды следует размещать после строки cmake_minimum(…), но перед строкой include(…).
Переименование main компонента
Система сборки использует особый подход к main компоненту. Это компонент, который автоматически добавляется в сборку при условии, что он находится в ожидаемом месте – PROJECT_DIR/main. Все остальные компоненты в сборке также добавляются в качестве его зависимостей, избавляя пользователя от ручного поиска зависимостей и предоставляя сборку, которая работает “из коробки”. Переименование компонента main
приводит к потере этой скрытой работы, выполняемой “за кулисами”, что потребует от пользователя указания местоположения недавно переименованного компонента и ручного указания его всех зависимостей. Но если вы таки желаете переименовать main, то Ваши шаги будут примерно следующие:
- Переименовать main каталог.
- Установите переменную
EXTRA_COMPONENT_DIRS
в головном CMakeLists.txt проекта для включение переименованного main каталога. - Укажите зависимости в файле CMakeLists.txt переименованного компонента с помощью аргументов
REQUIRES
илиPRIV_REQUIRES
при регистрации нового компонента.
Переопределение спецификаций сборки по умолчанию
Сборка устанавливает некоторые глобальные опции сборки (флаги компиляции, определения и т. д.), которые используются при компиляции всех исходных кодов из всех включаемых в проект компонентов.
Например, одна из опций сборки по умолчанию — это опция компиляции -Wextra
. Предположим, пользователь хочет использовать переопределение этого с помощью -Wno-extra
, тогда это должно быть сделано после строки project()
:
cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(myProject) idf_build_set_property(COMPILE_OPTIONS "-Wno-error" APPEND)
Это гарантирует, что параметры компиляции, заданные пользователем, не будут переопределены спецификациями сборки по умолчанию, поскольку последние задаются внутри project().
Файлы CMakeLists компонента
Каждый проект содержит один (как минимум main) или несколько компонентов. Компонентом является любой каталог из списка COMPONENT_DIRS
, содержащий свой файл CMakeLists.txt. Компоненты могут быть частью ESP-IDF, частью собственного каталога компонентов проекта или добавляться из пользовательских каталогов компонентов (см. выше).
Поиск компонентов
Список каталогов COMPONENT_DIRS
просматривается по очереди для поиска включаемых компонентов проекта. Каталоги в этом списке могут быть либо самими компонентами (т.е. они содержат файл CMakeLists.txt); либо они могут быть каталогами верхнего уровня, подкаталоги которых являются компонентами.
Когда CMake запускается для настройки проекта, он выводит в лог перечень компонентов, включенных в сборку. Этот список может быть полезен для отладки включения или исключения определенных компонентов.
Несколько компонентов с одинаковым именем
Когда ESP-IDF собирает все компоненты для компиляции, он делает это в порядке, указанном в COMPONENT_DIRS
. По умолчанию это означает использование вначале внутренних компонентов ESP-IDF (IDF_PATH/components), затем добавляются компоненты в каталогах, указанных в EXTRA_COMPONENT_DIRS
, и, наконец, компоненты проекта (PROJECT_DIR/components). Если два или более из этих каталогов содержат подкаталоги компонентов с одинаковым именем, используется компонент в последнем месте поиска. Это позволяет, например, переопределить стандартные компоненты ESP-IDF измененной пользовательской версией, скопировав этот компонент из каталога компонентов ESP-IDF в каталог компонентов проекта и затем изменив его там. При таком использовании сам каталог ESP-IDF будет оставаться нетронутым.
Если компонент переопределяется в существующем проекте путем перемещения его в новое место, проект не “увидит” новый путь к компоненту автоматически. Выполните команду idf.py reconfigure
(или удалите папку сборки проекта), а затем выполните сборку вновь.
Минимальный CMakeLists для компонента
Минимальный CMakeLists.txt файл компонента просто регистрирует компонент в системе сборки с помощью idf_component_register
:
idf_component_register(SRCS "foo.c" "bar.c" INCLUDE_DIRS "include" REQUIRES mbedtls)
SRCS
— включает в себя список исходных файлов (с расширениями *.c, *.cpp, *.cc, *.S). Эти исходные файлы будут скомпилированы в статическую библиотеку.INCLUDE_DIRS
— определяет список каталогов для добавления в глобальный путь поиска включений для данного компонента, в которых содержаться также основные исходные файлы. Каталоги обычно указываются относительно самого CMakeLists.txt файла, хотя могут быть и абсолютными.REQUIRES
задает зависимости данного компонента. На самом деле эта строка не обязательна, но очень часто желательно её объявить, какие еще сторонние компоненты необходимы данному компоненту для сборки. См. раздел “Зависимости компонентов”.
В результате будет создана библиотека с именем компонента, которая будет связана с создаваемым приложением.
Есть и другие аргументы, которые можно передать idf_component_register
. Эти аргументы обсуждаются ниже. Более полные примеры компонентов см. в примерах определения зависимостей компонентов и примерах CMakeLists.txt ниже.
Предварительно заданные переменные компонента
Следующие переменные, специфичные для каждого компонента, доступны для использования внутри CMakeLists компонента, но их не следует как-либо переопределять или изменять:
COMPONENT_DIR
: Каталог компонента. Оценивается как абсолютный путь к каталогу, содержащему CMakeLists.txt. Путь компонента не может содержать пробелов. Это то же самое, что и переменнаяCMAKE_CURRENT_SOURCE_DIR
.COMPONENT_NAME
: Имя компонента. То же, что и имя каталога, в котором находится компонент.COMPONENT_ALIAS
: Псевдоним библиотеки, созданный внутри системы сборки компонента.COMPONENT_LIB
: Имя библиотеки, созданное внутри системы сборки компонента.
Следующие переменные задаются на уровне проекта, но доступны для использования в компоненте CMakeLists:
CONFIG_*
: Параметры конфигурации компонента. Каждое значение в файле конфигурации проекта имеет соответствующую переменную, доступную в том числе и для cmake. Все имена параметров конфигурации начинаются с CONFIG_. Подробнее здесь.ESP_PLATFORM
: Устанавливается на 1, если файл CMake обрабатывается в системе сборки ESP-IDF.
Переменные сборки (проекта)
Ниже приведены некоторые переменные проекта, которые доступны как свойства сборки и значения которых можно получить с помощью директивы idf_build_get_property
компонента CMakeLists.txt:
PROJECT_NAME
: Имя проекта, как указано в файле проекта CMakeLists.txt.PROJECT_DIR
: Абсолютный путь к каталогу проекта, содержащему проект CMakeLists. То же, что и переменнаяCMAKE_SOURCE_DIR
.COMPONENTS
: Имена всех компонентов, включенных в эту сборку, отформатированные как список CMake, разделенный точкой с запятой.IDF_VER
: Git-версия ESP-IDF (созданная с помощью git describe)IDF_VERSION_MAJOR
,IDF_VERSION_MINOR
,IDF_VERSION_PATCH
: Компоненты версии ESP-IDF, которые будут использоваться в условных выражениях. Обратите внимание, что эта информация этих переменных менее точна, чем предоставляемая переменнойIDF_VER
: версии v4.0-dev-*, v4.0-beta1, v4.0-rc1 и v4.0 – все будут иметь одинаковые значения переменныхIDF_VERSION_*
, но разные значенияIDF_VER
.IDF_TARGET
: Название цели, для которой создается проект (в общем случае это серия чипа ESP).PROJECT_VER
: Версия проекта. Она может быть определена несколькими способами:- Если задан параметр CONFIG_APP_PROJECT_VER_FROM_CONFIG, будет использоваться значение переменной CONFIG_APP_PROJECT_VER.
- В противном случае, если переменная
PROJECT_VER
задана в файле проекта CMakeLists.txt, то будет использовано ее значение. - В противном случае, если существует файл PROJECT_DIR/version.txt, то его содержимое будет использовано как
PROJECT_VER
. - В противном случае, если вызову
project()
был передан аргументVERSION
в файле CMakeLists.txt, то он будет использоваться какPROJECT_VER
. АргументVERSION
должен соответствовать стандарту cmake:project(... VERSION x.y.z.w)
- В противном случае, если проект находится внутри репозитория Git, будет использован вывод git description.
- В противном случае
PROJECT_VER
будет равен «1».
EXTRA_PARTITION_SUBTYPES
: Список дополнительных подтипов разделов. Описание каждого подтипа — это строка в формате с разделением запятыми: type_name, subtype_name, numeric_value. Компоненты могут добавлять новые подтипы, добавляя их в этот список.
Управление компиляцией компонентов
Чтобы передать параметры компилятора при компиляции исходных файлов, принадлежащих определенному компоненту, используйте функцию target_compile_options:
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-variable)
Чтобы применить флаги компиляции к одному исходному файлу, используйте команду CMake set_source_files_properties:
set_source_files_properties(mysrc.c PROPERTIES COMPILE_FLAGS -Wno-unused-variable )
Это может быть полезно, если имеется какой-либо код вышестоящего уровня, который выдает предупреждения.
Примечание. Команда CMake set_source_files_properties неприменима, если исходные файлы были добавлены с помощью переменной SRC_DIRS
в idf_component_register
. Подробнее см. в разделе “Подстановка файлов и инкрементальные сборки”.
При использовании этих команд размещайте их после вызова idf_component_register
в файле компонента CMakeLists.
Конфигурация компонента
Каждый компонент наряду с CMakeLists.txt, может (но не обязательно должен) иметь файл Kconfig. Он содержит параметры конфигурации для добавления в общее меню конфигурации, специфичные для этого компонента. Эти настройки находятся в меню «Настройки компонента» при запуске menuconfig.
Чтобы создать файл Kconfig для вашего компонента, проще всего начать с одного из файлов Kconfig, поставляемых вместе с ESP-IDF. Пример см. в разделе “Добавление условной конфигурации”.
Определения препроцессора
Система сборки ESP-IDF добавляет следующие определения препроцессора C в командную строку:
ESP_PLATFORM
: Может использоваться для обнаружения того, что сборка происходит в ESP-IDF.IDF_VER
: Определено как строка версии git. Например v2.0, для помеченного релиза или v1.0-275-g0efaa4f для произвольного коммита.
Зависимости компонентов
При компиляции каждого компонента система сборки ESP-IDF рекурсивно оценивает его зависимости. Это означает, что каждый компонент должен явно объявить компоненты, от которых непосредственно зависит («requires») его работа и компиляция.
Объявление зависимостей при регистрации компонента
idf_component_register(... REQUIRES mbedtls PRIV_REQUIRES console spiffs)
REQUIRES
в этот список должны быть включены все компоненты, на которые ссылаются (через#included
) публичные заголовочные header-файлы для данного компонента.PRIV_REQUIRES
в этот список следует включить любые задействованные#included
компоненты в любых других файлах (кроме заголовочных), но если они уже не были включены вREQUIRES
. Здесь также можно указать любые субкомпоненты, которые требуются для правильной работы вашего компонента.- Значения
REQUIRES
иPRIV_REQUIRES
не должны зависеть от каких-либо параметров конфигурации (CONFIG_xxx макросов). Это связано с тем, что зависимости обрабатываются расширяются до загрузки конфигурации. Другие переменные компонентов (например, пути включения или исходные файлы) могут зависеть от конфигурации. - Вполне допускается не устанавливать ни одну, ни обе
REQUIRES
переменные. Например если у компонента нет особых зависимостей, кроме тех, что перечислены в разделе “Общедоступные компоненты ESP-IDF” – RTOS, libc и т.д.
Если компонент поддерживает только некоторые целевые чипы (значения IDF_TARGET
), то он может указать REQUIRED_IDF_TARGETS
при вызове idf_component_register
, чтобы указать данные аппаратные зависимости. В этом случае система сборки выдаст ошибку, если компонент будет включен в сборку с неподдерживаемым IDF_TARGET
.
Примечание. В терминах CMake REQUIRES
и PRIV_REQUIRES
– это приблизительные обертки вокруг функций CMake target_link_libraries(... PUBLIC ...)
и target_link_libraries(... PRIVATE ...)
Пример системы зависимостей компонентов
Представьте себе, что есть компонент car, который использует компонент engine, который использует компонент spark_plug:
- autoProject/ - CMakeLists.txt - components/ - car/ - CMakeLists.txt - car.c - car.h - engine/ - CMakeLists.txt - engine.c - include/ - engine.h - spark_plug/ - CMakeLists.txt - spark_plug.c - spark_plug.h
Компонент “Автомобиль”
Заголовочный файл car.h — это открытый интерфейс для компонента car. Этот заголовочный файл включает engine.h напрямую, поскольку он использует некоторые объявления из engine.h:
/* car.h */ #include "engine.h" #ifdef ENGINE_IS_HYBRID #define CAR_MODEL "Hybrid" #endif
А car.c также включает в себя car.h:
/* car.c */ #include "car.h"
Это означает, что в файле car/CMakeLists.txt необходимо указать, что car зависит от engine:
idf_component_register(SRCS "car.c" INCLUDE_DIRS "." REQUIRES engine)
SRCS
задает список исходных файлов в компоненте car.INCLUDE_DIRS
задает список общедоступных каталогов include для этого компонента. Поскольку общедоступный интерфейс car.h расположен в текущем каталоге, то указываем “.”.REQUIRES
задает список компонентов, требуемых публичным интерфейсом этого компонента для сборки. Поскольку car.h является публичным заголовочным файлом и включает заголовок из engine, мы указываем его здесь. Это гарантирует, что любой другой компонент, который включает car.h сможет также рекурсивно включить требуемый engine.h.
Компонент “Двигатель”
Компонент engine также имеет публичный заголовочный файл include/engine.h, но его заголовочный файл немного проще:
/* engine.h */ #define ENGINE_IS_HYBRID void engine_start(void);
Реализация находится в engine.c:
/* engine.c */ #include "engine.h" #include "spark_plug.h" ...
В этом компоненте engine зависит от spark_plug, но это частная зависимость (определенная не в заголовочном файле). spark_plug.h необходим для компиляции engine.c, но не требуется для включения в engine.h.
Это означает, что в файле engine/CMakeLists.txt можно использовать PRIV_REQUIRES
:
idf_component_register(SRCS "engine.c" INCLUDE_DIRS "include" PRIV_REQUIRES spark_plug)
В результате исходные файлы в компоненте car не нуждаются в добавлении каталогов include spark_plug в их путь поиска компилятора. Это может ускорить компиляцию и не даст строкам команд компилятора стать длиннее, чем необходимо.
Компонент “Свеча зажигания”
Компонент spark_plug не зависит ни от чего другого. Он имеет публичный заголовочный файл spark_plug.h, но он не включает заголовки от других компонентов.
Это означает, что файлу spark_plug/CMakeLists.txt не нужны никакие предложения REQUIRES
или PRIV_REQUIRES
:
idf_component_register(SRCS "spark_plug.c" INCLUDE_DIRS ".")
Включение каталогов компонентов
Исходные файлы каждого компонента могут компилироваться с указанием дополнительных include каталогов, если это указано в переданных аргументах idf_component_register
:
idf_component_register(.. INCLUDE_DIRS "include" PRIV_INCLUDE_DIRS "other")
- Текущие компоненты включают
INCLUDE_DIRS
иPRIV_INCLUDE_DIRS
. - Включаются все
INCLUDE_DIRS
всех других компонентов, перечисленных в параметрахREQUIRES
иPRIV_REQUIRES
(т. е. все публичные и частные зависимости текущего компонента). - Рекурсивно, все списки
INCLUDE_DIRS
этих компонентовREQUIRES
(т.е. все публичные зависимости зависимостей этого компонента, расширенные рекурсивно).
Зависимости основного компонента
Компонент с именем main является особенным, поскольку он автоматически включает все остальные компоненты в сборке. Поэтому нет необходимости передавать ему какие-либо REQUIRES
или PRIV_REQUIRES
. См. раздел “Переименование main компонента” для описания того, что нужно изменить, если компонент main больше не используется.
Общедоступные компоненты ESP-IDF
Чтобы избежать дублирования, каждый компонент автоматически может включать некоторые “общие” компоненты ESP-IDF, даже если они не упомянуты явно. Заголовки из этих компонентов всегда могут быть включены в ваш код без необходимости указания их в переменных REQUIRES
или PRIV_REQUIRES
.
Список общих компонентов:
- cxx
- newlib
- freertos
- esp_hw_support
- heap
- log
- soc
- hal
- esp_rom
- esp_common
- esp_system
- xtensa/riscv.
Включение компонентов в сборку
- По умолчанию в сборку включены все компоненты.
- Если вы зададите переменную
COMPONENTS
c минимальным списком компонентов, используемых непосредственно вашим проектом, то сборка будет автоматически расширена, чтобы также включать требуемые компоненты. Полный список компонентов будет включать:- Компоненты, явно упомянутые в
COMPONENTS
. - Зависимости этих компонентов
REQUIRES
иPRIV_REQUIRES
(рекурсивно). - «Общие» компоненты, от которых зависит каждый компонент.
- Компоненты, явно упомянутые в
Установка в COMPONENTS
минимального списка необходимых компонентов может значительно сократить время компиляции.
Циклические зависимости
Проект может содержать Компонент A, который зависит (через REQUIRES
или PRIV_REQUIRES
) от Компонента B; и Компонент B, который, в свою очередь, сам зависит от Компонента A. Это известно как циклическая зависимость или круговая порука зависимость.
CMake обычно находит циклические зависимости автоматически, дважды повторяя имена библиотек компонентов в командной строке компоновщика. Однако эта стратегия не всегда работает, и сборка может завершиться ошибкой компоновщика о “Undefined reference to …”, ссылающейся на символ, определенный одним из компонентов внутри циклической зависимости. Это особенно вероятно, если имеется большая циклическая зависимость, например, A > B > C > D > A.
Лучшим решением в этом случае является реструктуризация компонентов для устранения циклической зависимости. В большинстве случаев архитектура программного обеспечения без циклических зависимостей обладает желаемыми свойствами модульности и чистого разделения на слои и будет более удобной для обслуживания в долгосрочной перспективе. Однако удаление циклических зависимостей не всегда возможно.
Простейшее решение
Чтобы обойти ошибку компоновщика, вызванную циклической зависимостью, простейшим решением будет увеличение свойства CMake LINK_INTERFACE_MULTIPLICITY одной из библиотек компонентов. Это заставляет CMake повторять эту библиотеку и ее зависимости более двух раз в командной строке компоновщика.
Например:
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY LINK_INTERFACE_MULTIPLICITY 3)
- Эту строку следует поместить после
idf_component_register
в файле CMakeLists.txt компонента. - Если это возможно, поместите эту строку в тот компонент, который создает циклическую зависимость, завися от множества других компонентов. Однако строку можно поместить внутрь любого компонента, который является частью общего цикла зависимостей. Выбор компонента, которому принадлежит исходный файл, показанный в сообщении об ошибке компоновщика, или компонента, который определяет символ(ы), упомянутые в сообщении об ошибке компоновщика, является хорошим выбором для начала.
- Обычно достаточно увеличить значение до 3 (по умолчанию оно равно 2), но если это не сработает, попробуйте увеличить число еще больше.
- Добавление этой опции сделает командную строку компоновщика длиннее, а этап компоновки — медленнее.
Расширенное решение: неопределенные символы
Если только один или два символа вызывают циклическую зависимость, а все остальные зависимости линейные, то существует альтернативный метод предотвращения ошибок компоновщика: указать конкретные символы, необходимые для «обратной» зависимости, как неопределенные символы во время компоновки.
Например, если компонент A зависит от компонента B, но компонент B также должен ссылаться на компонент A (и больше ни на что), то вы можете добавить следующую строку в CMakeLists.txt компонента B, чтобы разрешить замкнутый цикл во время компоновки:
# This symbol is provided by 'Component A' at link time target_link_libraries(${COMPONENT_LIB} INTERFACE "-u reverse_ops")
- Опция
-u
означает, что компоновщик всегда будет включать этот символ в сборку, независимо от порядка зависимостей. - Эту строку следует поместить после
idf_component_register
файла CMakeLists.txt компонента. - Если Компоненту B не требуется доступ ни к каким заголовкам Компонента A, а только ссылка на несколько символов, то эту строку можно использовать вместо любой
REQUIRES
от B к A. Это еще больше упрощает структуру компонента в системе сборки.
Дополнительную информацию об этой функции CMake смотрите в документации target_link_libraries.
Реализация системы сборки
- На самом раннем этапе процесса настройки CMake запускается скрипт
expand_requirements.cmake
. Этот скрипт выполняет частичную оценку всех файлов CMakeLists.txt для всех компонентов и строит граф (дерево) зависимостей компонентов (этот граф может иметь замкнутые циклы). Созданный граф используется для генерации файлаcomponent_depends.cmake
в каталоге сборки. - Затем основной процесс CMake использует этот файл для определения списка компонентов, включаемых в сборку (это внутренняя переменная
BUILD_COMPONENTS
). Значение переменнойBUILD_COMPONENTS
сортируется, поэтому зависимости перечисляются первыми, однако, поскольку график зависимостей компонентов может иметь циклы, это не может быть гарантировано для всех компонентов. Порядок должен быть детерминированным, учитывая тот же набор компонентов и зависимостей компонентов. - Значение
BUILD_COMPONENTS
выводится в терминал CMake как «Component names:» - Затем оценивается конфигурация компонентов, включенных в сборку.
- Каждый компонент включается в сборку обычным образом, а файл CMakeLists.txt снова оценивается для добавления библиотек компонентов в сборку.
Порядок зависимости компонентов
Порядок компонентов в переменной BUILD_COMPONENTS
определяет порядок зависимостей компонентов во время сборки:
- Обрабатываются все файлы Project_include.cmake.
- Генерируется список путей заголовков для компиляции (через аргумент
-I
). (Обратите внимание, что для исходных файлов данного компонента компилятору передаются только пути заголовков зависимостей этого компонента).
Добавление зависимостей времени компоновки
Вспомогательная функция ESP-IDF idf_component_add_link_dependency
добавляет зависимость только по ссылке между одним компонентом и другим. Почти во всех случаях лучше использовать PRIV_REQUIRES
функцию idf_component_register
для создания зависимости. Однако в некоторых случаях необходимо добавить зависимость времени ссылки другого компонента к этому компоненту, т. е. обратный порядок PRIV_REQUIRES
(например: Overriding Default Chip Drivers).
Чтобы сделать другой компонент зависимым от этого компонента во время компоновки:
idf_component_add_link_dependency(FROM other_component)
Поместите эту строку вместе с idf_component_register
.
Также можно указать оба компонента по имени:
idf_component_add_link_dependency(FROM other_component TO that_component)
Переопределение частей проекта
Project_include.cmake
Для компонентов, у которых есть зависимости при сборке, которые необходимо оценить перед оценкой любых файлов CMakeLists компонента, вы можете создать файл с именем project_include.cmake в каталоге компонента. Этот файл используется CMake, когда project.cmake оценивает весь проект.
Файлы project_include.cmake используются внутри ESP-IDF для определения параметров сборки всего проекта, таких как утилита командной строки esptool.py и «специального приложения» bootloader.
В отличие от файлов компонентов CMakeLists.txt, при включении файла project_include.cmake текущим рабочим каталогом ( переменная CMAKE_CURRENT_SOURCE_DIR
) является каталог проекта. Используйте переменную COMPONENT_DIR
для определения абсолютного каталога компонента.
Обратите внимание, что project_include.cmake – это не обязательная деталь при наиболее распространенных применениях компонентов, таких как добавление каталогов include в проект или LDFLAGS
на последнем этапе связывания. Эти значения можно настроить через сам файл CMakeLists.txt. Подробнее см. в разделе “Необязательные переменные проекта”.
Файлы project_include.cmake включаются в порядке, указанном в переменной BUILD_COMPONENTS
(как это регистрируется CMake). Это означает, что файл project_include.cmake компонента будет включен после всех project_include.cmake файлов его зависимостей, если только оба компонента не являются частью цикла зависимостей. Это важно, если файл project_include.cmake зависит от переменных, установленных другим компонентом.
Будьте очень осторожны при установке переменных или целей в файле project_include.cmake. Поскольку значения включены в проход CMake проекта верхнего уровня, они могут повлиять на функциональность всех компонентов или нарушить ее!
KConfig.projbuild
Это эквивалентно файлам project_include.cmake конфигурации компонентов KConfig. Если вы хотите включить параметры конфигурации на верхнем уровне menuconfig, а не внутри подменю “Component Configuration“, то их можно определить в файле KConfig.projbuild рядом с файлом CMakeLists.txt.
Будьте осторожны при добавлении значений конфигурации в этот файл, так как они будут включены во всю конфигурацию проекта. Где это возможно, лучше создать обычный файл KConfig для подменю “Component Configuration“.
Обертки для переопределения или расширения существующих функций
Благодаря функции компоновщика wrap
можно переопределить или расширить поведение существующей функции ESP-IDF. Для этого вам нужно будет предоставить следующую декларацию CMake в файле CMakeLists.txt вашего проекта:
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=function_to_redefine")
Где function_to_redefine
– это имя функции для переопределения или расширения. Эта опция позволит компоновщику заменить все вызовы функций function_to_redefine
в бинарных библиотеках на вызовы __wrap_function_to_redefine
функций. Таким образом, вы должны определить этот новый символ __wrap_function_to_redefine
в своем приложении.
Компоновщик предоставит новый символ с именем __real_function_to_redefine
, который указывает на предыдущую реализацию функции для переопределения. Его можно вызвать из новой реализации, сделав ее расширением предыдущей.
Этот механизм показан в примере build_system/wrappers. Проверьте examples/build_system/wrappers/README.md для более подробной информации.
Переопределение загрузчика по умолчанию
Благодаря необязательному каталогу bootloader_components
, присутствующему в вашем проекте ESP-IDF, можно переопределить загрузчик по умолчанию. Для этого в каталоге bootloader_components/main
необходимо определить новый компонент, который придаст дереву каталогов проекта следующий вид:
myProject/ - CMakeLists.txt - sdkconfig - bootloader_components/ - main/ - CMakeLists.txt - Kconfig - my_bootloader.c - main/ - CMakeLists.txt - app_main.c - build/
Здесь файл my_bootloader.c становится исходным кодом для нового загрузчика, а это значит, что ему нужно будет выполнить все необходимые операции по настройке и загрузке приложения main из флэш-памяти.
Также возможно условно изменить загрузчик в зависимости от определенного условия, например, в зависимости от цели. Этого можно добиться благодаря переменной CMake BOOTLOADER_IGNORE_EXTRA_COMPONENT
. Этот список можно использовать, чтобы указать проекту загрузчика ESP-IDF игнорировать и не компилировать указанные в данной переменной компоненты, которые присутствуют в bootloader_components
. Например, если кто-то хочет использовать загрузчик по умолчанию для цели ESP32, то файл myProject/CMakeLists.txt должен выглядеть следующим образом:
include($ENV{IDF_PATH}/tools/cmake/project.cmake) if(${IDF_TARGET} STREQUAL "esp32") set(BOOTLOADER_IGNORE_EXTRA_COMPONENT "main") endif() project(main)
Важно отметить, что этот механизм также может быть использован для любых других компонентов загрузчика, отличных от main. Во всех случаях префикс bootloader_component
указывать не нужно.
Пример переопределения загрузчика по умолчанию см. в разделе custom_bootloader/bootloader_override.
Компоненты, предназначенные только для конфигурации
Компоненты, которые не содержат каких-либо исходных файлов, а только Kconfig.projbuild или KConfig, могут иметь однострочный файл CMakeLists.txt, который вызывает функцию idf_component_register()
без аргументов. Эта функция включит компонент в сборку проекта, но библиотека не будет собрана, и файлы заголовков не будут добавлены ни в один из включаемых путей.
Отладка CMake
Полную информацию о CMake и командах CMake см. в документации CMake v3.16.
Несколько советов по отладке системы сборки на основе ESP-IDF CMake:
- При запуске CMake выводит довольно много диагностической информации, включая списки компонентов и пути к компонентам. Эту информацию можно и нужно использовать для отладки проекта.
- Запуск команды
cmake -DDEBUG=1
приведет к получению ещё более подробного диагностического вывода от системы сборки IDF. - Запуск
cmake
с параметрами--trace
или--trace-expand
даст очень много информации о потоке управления. См. документацию по командной строке cmake .
При обработке файла CMakeLists проекта файл project.cmake
определяет некоторые служебные глобальные переменные, а затем устанавливает IDF_PATH
, если они не были установлены в системной среде. Он также определяет переопределенную пользовательскую версию встроенной функции CMake project()
. Эта функция переопределяется для добавления всех функций проекта, специфичных для ESP-IDF.
Предупреждение о неопределенных переменных
По умолчанию функция предупреждений о неопределенных переменных отключена.
Чтобы включить эту функцию, вы должны передать флаг --warn-uninitialized
или --cmake-warn-uninitialized
, чтобы idf.py
вывел предупреждение, если в сборке есть ссылка на неопределенную переменную. Это может быть очень полезно для поиска глючных файлов проекта.
Для получения более подробной информации просмотрите файл /tools/cmake/project.cmake и вспомогательные функции в /tools/cmake/.
Пример компонента
Поскольку среда сборки пытается установить разумные значения по умолчанию, которые будут работать большую часть времени, компонент CMakeLists.txt может быть очень маленьким или даже пустым (см. “Минимальный CMakeLists для компонента”). Однако для некоторых функций иногда требуется переопределение preset_component_variables
.
Ниже представлены несколько продвинутых примеров файлов компонентов CMakeLists.
Добавление условной конфигурации
Система конфигурации может использоваться для условной компиляции некоторых файлов в зависимости от параметров, выбранных в конфигурации проекта.
Kconfig
:
config FOO_ENABLE_BAR bool "Enable the BAR feature." help This enables the BAR feature of the FOO component.
CMakeLists.txt
:
set(srcs "foo.c" "more_foo.c") if(CONFIG_FOO_ENABLE_BAR) list(APPEND srcs "bar.c") endif() idf_component_register(SRCS "${srcs}" ...)
В этом примере используются функции CMake if и list APPEND.
Этот механизм также можно использовать для выбора или заглушки реализации, например:
Kconfig
:
config ENABLE_LCD_OUTPUT bool "Enable LCD output." help Select this if your board has an LCD. config ENABLE_LCD_CONSOLE bool "Output console text to LCD" depends on ENABLE_LCD_OUTPUT help Select this to output debugging output to the LCD config ENABLE_LCD_PLOT bool "Output temperature plots to LCD" depends on ENABLE_LCD_OUTPUT help Select this to output temperature plots
CMakeLists.txt
:
if(CONFIG_ENABLE_LCD_OUTPUT) set(srcs lcd-real.c lcd-spi.c) else() set(srcs lcd-dummy.c) endif() # We need font if either console or plot is enabled if(CONFIG_ENABLE_LCD_CONSOLE OR CONFIG_ENABLE_LCD_PLOT) list(APPEND srcs "font.c") endif() idf_component_register(SRCS "${srcs}" ...)
Условия, которые зависят от цели
Текущая цель доступна файлам CMake через переменную IDF_TARGET
. Кроме того, если используется цель xyz
(IDF_TARGET=xyz
), то будет установлена переменная CONFIG_IDF_TARGET_XYZ
и в системе Kconfig.
Обратите внимание, что зависимости компонентов могут зависеть от переменной IDF_TARGET
, но не от переменных Kconfig. Также нельзя использовать переменные Kconfig в include
операторах в файлах CMake, но IDF_TARGET
можно использовать в таком контексте.
Генерация исходного кода
Некоторые компоненты будут иметь ситуацию, когда исходный файл не поставляется с самим компонентом, а должен быть сгенерирован из другого файла. Допустим, у нашего компонента есть файл заголовка, который состоит из преобразованных двоичных данных файла BMP, преобразованных с помощью гипотетического инструмента под названием bmp2h. Затем файл заголовка включается как исходный файл C под названием graphics_lib.c:
add_custom_command(OUTPUT logo.h COMMAND bmp2h -i ${COMPONENT_DIR}/logo.bmp -o log.h DEPENDS ${COMPONENT_DIR}/logo.bmp VERBATIM) add_custom_target(logo DEPENDS logo.h) add_dependencies(${COMPONENT_LIB} logo) set_property(DIRECTORY "${COMPONENT_DIR}" APPEND PROPERTY ADDITIONAL_CLEAN_FILES logo.h)
Примечание: Этот пример взят из раздела FAQ по CMake , в котором содержатся некоторые другие примеры, которые также будут работать со сборками ESP-IDF.
В этом примере logo.h будет сгенерирован в текущем каталоге (каталоге сборки), а logo.bmp поставляется с компонентом и находится в каталоге компонента. Поскольку logo.h — это сгенерированный файл, его следует очистить при очистке проекта. По этой причине он добавляется в свойство ADDITIONAL_CLEAN_FILES.
Примечание. Если файлы генерируются как часть файла проекта CMakeLists.txt, а не компонента CMakeLists.txt, то используйте свойство PROJECT_DIR
вместо ${COMPONENT_DIR}
и ${PROJECT_NAME}.elf
вместо ${COMPONENT_LIB}
.
Если включен исходный файл из другого компонента logo.h
, то add_dependencies
необходимо вызвать метод для добавления зависимости между двумя компонентами, чтобы гарантировать, что исходные файлы компонентов всегда компилируются в правильном порядке.
Внедрение двоичных данных
Иногда у нас имеется файл с двоичными или текстовыми данными, которые мы хотели бы сделать доступными для своего компонента, но вы не хотите переформатировать файл как исходный код на языке C.
Вы можете при регистрации компонента указать аргумент EMBED_FILES
, указав разделенные пробелами имена файлов для встраивания:
idf_component_register(... EMBED_FILES server_root_cert.der)
Или, если файл является текстом, вы можете использовать переменную EMBED_TXTFILES
. Это встроит содержимое текстового файла в виде строки с нулевым завершением:
idf_component_register(... EMBED_TXTFILES server_root_cert.pem)
Содержимое файла будет добавлено в раздел .rodata на флэш-памяти и доступно через следующие имена символов:
extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start"); extern const uint8_t server_root_cert_pem_end[] asm("_binary_server_root_cert_pem_end");
Имена генерируются из полного имени файла, как указано в EMBED_FILES
. Символы /,.
и т. д. заменяются подчеркиваниями. Префикс _binary в имени символа добавляется к сгенерированному имени и одинаков как для текстовых, так и для двоичных файлов.
Чтобы встроить в проект файл, а не компонент, можно вызвать функцию target_add_binary_data
следующим образом:
target_add_binary_data(myproject.elf "main/data.bin" TEXT)
Поместите эту строку после строки project()
в файле CMakeLists.txt вашего проекта. Замените myproject.elf
на имя вашего проекта. Последним аргументом может быть либо TEXT
для встраивание строки с нулевым завершением, либо BINARY
для встраивания содержимого “как есть”.
Пример использования этой техники см. в компоненте «main» примера file_serving protocols/http_server/file_serving/main/CMakeLists.txt — два файла загружаются во время сборки и связываются с прошивкой.
Также возможно встроить сгенерированный файл:
add_custom_command(OUTPUT my_processed_file.bin COMMAND my_process_file_cmd my_unprocessed_file.bin) target_add_binary_data(my_target "my_processed_file.bin" BINARY)
В приведенном выше примере my_processed_file.bin
генерируется из my_unprocessed_file.bin
помощью некоторой команды my_process_file_cmd
, а затем внедряется в цель.
Чтобы указать зависимость от цели, используйте DEPENDS
аргумент:
add_custom_target(my_process COMMAND ...) target_add_binary_data(my_target "my_embed_file.bin" BINARY DEPENDS my_process)
Аргумент DEPENDS
в target_add_binary_data
гарантирует, что цель будет выполнена первой.
Размещение кода и данных
ESP-IDF имеет функцию, называемую генерацией скрипта компоновщика, которая позволяет компонентам определять, где их код и данные будут размещены в памяти через файлы фрагментов компоновщика. Эти файлы обрабатываются системой сборки и используются для дополнения скрипта компоновщика, используемого для компоновки двоичного файла приложения. См. Генерация скрипта компоновщика для краткого руководства по началу работы и подробного обсуждение механизма.
Полное переопределение процесса сборки компонента
Очевидно, есть случаи, когда все эти рецепты недостаточны для определенного компонента, например, когда компонент по сути является оберткой вокруг другого стороннего компонента, изначально не предназначенного для компиляции в этой системе сборки. В этом случае можно полностью отказаться от системы сборки ESP-IDF, используя функцию CMake под названием ExternalProject.
Пример компонента CMakeLists:
# External build process for quirc, runs in source dir and # produces libquirc.a externalproject_add(quirc_build PREFIX ${COMPONENT_DIR} SOURCE_DIR ${COMPONENT_DIR}/quirc CONFIGURE_COMMAND "" BUILD_IN_SOURCE 1 BUILD_COMMAND make CC=${CMAKE_C_COMPILER} libquirc.a INSTALL_COMMAND "" ) # Add libquirc.a to the build process add_library(quirc STATIC IMPORTED GLOBAL) add_dependencies(quirc quirc_build) set_target_properties(quirc PROPERTIES IMPORTED_LOCATION ${COMPONENT_DIR}/quirc/libquirc.a) set_target_properties(quirc PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${COMPONENT_DIR}/quirc/lib) set_directory_properties( PROPERTIES ADDITIONAL_CLEAN_FILES "${COMPONENT_DIR}/quirc/libquirc.a")
Приведенный выше файл CMakeLists.txt можно использовать для создания компонента с именем quirc
, который собирает проект Quirc , используя собственный Makefile.
-
externalproject_add
определяет внешнюю систему сборки.
-
SOURCE_DIR
,CONFIGURE_COMMAND
,BUILD_COMMAND
иINSTALL_COMMAND
всегда должны быть определены.CONFIGURE_COMMAND
может быть установлена в пустую строку, если в системе сборки нет шага «настройка».INSTALL_COMMAND
обычно будет пустым для сборок ESP-IDF.- Настройка
BUILD_IN_SOURCE
означает, что каталог сборки совпадает с исходным каталогом. В противном случае вы можете задатьBUILD_DIR
. - Более подробную информацию см.
externalproject_add()
в документации ExternalProject.
-
Второй набор команд добавляет целевую библиотеку, которая указывает на “импортированный” файл библиотеки, созданный внешней системой. Необходимо задать некоторые свойства, чтобы добавить каталоги include и сообщить CMake, где находится этот файл.
-
Наконец, сгенерированная библиотека добавляется в ADDITIONAL_CLEAN_FILES. Это означает, что данная библиотека будет удалена
make clean
. (Обратите внимание, что другие объектные файлы из сборки не будут удалены)
Примечание. При использовании внешнего процесса сборки с PSRAM не забудьте добавить -mfix-esp32-psram-cache-issue
к аргументам компилятора C. Подробности этого флага см. в CONFIG_SPIRAM_CACHE_WORKAROUND.
Внешние зависимости проекта и чистые сборки
CMake ведет себя необычно при сборке внешних проектов:
- ADDITIONAL_CLEAN_FILES работает только когда в качестве системы сборки используется make или ninja. Если используется система сборки IDE, она не удалит эти файлы при очистке.
- Однако команды настройки и сборки ExternalProject всегда будут запускаться повторно после выполнения очистки.
Поэтому существует два альтернативных рекомендуемых способа настройки внешней команды сборки:
Запустите внешнюю команду
BUILD_COMMAND
для полной чистой компиляции всех исходников. Команда сборки будет запущена, если какие-либо зависимости, переданные вexternalproject_add
сDEPENDS
, были изменены или если это чистая сборка (т. е. был запущен любая из командidf.py clean
,ninja clean
илиmake clean
).Запустите внешнюю
BUILD_COMMAND
командой инкрементальной сборки. Передайте параметрBUILD_ALWAYS 1
вexternalproject_add
. Это означает, что внешний проект будет собираться каждый раз при запуске сборки, независимо от зависимостей. Это рекомендуется только в том случае, если внешний проект имеет правильное поведение инкрементальной сборки и не требует слишком много времени для выполнения.
Лучший из этих подходов к созданию внешнего проекта будет зависеть от самого проекта, его системы сборки и того, предполагаете ли вы необходимость частой перекомпиляции проекта.
Пользовательские настройки Sdkconfig по умолчанию
При создании проекта, где вы не хотите указывать полную конфигурацию sdkconfig, но хотите заранее переопределить некоторые ключевые значения из значений ESP-IDF по умолчанию, можно создать файл sdkconfig.defaults в каталоге проекта. Этот файл будет использоваться при создании новой конфигурации с нуля или когда какое-либо новое значение конфигурации еще не было установлено в файле sdkconfig.
Чтобы переопределить имя этого файла или указать несколько файлов, задайте переменную среду SDKCONFIG_DEFAULTS
или установите SDKCONFIG_DEFAULTS
в верхнем уровне CMakeLists.txt. Имена файлов, которые не указаны как полные пути, разрешаются относительно каталога текущего проекта. При указании нескольких файлов используйте точку с запятой в качестве разделителя списка. Файлы, перечисленные первыми, будут применены первыми. Если конкретный ключ определен в нескольких файлах, определение в последнем файле переопределит определения из предыдущих файлов.
Некоторые примеры IDF включают файл sdkconfig.ci. Это часть фреймворка непрерывной интеграции (CI) и игнорируется обычным процессом сборки.
Целевые значения Sdkconfig по умолчанию
Если и только если файл sdkconfig.defaults существует, система сборки также попытается загрузить значения по умолчанию из файла sdkconfig.defaults.TARGET_NAME, где TARGET_NAME — значение IDF_TARGET
. Например, для esp32
сначала будут взяты значения по умолчанию sdkconfig.defaults, а затем — из sdkconfig.defaults.esp32.
Если общих значений по умолчанию нет, вам все равно необходимо создать пустой файл sdkconfig.defaults , чтобы система сборки смогла распознавать какие-либо дополнительные файлы sdkconfig.defaults.TARGET_NAME, зависящие от target.
Если используется переменная SDKCONFIG_DEFAULTS
для переопределения имени файла/файлов по умолчанию, имя целевого файла по умолчанию будет получено из SDKCONFIG_DEFAULTS
с использованием правила выше.
Например, если SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig_devkit1"
и в той же папке есть файл sdkconfig.defaults.esp32, то файлы будут применены в следующем порядке: (1) sdkconfig.defaults (2) sdkconfig.defaults.esp32 (3) sdkconfig_devkit1.
Флэш-аргументы
Есть несколько сценариев, когда мы хотим прошить целевую плату без IDF. Для этого случая мы хотим получить скомпилированные двоичные файлы, esptool.py и аргументы esptool write_flash. Написать скрипт для сохранения двоичных файлов и esptool.py довольно просто.
После выполнения компиляции проекта каталог сборки содержит двоичные выходные файлы ( .bin – файлы) для проекта, а также следующие файлы данных прошивки:
flash_project_args
содержит аргументы для прошивки всего проекта (приложения, загрузчика, таблицы разделов, данных PHY, если они настроены).flash_app_args
содержит аргументы для прошивки только приложения.flash_bootloader_args
содержит аргументы для прошивки только загрузчика.
Вы можете передать любой из этих файлов аргументов утилите esptool.py
следующим образом:
python esptool.py --chip esp32 write_flash @build/flash_project_args
В качестве альтернативы можно вручную скопировать параметры из файла аргументов и передать их в командную строку.
Каталог сборки также содержит сгенерированный файл flasher_args.json, содержащий информацию о проекте в формате JSON. Этот файл используется idf.py
и может использоваться другими инструментами, которым нужна информация о сборке проекта.
Сборка загрузчика
Загрузчик — это специальный «подпроект» внутри /components/bootloader/subproject. Он имеет свой собственный файл проекта CMakeLists.txt и компилирует отдельные файлы .ELF и .BIN в основной проект. Однако он разделяет свою конфигурацию и каталог сборки с основным проектом.
Подпроект вставляется как внешний проект из проекта верхнего уровня с помощью файла /components/bootloader/project_include.cmake . Основной процесс сборки запускает CMake для подпроекта, что включает обнаружение компонентов (подмножество основных компонентов) и генерацию конфигурации, специфичной для загрузчика (производной от основного sdkconfig
).
Написание чистых компонентов CMake
Система сборки ESP-IDF «оборачивает» CMake концепцией «компонентов» и вспомогательными функциями для автоматической интеграции этих компонентов в сборку проекта.
Однако под концепцией “компонентов” скрывается полноценная система сборки CMake. Так что вполне возможно создать компонент, который будет “чистым” CMake. Вот пример минимального файла CMakeLists компонента «чистого CMake» для компонента с именем json:
add_library(json STATIC cJSON/cJSON.c cJSON/cJSON_Utils.c) target_include_directories(json PUBLIC cJSON)
- На самом деле это эквивалентное объявление
json
компонента IDF /components/json/CMakeLists.txt. - Этот файл довольно прост, так как исходных файлов не так много. Для компонентов с большим количеством файлов поведение подстановки логики компонентов ESP-IDF может сделать стиль CMakeLists компонента более простым).
- Каждый раз, когда компонент добавляет целевую библиотеку с именем компонента, система сборки ESP-IDF автоматически добавляет ее в сборку, открывает общедоступные каталоги включения и т. д. Если компонент хочет добавить целевую библиотеку с другим именем, зависимости необходимо добавить вручную с помощью команд CMake.
Использование сторонних проектов CMake с компонентами
CMake используется для множества проектов с открытым исходным кодом на C и C++ — кода, который пользователи могут использовать для своих приложений. Одним из преимуществ наличия системы сборки CMake является возможность импортировать эти сторонние проекты, иногда даже без изменений! Это позволяет пользователям получать функциональность, которая может быть еще не предоставлена компонентом, или использовать другую библиотеку для той же функциональности.
Импорт библиотеки может выглядеть следующим образом для гипотетической библиотеки foo
, которая будет использоваться в main
компоненте:
# Register the component idf_component_register(...) # Set values of hypothetical variables that control the build of `foo` set(FOO_BUILD_STATIC OFF) set(FOO_BUILD_TESTS OFF) # Create and import the library targets add_subdirectory(foo) # Publicly link `foo` to `main` component target_link_libraries(main PUBLIC foo)
Для понимания фактического примера взгляните на build_system/cmake/import_lib. Обратите внимание, что то, что нужно сделать для импорта вашей библиотеки, может отличаться от приведенного в примерах. Рекомендуется прочитать документацию подключаемой библиотеки для получения инструкций о том, как импортировать ее из других проектов. Изучение CMakeLists.txt библиотеки и структуры сборки также может быть полезным.
Аналогично можно обернуть стороннюю библиотеку для использования в качестве компонента ESP-IDF. Например, компонент mbedtls является оберткой для форка Espressif mbedtls. См. его компонент CMakeLists.txt.
Переменная CMake ESP_PLATFORM
устанавливается в 1 всякий раз, когда используется система сборки ESP-IDF. Тесты if (ESP_PLATFORM)
могут использоваться в общем коде CMake, если требуется специальная логика, специфичная для IDF.
Использование компонентов ESP-IDF из внешних библиотек
В приведенном выше примере предполагается, что внешней библиотеке foo
(или tinyxml
в случае примера import_lib
) не нужно использовать какие-либо API ESP-IDF, кроме общих API, таких как libc, libstdc++ и т. д. Если внешней библиотеке необходимо использовать API, предоставляемые другими компонентами ESP-IDF, это необходимо указать во внешнем файле CMakeLists.txt, добавив зависимость от target библиотеки idf::<componentname>
.
Например, в файле foo/CMakeLists.txt:
add_library(foo bar.c fizz.cpp buzz.cpp) if(ESP_PLATFORM) # On ESP-IDF, bar.c needs to include esp_flash.h from the spi_flash component target_link_libraries(foo PRIVATE idf::spi_flash) endif()
Использование готовых библиотек
Еще одна возможность заключается в том, что у вас есть готовая статическая библиотека ( представленная в виде файла .a
), созданная каким-то другим компилятором.
Система сборки ESP-IDF предоставляет пользователям вспомогательную функцию add_prebuilt_library
, позволяющую легко интегрировать и использовать готовые библиотеки:
add_prebuilt_library(target_name lib_path [REQUIRES req1 req2 ...] [PRIV_REQUIRES req1 req2 ...])
где:
target_name
– имя, которое можно использовать для ссылки на импортированную библиотеку, например, при связывании с другими целямиlib_path
– путь к готовой библиотеке; может быть абсолютным или относительным путем к каталогу компонента
Необязательные аргументы REQUIRES
и PRIV_REQUIRES
определяют зависимости от других компонентов. Они имеют то же значение, что и аргументы для idf_component_register
.
Обратите внимание, что предварительно собранная библиотека должна быть скомпилирована для той же цели, что и потребляющий проект. Конфигурация, относящаяся к предварительно собранной библиотеке, также должна совпадать. Если не обращать на это внимания, эти два фактора могут способствовать появлению неявных ошибок в приложении.
Для примера взгляните на build_system/cmake/import_prebuilt.
Использование ESP-IDF в пользовательских проектах CMake
ESP-IDF предоставляет шаблон проекта CMake для простого создания приложения. Однако в некоторых случаях у пользователя может уже быть существующий проект CMake или он может захотеть создать свой собственный. В этих случаях желательно иметь возможность использовать компоненты IDF как библиотеки для привязки к целям пользователя (библиотеки/исполняемые файлы).
Это можно сделать, используя API системы сборки, предоставляемые tools/cmake/idf.cmake. Например:
cmake_minimum_required(VERSION 3.16) project(my_custom_app C) # Include CMake file that provides ESP-IDF CMake build system APIs. include($ENV{IDF_PATH}/tools/cmake/idf.cmake) # Include ESP-IDF components in the build, may be thought as an equivalent of # add_subdirectory() but with some additional processing and magic for ESP-IDF build # specific build processes. idf_build_process(esp32) # Create the project executable and plainly link the newlib component to it using # its alias, idf::newlib. add_executable(${CMAKE_PROJECT_NAME}.elf main.c) target_link_libraries(${CMAKE_PROJECT_NAME}.elf idf::newlib) # Let the build system know what the project executable is to attach more targets, dependencies, etc. idf_build_executable(${CMAKE_PROJECT_NAME}.elf)
Пример в build_system/cmake/idf_as_lib демонстрирует создание приложения, эквивалентного приложению hello world, с использованием пользовательского проекта CMake.
Примечание. Система сборки IDF может устанавливать флаги компилятора только для исходных файлов, которые она собирает. При использовании внешнего файла CMakeLists.txt и включении PSRAM не забудьте добавить -mfix-esp32-psram-cache-issue
в аргументы компилятора C. Подробности этого флага см. в CONFIG_SPIRAM_CACHE_WORKAROUND.
API системы сборки ESP-IDF CMake
Команды idf-build
idf_build_get_property(var property [GENERATOR_EXPRESSION])
Извлечь свойство сборки property
и сохранить его в var
, доступном из текущей области. Указание GENERATOR_EXPRESSION
извлечет строку выражения генератора для этого свойства вместо фактического значения, которое можно использовать с командами CMake, поддерживающими выражения генератора.
idf_build_set_property(property val [APPEND])
Установить свойство сборки property
со значением val
. Указание APPEND
добавит указанное значение к текущему значению свойства. Если свойство ранее не существовало или в настоящее время пусто, указанное значение вместо этого становится первым элементом/членом.
idf_build_component(component_dir)
Представить каталог component_dir
, содержащий компонент, в системе сборки. Относительные пути преобразуются в абсолютные пути относительно текущего каталога. Все вызовы этой команды должны быть выполнены до idf_build_process
.
Эта команда не гарантирует, что компонент будет обработан во время сборки (см. описание аргумента COMPONENTS
для idf_build_process
)
idf_build_process(target [PROJECT_DIR project_dir] [PROJECT_VER project_ver] [PROJECT_NAME project_name] [SDKCONFIG sdkconfig] [SDKCONFIG_DEFAULTS sdkconfig_defaults] [BUILD_DIR build_dir] [COMPONENTS component1 component2 ...])
Выполняет большую часть закулисной магии для включения компонентов ESP-IDF, таких как конфигурация компонентов, создание библиотек, расширение и разрешение зависимостей. Среди этих функций, возможно, наиболее важной с точки зрения пользователя является создание библиотек путем вызова каждого компонента idf_component_register
. Эта команда создает библиотеки для каждого компонента, которые доступны с помощью псевдонимов в форме idf::component_name
. Эти псевдонимы можно использовать для связывания компонентов с собственными целями пользователя, библиотеками или исполняемыми файлами.
Для вызова требуется указание целевого чипа с аргументом target
. Необязательные аргументы для вызова включают:
PROJECT_DIR
— каталог проекта; по умолчаниюCMAKE_SOURCE_DIR
PROJECT_NAME
— имя проекта; по умолчаниюCMAKE_PROJECT_NAME
PROJECT_VER
— версия/ревизия проекта; по умолчанию «1»SDKCONFIG
— выходной путь сгенерированного файла sdkconfig; по умолчанию PROJECT_DIR/sdkconfig или CMAKE_SOURCE_DIR/sdkconfig в зависимости от того, задан лиPROJECT_DIR
SDKCONFIG_DEFAULTS
– список файлов, содержащих конфигурацию по умолчанию для использования в сборке (список должен содержать полные пути); по умолчанию пусто. Для каждого значения filename в списке также загружается конфигурация из файла filename.target, если он существует.BUILD_DIR
— каталог для размещения артефактов, связанных со сборкой ESP-IDF, таких как сгенерированные двоичные файлы, текстовые файлы, компоненты; по умолчаниюCMAKE_BINARY_DIR
COMPONENTS
— выберите компоненты для обработки среди компонентов, известных системе сборки (добавленных черезidf_build_component
). Этот аргумент используется для обрезки сборки. Другие компоненты добавляются автоматически, если они требуются в цепочке зависимостей, т. е. автоматически добавляются публичные и частные требования компонентов в этом списке, и в свою очередь публичные и частные требования этих требований и т. д. и т. п. Если не указано, обрабатываются все компоненты, известные системе сборки.
idf_build_executable(executable)
Укажите исполняемый файл для сборки ESP-IDF. Это прикрепляет дополнительные цели, такие как зависимости, связанные с прошивкой, генерацией дополнительных двоичных файлов и т. д. Следует вызывать после idf_build_process
.
idf_build_get_config(var config [GENERATOR_EXPRESSION])
Получить значение указанной конфигурации. Подобно свойствам сборки, указание GENERATOR_EXPRESSION
извлечет строку выражения генератора для этой конфигурации вместо фактического значения, которое можно использовать с командами CMake, поддерживающими выражения генератора. Однако фактические значения конфигурации становятся известны только после вызова idf_build_process
.
Свойства idf-build
Это свойства, которые описывают сборку. Значения свойств сборки можно получить с помощью команды build idf_build_get_property
. Например, чтобы получить интерпретатор Python, используемый для сборки:
idf_build_get_property(python PYTHON) message(STATUS "The Python interpreter is: ${python}")
BUILD_DIR
— каталог сборки; устанавливается из аргументаBUILD_DIR
дляidf_build_process
BUILD_COMPONENTS
— список компонентов, включенных в сборку; задается с помощьюidf_build_process
BUILD_COMPONENT_ALIASES
– список псевдонимов библиотеки компонентов, включенных в сборку; задается с помощьюidf_build_process
C_COMPILE_OPTIONS
— параметры компиляции, применяемые ко всем исходным файлам C компонентовCOMPILE_OPTIONS
— параметры компиляции, применяемые ко всем исходным файлам компонентов, независимо от того, написаны ли они на C или C++COMPILE_DEFINITIONS
— компилировать определения, применяемые ко всем исходным файлам компонентовCXX_COMPILE_OPTIONS
— параметры компиляции, применяемые ко всем исходным файлам C++ компонентовDEPENDENCIES_LOCK
– путь к файлу блокировки, используемый в менеджере компонентов. Значение по умолчанию – dependencies.lock в каталоге проекта.EXECUTABLE
— исполняемый файл проекта; устанавливается вызовомidf_build_executable
EXECUTABLE_NAME
— имя исполняемого файла проекта без расширения; задается вызовомidf_build_executable
EXECUTABLE_DIR
— путь, содержащий выходной исполняемый файлIDF_COMPONENT_MANAGER
— менеджер компонентов включен по умолчанию, но если это свойство установлено в значение0
, он будет отключенIDF_PATH
— путь к каталогу ESP-IDF; устанавливается из переменной средыIDF_PATH
, если нет, вычисляется из расположенияidf.cmake
IDF_TARGET
— целевой чип для сборки; устанавливается из требуемого целевого аргумента дляidf_build_process
IDF_VER
— версия ESP-IDF; устанавливается либо из файла версии, либо из ревизии Git репозиторияIDF_PATH
INCLUDE_DIRECTORIES
— включить каталоги для всех исходных файлов компонентовKCONFIGS
— список файлов Kconfig, найденных в компонентах сборки; задаетсяidf_build_process
KCONFIG_PROJBUILDS
— список файлов Kconfig.projbuild, найденных в компонентах сборки; задаетсяidf_build_process
PROJECT_NAME
– имя проекта; задаетсяidf_build_process
с помощью аргументаPROJECT_NAME
PROJECT_DIR
– каталог проекта; устанавливается изidf_build_process
с помощью аргументаPROJECT_DIR
PROJECT_VER
– версия проекта; устанавливается изidf_build_process
с помощью аргументаPROJECT_VER
PYTHON
— интерпретатор Python, используемый для сборки; устанавливается из переменной среды PYTHON, если она доступна, в противном случае используется «python»SDKCONFIG
— полный путь к выходному файлу конфигурации; устанавливается изidf_build_process
с помощью аргументаSDKCONFIG
SDKCONFIG_DEFAULTS
— список файлов, содержащих конфигурацию по умолчанию для использования в сборке; устанавливается с помощьюidf_build_process
с помощью аргументаSDKCONFIG_DEFAULTS
SDKCONFIG_HEADER
— полный путь к заголовочному файлу C/C++, содержащему конфигурацию компонента; задаетсяidf_build_process
SDKCONFIG_CMAKE
— полный путь к файлу CMake, содержащему конфигурацию компонента; задаетсяidf_build_process
SDKCONFIG_JSON
— полный путь к JSON-файлу, содержащему конфигурацию компонента; задаетсяidf_build_process
SDKCONFIG_JSON_MENUS
— полный путь к JSON-файлу, содержащему меню конфигурации; задаетсяidf_build_process
Команды idf-component
idf_component_get_property(var component property [GENERATOR_EXPRESSION])
Извлечь свойство property
указанного компонента component
и сохранить его в var
, доступном из текущей области видимости. Указание GENERATOR_EXPRESSION
извлечет строку выражения генератора для этого свойства вместо фактического значения, которое можно использовать с командами CMake, поддерживающими выражения генератора.
idf_component_set_property(component property val [APPEND])
Установить свойство property
для указанного компонента component
из значения val
. Указание APPEND
добавит указанное значение к текущему значению свойства. Если свойство ранее не существовало или в настоящее время пусто, указанное значение вместо этого становится первым элементом/членом.
idf_component_register([[SRCS src1 src2 ...] | [[SRC_DIRS dir1 dir2 ...] [EXCLUDE_SRCS src1 src2 ...]] [INCLUDE_DIRS dir1 dir2 ...] [PRIV_INCLUDE_DIRS dir1 dir2 ...] [REQUIRES component1 component2 ...] [PRIV_REQUIRES component1 component2 ...] [LDFRAGMENTS ldfragment1 ldfragment2 ...] [REQUIRED_IDF_TARGETS target1 target2 ...] [EMBED_FILES file1 file2 ...] [EMBED_TXTFILES file1 file2 ...] [KCONFIG kconfig] [KCONFIG_PROJBUILD kconfig_projbuild] [WHOLE_ARCHIVE])
Регистрация компонента в системе сборки. Подобно команде project()
, ее следует вызывать из CMakeLists.txt компонента напрямую (а не через функцию или макрос), а также она рекомендуется вызывать перед любой другой командой. Вот несколько рекомендаций о том, какие команды нельзя вызывать раньше idf_component_register
:
- команды, недопустимые в режиме скрипта CMake
- пользовательские команды, определенные в project_include.cmake
- команды API системы сборки, за исключением
idf_build_get_property
; хотя и тут следует учитывать, что свойство еще не установлено
Команды, которые устанавливают и обрабатывают переменные, обычно можно вызывать и до idf_component_register
.
Аргументы idf_component_register
включают в себя:
SRCS
— исходные файлы компонентов, используемые для создания статической библиотеки для компонента; если не указано иное, компонент рассматривается как компонент, предназначенный только для конфигурации, и вместо этого создается библиотека интерфейсов.SRC_DIRS
,EXCLUDE_SRCS
— используется для глобализации исходных файлов (.c, .cpp, .S) путем указания каталогов вместо указания исходных файлов вручную черезSRCS
. Обратите внимание, что это зависит от ограничений глобализации в CMake . Исходные файлы, указанные вEXCLUDE_SRCS
, удаляются из глобализированных файлов.INCLUDE_DIRS
— пути относительно каталога компонента, которые будут добавлены к пути поиска включений для всех других компонентов, которым требуется текущий компонент.PRIV_INCLUDE_DIRS
— пути к каталогам должны быть относительными к каталогу компонента, который будет добавлен к пути поиска включений только для исходных файлов этого компонента.REQUIRES
– зависимости общедоступных компонент для данного компонентаPRIV_REQUIRES
— частные зависимости компонента; игнорируются для компонентов, предназначенных только для конфигурацииLDFRAGMENTS
– файлы фрагментов компоновщика компонентовREQUIRED_IDF_TARGETS
— укажите единственную цель, поддерживаемую данным компонентомKCONFIG
— переопределяет файл Kconfig по умолчаниюKCONFIG_PROJBUILD
— переопределяет файл Kconfig.projbuild по умолчаниюWHOLE_ARCHIVE
– если указано, библиотека компонентов будет окружена опциями-Wl,--whole-archive
,-Wl,--no-whole-archive
при связывании. Это имеет тот же эффект, что и настройкаWHOLE_ARCHIVE
свойства компонента.
Следующие аргументы используются для внедрения сторонних данных в компонент и рассматриваются как исходные файлы при определении того, является ли компонент только конфигурационным. Это означает, что даже если компонент не указывает исходные файлы, статическая библиотека все равно создается внутри компонента, если он указывает что-либо из следующего:
EMBED_FILES
— двоичные файлы для внедрения в компонентEMBED_TXTFILES
— текстовые файлы для встраивания в компонент
Свойства idf-component
Это свойства, которые описывают компонент. Значения свойств компонента можно получить с помощью команды idf_component_get_property
. Например, чтобы получить каталог компонента freertos
:
idf_component_get_property(dir freertos COMPONENT_DIR) message(STATUS "The 'freertos' component directory is: ${dir}")
COMPONENT_ALIAS
— псевдоним дляCOMPONENT_LIB
, используемый для привязки компонента к внешним целям; задаетсяidf_build_component
, а сама библиотека псевдонимов создаетсяidf_component_register
COMPONENT_DIR
– каталог компонентов; устанавливаетсяidf_build_component
COMPONENT_OVERRIDEN_DIR
— содержит каталог исходного компонента, если этот компонент переопределяет другой компонентCOMPONENT_LIB
– имя для созданной библиотеки статических/интерфейсных компонентов; задаетсяidf_build_component
, а сама библиотека псевдонимов создаетсяidf_component_register
COMPONENT_NAME
– имя компонента; задаетсяidf_build_component
на основе имени каталога компонентаCOMPONENT_TYPE
— тип компонента, LIBRARY или CONFIG_ONLY. Компонент имеет тип LIBRARY, если он указывает исходные файлы или встраивает файлEMBED_FILES
— список файлов для встраивания в компонент; задаетсяidf_component_register
аргументомEMBED_FILES
EMBED_TXTFILES
— список текстовых файлов для встраивания в компонент; задаетсяidf_component_register
аргументомEMBED_TXTFILES
INCLUDE_DIRS
— список каталогов включения компонентов; устанавливается изidf_component_register
аргументомINCLUDE_DIRS
KCONFIG
– компонент файла Kconfig; устанавливаетсяidf_build_component
KCONFIG_PROJBUILD
– компонент Kconfig.projbuild; устанавливаетсяidf_build_component
LDFRAGMENTS
— список файлов фрагментов компоновщика компонентов; устанавливаетсяidf_component_register
из аргументаLDFRAGMENTS
MANAGED_PRIV_REQUIRES
— список зависимостей частных компонентов, добавленных менеджером компонентов IDF из зависимостей в файле манифестаidf_component.yml
MANAGED_REQUIRES
— список зависимостей публичных компонентов, добавленных менеджером компонентов IDF из зависимостей в файле манифестаidf_component.yml
PRIV_INCLUDE_DIRS
— список частных каталогов включения компонентов; устанавливаетсяidf_component_register
изPRIV_INCLUDE_DIRS
для компонентов типа LIBRARYPRIV_REQUIRES
— список зависимостей частных компонентов; устанавливается на основе значенияidf_component_register
из аргументаPRIV_REQUIRES
и зависимостей в файле манифестаidf_component.yml
REQUIRED_IDF_TARGETS
— список целей, поддерживаемых компонентом; устанавливаетсяidf_component_register
из аргументаREQUIRED_IDF_TARGETS
REQUIRES
— список зависимостей публичных компонентов; устанавливается на основе значения аргументаREQUIRES
idf_component_register
и зависимостей в файле манифестаidf_component.yml
SRCS
– список исходных файлов компонентов; устанавливается из SRCS или аргументаSRC_DIRS/EXCLUDE_SRCS
idf_component_register
WHOLE_ARCHIVE
— если это свойство установлено в TRUE (или любое логическое значение CMake «true»: 1, ON, YES, Y), библиотека компонентов будет окружена-Wl,--whole-archive
,-Wl,--no-whole-archive
при компоновке. Это можно использовать, чтобы заставить компоновщик включить каждый объектный файл в исполняемый файл, даже если объектный файл не разрешает никаких ссылок из остальной части приложения. Это обычно используется, когда компонент содержит плагины или модули, которые полагаются на регистрацию во время компоновки. Это свойство имеет значение FALSE по умолчанию. Его можно установить в TRUE из файла CMakeLists.txt компонента.
Файловая подстановка и инкрементные сборки
Предпочтительным способом включения исходных файлов в компонент ESP-IDF является их перечисление вручную с помощью аргумента SRCS idf_component_register
:
idf_component_register(SRCS library/a.c library/b.c platform/platform.c ...)
Это предпочтение отражает лучшую практику CMake по ручному перечислению исходных файлов. Однако это может быть неудобно, когда в сборку нужно добавить много исходных файлов. Система сборки ESP-IDF предоставляет альтернативный способ указания исходных файлов с помощью SRC_DIRS
:
idf_component_register(SRC_DIRS library platform ...)
Это использует подстановку за кулисами для поиска исходных файлов в указанных каталогах. Однако имейте в виду, что если добавляется новый исходный файл и используется этот метод, то CMake не будет знать, что нужно автоматически пересобрать проект, и этот обновленный файл не будет добавлен в конечную сборку.
Компромисс приемлем, когда вы добавляете файл самостоятельно, поскольку вы можете запустить чистую сборку idf.py reconfigure
или запустить CMake вручную. Однако проблема усложняется, когда вы делитесь своим проектом с другими, которые могут проверить новую версию с помощью инструмента управления исходным кодом, например Git…
Для компонентов, которые являются частью ESP-IDF, мы используем сторонний модуль интеграции Git CMake ( /tools/cmake/third_party/GetGitRevisionDescription.cmake ), который автоматически перезапускает CMake каждый раз, когда изменяется коммит репозитория. Это означает, что если вы извлечете новую версию ESP-IDF, CMake автоматически перезапустит чистую сборку.
Для компонентов проекта (не являющихся частью ESP-IDF) существует несколько различных вариантов:
- Если файл проекта хранится в Git, ESP-IDF автоматически отслеживает версию Git и перезапускает CMake, если версия изменится.
- Если некоторые компоненты хранятся в третьем репозитории git (не в репозитории проекта или репозитории ESP-IDF), вы можете добавить вызов функции
git_describe
в файл CMakeLists компонента, чтобы автоматически запускать повторные запуски CMake при изменении ревизии Git. - Если вы не используете Git, не забывайте запускать его вручную
idf.py reconfigure
каждый раз, когда исходный файл может измениться. - Чтобы полностью избежать этой проблемы, используйте
SRCS
аргумент дляidf_component_register
с перечислением всех исходных файлов в компонентах проекта.
Лучший вариант будет зависеть от вашего конкретного проекта и его пользователей.
Метаданные системы сборки
Для интеграции в IDE и другие системы сборки, когда CMake запускается, процесс сборки генерирует ряд файлов метаданных в каталоге build/
. Чтобы повторно сгенерировать эти файлы, запустите cmake
или idf.py reconfigure
(или любую другую команду сборки idf.py
)
compile_commands.json
— это стандартный формат файла JSON, который описывает каждый исходный файл, скомпилированный в проекте. Функция CMake генерирует этот файл, и многие IDE умеют его анализировать.project_description.json
— содержит некоторую общую информацию о проекте ESP-IDF, настроенных путях и т. д.flasher_args.json
— содержит аргументыesptool.py
для прошивки двоичных файлов проекта. Также есть файлыflash_*_args
, которые можно использовать напрямую сesptool.py
. См. “Аргументы Flash”.CMakeCache.txt
— это файл кэша CMake, содержащий другую информацию о процессе CMake, цепочке инструментов и т. д.config/sdkconfig.json
представляет собой версию значений конфигурации проекта в формате JSON.config/kconfig_menus.json
представляет собой отформатированную в JSON версию меню, показанных в menuconfig, для использования во внешних пользовательских интерфейсах IDE.
Сервер конфигурации JSON
Инструмент, называемый kconfserver
, позволяет легко интегрировать IDE с логикой системы конфигурации. kconfserver
предназначен для работы в фоновом режиме и взаимодействия с вызывающим процессом путем чтения и записи JSON через каналы stdin и stdout процесса.
Вы можете запустить kconfserver
проекта через idf.py confserver
или ninja kconfserver
, или аналогичную цель, запущенную из другого генератора сборки.
Более подробную информацию см раздел kconfserver
в документации esp-idf-kconfig.
Внутреннее устройство системы сборки
Скрипты сборки
Файлы списка для системы сборки ESP-IDF находятся в /tools/cmake . Модули, реализующие основные функциональные возможности системы сборки, следующие:
- build.cmake — команды, связанные со сборкой, т. е. инициализация сборки, получение/установка свойств сборки, обработка сборки.
- component.cmake — команды, связанные с компонентами, т. е. добавление компонентов, получение/настройка свойств компонентов, регистрация компонентов.
- kconfig.cmake — Генерация файлов конфигурации (sdkconfig, sdkconfig.h, sdkconfig.cmake и т. д.) из файлов Kconfig.
- ldgen.cmake — Генерация окончательного скрипта компоновщика из файлов фрагментов компоновщика.
- target.cmake — настройка цели сборки и файла цепочки инструментов.
- utilities.cmake — Различные вспомогательные команды.
Помимо этих файлов, в /tools/cmake есть еще два важных скрипта CMake :
- idf.cmake — Настраивает сборку и включает основные модули, перечисленные выше. Включен в проекты CMake для доступа к функционалу системы сборки ESP-IDF.
- project.cmake — включает
idf.cmake
и предоставляет пользовательскую командуproject()
, которая берет на себя всю тяжелую работу по созданию исполняемого файла. Включен в CMakeLists.txt верхнего уровня стандартных проектов ESP-IDF.
Остальные файлы в /tools/cmake — это вспомогательные или сторонние скрипты, используемые в процессе сборки.
Процесс сборки
В этом разделе описывается стандартный процесс сборки приложения ESP-IDF. Процесс сборки можно грубо разбить на четыре фазы:
- Initialization
- Enumeration
- Processing
- Finalization
1. Инициализация
На этом этапе устанавливаются необходимые параметры для сборки.
- При включении
idf.cmake
вproject.cmake
выполняются следующие шаги:- Устанавливается
IDF_PATH
из переменной среды или выводится из пути для включенияproject.cmake
в CMakeLists.txt верхнего уровня. - Добавляется /tools/cmake в
CMAKE_MODULE_PATH
, что включает основные модули, а также различные вспомогательные и (или) сторонние скрипты. - Устанавливаются инструменты сборки и исполняемые файлы, такие как интерпретатор Python по умолчанию.
- Получается git-версию ESP-IDF и сохраняется как
IDF_VER
. - Задаются глобальные спецификации сборки, т. е. параметры компиляции, определения компиляции, включайте каталоги для всех компонентов в сборке.
- Добавляются компоненты в компоненты сборки.
- Устанавливается
- Начальная часть пользовательской команды
project()
выполняет следующие шаги:- Устанавливается переменная
IDF_TARGET
из переменной среды или кэша CMake и определяется соответствующее значениеCMAKE_TOOLCHAIN_FILE
, которое будет использоваться. - Добавляются компоненты из
EXTRA_COMPONENT_DIRS
в сборку. - Подготавливаются аргументы для вызова команды
idf_build_process()
из таких переменных, какCOMPONENTS
/EXCLUDE_COMPONENTS
,SDKCONFIG
,SDKCONFIG_DEFAULTS
.
- Устанавливается переменная
- Вызов
idf_build_process()
знаменует собой конец этой фазы.
2. Перечисление
На этом этапе формируется окончательный список компонентов, которые будут обработаны при сборке, и он выполняется в первой половине idf_build_process()
.
- Определяются публичные и частные зависимости каждого компонента. Создается дочерний процесс, который выполняет CMakeLists.txt каждого компонента в режиме скрипта. Значения аргументов REQUIRES и PRIV_REQUIRES для
idf_component_register
возвращаются родительскому процессу сборки. Это называется ранним расширением. ПеременнаяCMAKE_BUILD_EARLY_EXPANSION
определяется на этом этапе. - Рекурсивно включаются компоненты на основе публичные и частные зависимостей каждого включенного компонента.
3. Обработка
На этом этапе обрабатываются компоненты сборки, и это вторая половина idf_build_process()
.
- Загружается конфигурация проекта из файла sdkconfig и генеруруются заголовочные файлы sdkconfig.cmake и sdkconfig.h. Они определяют переменные конфигурации /макросы, которые доступны из скриптов сборки и исходных и заголовочных файлов C/C++ соответственно.
- Включается каждый компонент
project_include.cmake
. - Добавляется каждый компонент как подкаталог, обрабатывая его CMakeLists.txt. Файл CMakeLists.txt вызывает команду регистрации
idf_component_register
, которая добавляет исходные файлы, включает каталоги, создает библиотеку компонентов, связывает зависимости и т. д.
4. Завершение
Эта фаза — всё, что следует после idf_build_process()
.
- Создается исполняемый файл и к нему привязываются все библиотеки компонентов.
- Генерируются файлы метаданных проекта, такие как project_description.json, и отображается соответствующая информация о созданном проекте.
Для получения более подробной информации просмотрите /tools/cmake/project.cmake.
Миграция с системы ESP-IDF GNU Make
Некоторые аспекты системы сборки ESP-IDF на основе CMake очень похожи на более старую систему на основе GNU Make. Разработчику необходимо предоставить значения каталогов include, исходных файлов и т. д. Однако существует синтаксическое различие, поскольку разработчику необходимо передать их в качестве аргументов команде регистрации idf_component_register
.
Инструмент автоматического преобразования
Инструмент автоматического преобразования проекта доступен в tools/cmake/convert_to_cmake.py в релизах ESP-IDF v4.x. Скрипт был удален в v5.0 из-за его зависимости от системы сборки make.
Больше не доступно в CMake
Некоторые функции существенно отличаются или удалены в системе на основе CMake. Следующие переменные больше не существуют в системе сборки на основе CMake:
COMPONENT_BUILD_DIR
: ИспользуйтеCMAKE_CURRENT_BINARY_DIR
вместо этого.COMPONENT_LIBRARY
: По умолчанию$(COMPONENT_NAME).a
, но имя библиотеки может быть переопределено компонентом. Имя библиотеки компонента больше не может быть переопределено компонентом.CC
,LD
,AR
,OBJCOPY
: Полные пути к каждому инструменту из кросс-цепочки инструментов gcc xtensa. Вместо этого используйтеCMAKE_C_COMPILER
,CMAKE_C_LINK_EXECUTABLE
,CMAKE_OBJCOPY
, и т. д. Полный список здесь.HOSTCC
,HOSTLD
,HOSTAR
: Полные имена каждого инструмента из собственной цепочки инструментов хоста. Они больше не предоставляются, внешние проекты должны вручную обнаружить любую требуемую цепочку инструментов хоста.COMPONENT_ADD_LDFLAGS
: Используется для переопределения флагов компоновщика. Вместо этого используйте команду CMaketarget_link_libraries.COMPONENT_ADD_LINKER_DEPS
: Список файлов, от которых должна зависеть компоновка. target_link_libraries обычно выводит эти зависимости автоматически. Для скриптов компоновщика используйте предоставленную пользовательскую функцию CMaketarget_linker_scripts
.COMPONENT_SUBMODULES
: Больше не используется, система сборки автоматически перечислит все подмодули в репозитории ESP-IDF.COMPONENT_EXTRA_INCLUDES
: Раньше это была альтернатива для абсолютных путейCOMPONENT_PRIV_INCLUDEDIRS
. Теперь следует использовать аргументPRIV_INCLUDE_DIRS
idf_component_register
во всех случаях (может быть относительным или абсолютным).COMPONENT_OBJS
: Раньше источники компонентов можно было указать как список объектных файлов. Теперь их можно указать как список исходных файлов черезSRCS
аргументidf_component_register
.COMPONENT_OBJEXCLUDE
: Заменено аргументомEXCLUDE_SRCS
idf_component_register
. Вместо этого укажите исходные файлы (как абсолютные пути или относительно каталога компонента).COMPONENT_EXTRA_CLEAN
: Вместо этого установите свойствоADDITIONAL_CLEAN_FILES
, но учтите, что CMake имеет некоторые ограничения относительно этой функциональности.COMPONENT_OWNBUILDTARGET
&COMPONENT_OWNCLEANTARGET
: Вместо этого используйте CMake ExternalProject . Подробности см. в разделе “Полное переопределение процесса сборки компонента”.COMPONENT_CONFIG_ONLY
: Вызовidf_component_register
без аргументов. См. “Компоненты только конфигурации”.CFLAGS
,CPPFLAGS
,CXXFLAGS
: Вместо этого используйте эквивалентные команды CMake. См. “Управление компиляцией компонентов”.
Нет значений по умолчанию
В отличие от устаревшей системы сборки на основе Make, следующие параметры не имеют значений по умолчанию:
- Исходные каталоги (
COMPONENT_SRCDIRS
переменная в Make, аргументSRC_DIRS
idf_component_register
в CMake) - Включить каталоги (
COMPONENT_ADD_INCLUDEDIRS
переменная в Make, аргументINCLUDE_DIRS
idf_component_register
в CMake)
Больше не нужно
-
В устаревшей системе сборки на основе Make требуется задать
COMPONENT_SRCDIRS
если был заданCOMPONENT_SRCS
. В новой версии CMake этот эквивалент уже не нужен, т.е. указание аргументаSRC_DIRS
не требуется, если задан аргументSRCS
(фактически,SRCS
игнорируется, если был указанSRC_DIRS
).
Инструменты для прошивки чипов от производителя
make flash
и аналогичные инструменты все еще работают для сборки и прошивки. Однако проект sdkconfig
больше не определяет последовательный порт и скорость передачи данных. Переменные окружения могут быть использованы для переопределения этих данных. См. раздел “Прошивка ESP с помощью Ninja или Make” для получения более подробной информации.
💠 Полный архив статей вы найдете здесь
Пожалуйста, оцените статью:
Всё о чём вы хотели знать, но боялись спросить. ОГРОМНОЕ СПАСИБО!!!
Хорошие статьи у вас, хотел помочь сайту через Юмани, а у них какие-то проблемы и платежи не проходят. Советую добавить какой-нибудь ещё способ перевести деньги автору. И почему-то не все статьи открываются( по крайней мере 17-12-2024), жаль.
Да, они не принимают переводы с корпоративных карт (то есть, по сути, со всех зарплатных, выданных на предприятиях). Перевод можно сделать только с личных. Такие у них правила.
Я подумаю насчет других способов.
А какие конкретно статьи не открываются? Можно названия или ссылки – я проверю
Отлична работа, спасибо! Но вот на счет этого : “Если использовать CMake с ninja или make, Вам так же будут доступны большинство idf.py подкоманд. Например, команды make menuconfig или ninja menuconfig в каталоге проекта будет работать так же, как и idf.py menuconfig”… Я тут только начал работать с esp-idf в vscode и долго тупил с командой idf.py menuconfig. У меня она не запускается в терминале vscode (просто не находит ее система), а только в отдельном терминале запускается (нужно прыгнуть в папку с проектом), да еще и после каждой перезагрузки ОС нужно делать source export.sh. ninja menuconfig вообще нигде не запускается. Может это особенности Ubuntu и я еще не разобрался (тоже с неделю как установил) Может кто подскажет что-то по этому поводу.
Чтобы эта команда работала, нужно использовать специальные bat-ники созданные установщиком. Подробности я описывал в конце статьи: https://kotyara12.ru/iot/espressif-ide-setup/
Но таки да, переходить в каталог проекта придется “вручную”
Спасибо!