Перейти к содержимому

Здравствуйте, уважаемые читатели!

Предисловие от автора сайта.

Данная статья представляет собой более-менее “читабельный” перевод раздела 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.

Некоторые объекты не являются частью проекта:

 


Использование системы сборки

Основным компонентом для сборки проекта является 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

Доступные опции: flashapp-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.elfmyProject.bin. Для одного файла CMakeLists может быть определен только один проект.

Дополнительные переменные проекта

Кроме перечисленных переменных имеются и некоторые другие. Все указанные ниже переменные имеют значения по умолчанию, которые можно переопределить для изменения поведения при сборке. Более детально узнать об этом можно по ссылке /tools/cmake/project.cmake.

  • COMPONENT_DIRS: Каталоги для поиска компонентов. По умолчанию они включают IDF_PATH/componentsPROJECT_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, то Ваши шаги будут примерно следующие:

  1. Переименовать main каталог.
  2. Установите переменную EXTRA_COMPONENT_DIRS в головном CMakeLists.txt проекта для включение переименованного main каталога.
  3. Укажите зависимости в файле 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_MAJORIDF_VERSION_MINORIDF_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_DIRCONFIGURE_COMMANDBUILD_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 всегда будут запускаться повторно после выполнения очистки.

Поэтому существует два альтернативных рекомендуемых способа настройки внешней команды сборки:

  1. Запустите внешнюю команду BUILD_COMMAND для полной чистой компиляции всех исходников. Команда сборки будет запущена, если какие-либо зависимости, переданные в externalproject_add с DEPENDS, были изменены или если это чистая сборка (т. е. был запущен любая из команд idf.py clean, ninja clean или make clean).

  2. Запустите внешнюю 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 для компонентов типа LIBRARY
  • PRIV_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. Процесс сборки можно грубо разбить на четыре фазы:

  1. Initialization
  2. Enumeration
  3. Processing
  4. 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() из таких переменных, как COMPONENTSEXCLUDE_COMPONENTSSDKCONFIGSDKCONFIG_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, но имя библиотеки может быть переопределено компонентом. Имя библиотеки компонента больше не может быть переопределено компонентом.
  • CCLDAROBJCOPY: Полные пути к каждому инструменту из кросс-цепочки инструментов gcc xtensa. Вместо этого используйте CMAKE_C_COMPILERCMAKE_C_LINK_EXECUTABLECMAKE_OBJCOPY, и т. д. Полный список здесь.
  • HOSTCCHOSTLDHOSTAR: Полные имена каждого инструмента из собственной цепочки инструментов хоста. Они больше не предоставляются, внешние проекты должны вручную обнаружить любую требуемую цепочку инструментов хоста.
  • COMPONENT_ADD_LDFLAGS: Используется для переопределения флагов компоновщика. Вместо этого используйте команду CMaketarget_link_libraries.
  • COMPONENT_ADD_LINKER_DEPS: Список файлов, от которых должна зависеть компоновка. target_link_libraries обычно выводит эти зависимости автоматически. Для скриптов компоновщика используйте предоставленную пользовательскую функцию CMake target_linker_scripts.
  • COMPONENT_SUBMODULES: Больше не используется, система сборки автоматически перечислит все подмодули в репозитории ESP-IDF.
  • COMPONENT_EXTRA_INCLUDES: Раньше это была альтернатива для абсолютных путей COMPONENT_PRIV_INCLUDEDIRS. Теперь следует использовать аргумент PRIV_INCLUDE_DIRSidf_component_registerво всех случаях (может быть относительным или абсолютным).
  • COMPONENT_OBJS: Раньше источники компонентов можно было указать как список объектных файлов. Теперь их можно указать как список исходных файлов через SRCS аргумент idf_component_register.
  • COMPONENT_OBJEXCLUDE: Заменено аргументомEXCLUDE_SRCS idf_component_register. Вместо этого укажите исходные файлы (как абсолютные пути или относительно каталога компонента).
  • COMPONENT_EXTRA_CLEAN: Вместо этого установите свойство ADDITIONAL_CLEAN_FILES, но учтите, что CMake имеет некоторые ограничения относительно этой функциональности.
  • COMPONENT_OWNBUILDTARGETCOMPONENT_OWNCLEANTARGET: Вместо этого используйте CMake ExternalProject . Подробности см. в разделе “Полное переопределение процесса сборки компонента”.
  • COMPONENT_CONFIG_ONLY: Вызов idf_component_register без аргументов. См. “Компоненты только конфигурации”.
  • CFLAGSCPPFLAGSCXXFLAGS: Вместо этого используйте эквивалентные команды 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” для получения более подробной информации.

 


💠 Полный архив статей вы найдете здесь


Пожалуйста, оцените статью:
[ 5 из 5, всего 9 оценок ]

6 комментариев для “Система сборки ESP-IDF (перевод)”

  1. Хорошие статьи у вас, хотел помочь сайту через Юмани, а у них какие-то проблемы и платежи не проходят. Советую добавить какой-нибудь ещё способ перевести деньги автору. И почему-то не все статьи открываются( по крайней мере 17-12-2024), жаль.

    1. Да, они не принимают переводы с корпоративных карт (то есть, по сути, со всех зарплатных, выданных на предприятиях). Перевод можно сделать только с личных. Такие у них правила.
      Я подумаю насчет других способов.

      А какие конкретно статьи не открываются? Можно названия или ссылки – я проверю

  2. Алексей

    Отлична работа, спасибо! Но вот на счет этого : “Если использовать 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 и я еще не разобрался (тоже с неделю как установил) Может кто подскажет что-то по этому поводу.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *