Программирование ARM ESP-IDF Build System Fri, December 06 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


ESP-IDF Build System Печать
Добавил(а) microsin   

В этом документе (перевод [1]) объясняется реализация системы сборки ESP-IDF и концепция компонентов (components). Ознакомьтесь с этим руководством если хотите узнать, как организовать и собирать новый проект ESP-IDF или компонент.

Проект ESP-IDF можно рассматривать как объединение некоторого количества компонентов. Например для web-сервера, который показывает текущую влажность, это может быть:

• Базовыми библиотеками ESP-IDF (libc, ROM bindings, и т. д.).
• Драйверы Wi-Fi.
• Стек TCP/IP.
• Операционная система FreeRTOS.
• webserver.
• Драйвер для датчика влажности.
• Основной код (main), связывающий все это вместе.

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

Концепции:

"проект" (project) это директория, которая содержит все файлы и конфигурацию для сборки как одного приложения "app" (executable, исполняемый код), так и дополнительных элементов поддержки, таких как таблица разделов [8], разделы data/filesystem, и bootloader.

"Конфигурация проекта" (Project configuration) хранит один файл с именем sdkconfig в корневой директории проекта. Этот файл конфигурации модифицируется командой idf.py menuconfig, чтобы настроить конфигурацию проекта. Один проект содержит ровно одну конфигурацию проекта.

"app" это исполняемый код (приложение), который собирает ESP-IDF. Один проект обычно собирает сразу два приложения - "project app" (главный исполняемый код, т. е. приложение firmware пользователя), и "bootloader app" (самая первая запускаемая программа загрузчика, которая загружает и запускает project app).

"компоненты" (components) представляют модульные части отдельного кода, который компилируется в статические библиотеки (файлы с расширением .a), и затем линкуются в app. Некоторые из компонентов предоставляются самой ESP-IDF, другие могут быть взяты из других источников.

"target" это железо, для которого выполняется сборка приложения. Полный список поддерживаемых целей сборки для Вашей версии ESP-IDF можно просмотреть запуском команды idf.py –list-targets.

Кое-что не является частью проекта:

• "ESP-IDF" это не часть проекта. Вместо того среда разработки автономна, и связывается с проектом через переменную окружения IDF_PATH, которая хранит путь директории установки ESP-IDF (на Windows по умолчанию это может быть каталог наподобие C:\Espressif\frameworks\esp-idf-v4.4). Это позволяет отделить среду разработки IDF от Вашего проекта.
• Тулчейн для компиляции это не часть проекта. Тулчейн должен быть установлен так, чтобы путь до его исполняемых утилит находился в системной переменной путей поиска PATH.

[Как использовать систему сборки]

idf.py. Это утилита командной строки, предоставляющая простой способ управления сборками проекта. Она управляет следующими инструментами:

CMake, который конфигурирует проект для сборки.
Ninja, который собирает проект.
esptool.py для прошивки исполняемого кода (а также загрузчика, таблицы разделов и самих разделов flash) проекта в target.

Более подробно про конфигурацию системы сборки с помощью idf.py можно прочитать в документации [9].

Использование CMake напрямую. Скрипт idf.py является оберткой над CMake, который дает удобство для общего управления проектом. Однако Вы можете также, если хотите, запускать CMake напрямую.

Когда idf.py делает что-либо, она печатает каждую запускаемую команду, чтобы её было проще найти. Например, команда idf.py build делает то же самое, что и запуск следующих команд в bash shell (или подобных команд в Windows Command Prompt):

mkdir -p build
cd build
cmake .. -G Ninja   # or 'Unix Makefiles'
ninja

В показанном выше списке команд cmake конфигурирует проект и генерирует файлы сборки для использования конечным инструментарием сборки. В этом случае таким инструментарием служит Ninja: его запуск делает фактическую сборку проекта.

Необязательно запускать cmake больше одного раза. После первой сборки Вам только понадобится каждый раз запустить ninja. Ninja автоматически запустит cmake, если проекту понадобится реконфигурация.

Если CMake используется вместе с ninja или make, то существуют также цели (targets) для большего количества субкоманд idf.py. Например, запуск make menuconfig или ninja menuconfig в директории сборки (build) будет работать так же, как и idf.py menuconfig.

Прошивка с помощью ninja или make. Есть возможность компилировать и прошивать программу непосредственно из ninja или make, если запустить target примерно так:

ninja flash

или:

make app-flash

Доступны target-ы: flash, app-flash (только для приложения), bootloader-flash (только для загрузчика).

Когда прошивка производится таким способом, опционально устанавливаются переменные окружения ESPPORT и ESPBAUD, чтобы указать последовательный порт и скорость.

Вы можете установить переменные окружения в свой операционной системе, или в проекте IDE. Альтернативно установите их из непосредственно из командной строки, пример для Linux и macOS:

ESPPORT=/dev/ttyUSB0 ninja flash

На Windows переменные окружения устанавливаются с помощью команды set:

SET ESPPORT=COM3 ninja flash

Просмотреть переменные окружения можно командой set без опций, как на Linux, так и на Windows.

На операционной системе Linux можно отфильтровать вывод команды set с помощью grep. На Windows отфильтровать вывод можно с помощью find:

set | find "ESPPORT"

Можно установить переменные окружения в командной строке make:

make -j3 app-flash ESPPORT=COM4 ESPBAUD=2000000

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

Использование CMake в IDE. Вы можете использовать интеграцию IDE с утилитой CMake. IDE должна знать путь до файла CMakeLists.txt проекта. Системы IDE, где интегрирована CMake, часто предоставляют свои собственные инструменты для сборки (CMake вызывает эти "генераторы") как часть IDE для сборки исходных файлов.

Когда в IDE добавляются пользовательские шаги, не относящиеся к сборке, наподобие "flash", рекомендуется использовать idf.py для таких "специальных" команд.

Более подробную информацию по поводу интеграции ESP-IDF с утилитой CMake в IDE, см. далее "Метаданные системы сборки".

Настройка интерпретатора Python. ESP-IDF хорошо работает с Python версии 3.7+.

Скрипт idf.py, как и другие скрипты Python, будут работать с интерпретатором Python по умолчанию, т. е. с командой python. Вы можете переключиться на другой интерпретатор наподобие python3 $IDF_PATH/tools/idf.py ..., или можете настроить псевдоним командной строки (shell alias) или другой скрипт для упрощения команды.

Если CMake используется напрямую, то запуск cmake -D PYTHON=python3 ... приведет к отмене интерпретатора Python по умолчанию.

Если используется IDE с утилитой CMake, установка значения PYTHON в качестве отмена кэша *CMake cache override) в интерфейсе пользователя IDE отменит интерпретатор Python по умолчанию.

Для более общего управления версией Python через командную строку используйте инструменты pyenv или virtualenv. Это позволит Вам поменять версию Python по умолчанию.

Возможные проблемы. Иногда при использовании idf.py может возникать ошибка ImportError, описанная ниже.

Traceback (most recent call last):
  File "/Users/user_name/e/esp-idf/tools/kconfig_new/confgen.py", line 27, in < module>
    import kconfiglib
ImportError: bad magic number in 'kconfiglib': b'\x03\xf3\r\n'

Это исключение часто вызывается файлами .pyc, сгенерированными разными версиями Python. Чтобы решить эту проблему, запустите команду:

idf.py python-clean

Корневой каталог проекта может выглядеть примерно так:

- myProject/
             - CMakeLists.txt
             - sdkconfig
             - 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. В файле sdkconfig хранится конфигурация всех компонентов в проекте (включая саму ESP-IDF). Файл sdkconfig может быть добавлен, или может быть не добавлен под управление системы контроля версий исходного кода проекта.

• Опциональная директория components содержит компоненты, которые являются частью проекта. Проект не обязательно должен содержать пользовательские компоненты такого типа, однако это может быть полезно для структурирования повторно используемого кода, или для подключения компонентов сторонних разработчиков, не являющихся частью ESP-IDF. Альтернативно может быть установлена переменная EXTRA_COMPONENT_DIRS на верхнем уровне в файле CMakeLists.txt, чтобы искать компоненты в других местах. Для дополнительной информации см. далее секцию "Переименование компонента main". Если у Вас в проекте много исходных файлов, то рекомендуется разбивать их на компоненты вместо того, чтобы сваливать все в кучу в main.

main это директория специального компонента, которая содержит исходный код самого проекта. Здесь main это имя по умолчанию, CMake-переменная COMPONENT_DIRS включает этот компонент, но можно изменить эту переменную.

build это директория, где сохраняется файлы вывода сборки. Эта директория создается скриптом idf.py, если она еще не существует. CMake конфигурирует проект, и генерирует в этой директории промежуточные файлы сборки. Таким образом, после запуска основного процесса сборки эта директория будет также содержать как промежуточные объектные файлы и библиотеки, так и конечные выходные двоичные файлы. Эта директория обычно не добавляется под управление системы контроля версий, и не распространяется вместе с исходным кодом проекта.

• Каталоги компонентов (component1, component2) каждый содержат файл CMakeLists.txt. Этот файл содержит определения переменных для управления процессом сборки компонента, и интеграции его в целый проект. Для получения дополнительной информации см. "Файлы CMakeLists компонента".

Каждый компонент также может подключать файл Kconfig, определяющий опции конфигурации компонента, которые можно установить через menuconfig. Некоторые компоненты могут также включать файлы Kconfig.projbuild и project_include.cmake, это специальные файлы для переназначения частей проекта.

[Файл CMakeLists проекта]

Каждый проект содержит один файл верхнего уровня CMakeLists.txt, который содержит настройки сборки для всего проекта. По умолчанию этот файл весьма минимален:

cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)

Обязательные части. Для каждого проекта обязательно нужно подключать 3 строки, показанные выше. Они означают следующее:

cmake_minimum_required(VERSION 3.5) говорит для CMake о минимальной версии, которая требуется для сборки проекта. ESP-IDF разработан для взаимодействия с CMake 3.5 или более свежей версией. Эта строка должна быть первой в файле CMakeLists.txt верхнего уровня.

include($ENV{IDF_PATH}/tools/cmake/project.cmake) использует остальные функциональные возможности CMake для настройки проекта, обнаружения всех компонентов и т. д.

project(myProject) создает сам проект, и указывает имя проекта. Имя проекта используется для конечных выходных двоичных файлов приложения - например myProject.elf, myProject.bin. В файле CMakeLists может быть определен только один проект.

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

# Разработано для подключения из файла CMakeLists.txt приложения IDF
cmake_minimum_required(VERSION 3.5)
 
include(${CMAKE_CURRENT_LIST_DIR}/targets.cmake)
# Инициализирует цель сборки (build target), чтобы сборка использовала
# переменную окружения или переданное снаружи значение.
__target_init()
 
# Простое подключение этого файла CMake устанавливает некоторые свойства
# внутреннего построения. Эти свойства могут быть изменены между этим
# подключением и вызовом idf_build_process.
include(${CMAKE_CURRENT_LIST_DIR}/idf.cmake)
 
# Только для совместимости: здесь устанавливается переменная PYTHON,
# новый код должен использовать idf_build_get_property(variable PYTHON)
idf_build_get_property(PYTHON PYTHON)
if(NOT PYTHON)
    message(FATAL_ERROR "Internal error, PYTHON build property not set correctly.")|
endif()
 
# Устаревшая переменная, оставленная для совместимости
set(IDFTOOL ${PYTHON} "${IDF_PATH}/tools/idf.py")
 
# При обработке проверка требуемых модулей Python может быть отключена,
# если проверка была уже выполнена снаружи.
if(PYTHON_DEPS_CHECKED)
    idf_build_set_property(__CHECK_PYTHON 0)
endif()
 
# Сохранение аргументов CMake, которые надо передать также во все
# субпроекты CMake (bootloader, ULP, etc)
#
# Нельзя определить, был ли вызван CMake с ключом --warn-uninitialized,
# поэтому для получения этих предупреждений в субпроектах мы также задаем
# переменную кэша, а затем проверяем это.
if(WARN_UNINITIALIZED)
    idf_build_set_property(EXTRA_CMAKE_ARGS --warn-uninitialized)
else()
    idf_build_set_property(EXTRA_CMAKE_ARGS "")
endif()
 
#
# Получение версии проекта либо из файла version, либо из ревизии Git.
# Это передается в вызов idf_build_process. Здесь также устанавливаются
# зависимости, когда меняется файл version (если он используется).
#
function(__project_get_revision var)
    set(_project_path "${CMAKE_CURRENT_LIST_DIR}")
    if(NOT DEFINED PROJECT_VER)
        if(EXISTS "${_project_path}/version.txt")
            file(STRINGS "${_project_path}/version.txt" PROJECT_VER)
            set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_project_path}/version.txt")
        else()
            git_describe(PROJECT_VER_GIT "${_project_path}")
            if(PROJECT_VER_GIT)
                set(PROJECT_VER ${PROJECT_VER_GIT})
            else()
                message(STATUS "Project is not inside a git repository, or git repository has no commits;"
                        " will not use 'git describe' to determine PROJECT_VER.")
                set(PROJECT_VER 1)
            endif()
        endif()
    endif()
    set(${var} "${PROJECT_VER}" PARENT_SCOPE)
endfunction()
 
#
# Вывод компонентов сборки для пользователя. Генерирует файлы для вовлечения
# idf_monitor.py, который дублирует обзор некоторых более важных свойств сборки.
#
function(__project_info test_components)
    idf_build_get_property(prefix __PREFIX)
    idf_build_get_property(_build_components BUILD_COMPONENTS)
    idf_build_get_property(build_dir BUILD_DIR)
    idf_build_get_property(idf_path IDF_PATH)
list(SORT _build_components)
unset(build_components) unset(build_component_paths)
foreach(build_component ${_build_components}) __component_get_target(component_target "${build_component}") __component_get_property(_name ${component_target} COMPONENT_NAME) __component_get_property(_prefix ${component_target} __PREFIX) __component_get_property(_alias ${component_target} COMPONENT_ALIAS) __component_get_property(_dir ${component_target} COMPONENT_DIR)
if(_alias IN_LIST test_components) list(APPEND test_component_paths ${_dir}) else() if(_prefix STREQUAL prefix) set(component ${_name}) else() set(component ${_alias}) endif() list(APPEND build_components ${component}) list(APPEND build_component_paths ${_dir}) endif() endforeach()
set(PROJECT_NAME ${CMAKE_PROJECT_NAME}) idf_build_get_property(PROJECT_PATH PROJECT_DIR) idf_build_get_property(BUILD_DIR BUILD_DIR) idf_build_get_property(SDKCONFIG SDKCONFIG) idf_build_get_property(SDKCONFIG_DEFAULTS SDKCONFIG_DEFAULTS) idf_build_get_property(PROJECT_EXECUTABLE EXECUTABLE) set(PROJECT_BIN ${CMAKE_PROJECT_NAME}.bin) idf_build_get_property(IDF_VER IDF_VER)
idf_build_get_property(sdkconfig_cmake SDKCONFIG_CMAKE) include(${sdkconfig_cmake}) idf_build_get_property(COMPONENT_KCONFIGS KCONFIGS) idf_build_get_property(COMPONENT_KCONFIGS_PROJBUILD KCONFIG_PROJBUILDS)
# Write project description JSON file idf_build_get_property(build_dir BUILD_DIR) make_json_list("${build_components};${test_components}" build_components_json) make_json_list("${build_component_paths};${test_component_paths}" build_component_paths_json) configure_file("${idf_path}/tools/cmake/project_description.json.in" "${build_dir}/project_description.json")
# Теперь у нас есть следующие переменные, связанные с компонентами: # # build_components список компонентов для подключения в сборку. # build_component_paths пути для всех этих компонентов, полученные # из файла зависимостей компонента. # # Печать списка найденных компонентов и проверка компонентов string(REPLACE ";" " " build_components "${build_components}") string(REPLACE ";" " " build_component_paths "${build_component_paths}") message(STATUS "Components: ${build_components}") message(STATUS "Component paths: ${build_component_paths}")
if(test_components) string(REPLACE ";" " " test_components "${test_components}") string(REPLACE ";" " " test_component_paths "${test_component_paths}") message(STATUS "Test components: ${test_components}") message(STATUS "Test component paths: ${test_component_paths}") endif() endfunction()
function(__project_init components_var test_components_var) # Используйте EXTRA_CFLAGS, EXTRA_CXXFLAGS и EXTRA_CPPFLAGS для добавления # более приоритетных опций для компилятора. EXTRA_CPPFLAGS используется # как для C, так и для C++. В отличие от переменных окружения # CFLAGS/CXXFLAGS/CPPFLAGS, которые работают как для хоста, так # и для сборки target, эти работают только для сборки target set(extra_cflags "$ENV{EXTRA_CFLAGS}") set(extra_cxxflags "$ENV{EXTRA_CXXFLAGS}") set(extra_cppflags "$ENV{EXTRA_CPPFLAGS}")
spaces2list(extra_cflags) spaces2list(extra_cxxflags) spaces2list(extra_cppflags)
idf_build_set_property(C_COMPILE_OPTIONS "${extra_cflags}" APPEND) idf_build_set_property(CXX_COMPILE_OPTIONS "${extra_cxxflags}" APPEND) idf_build_set_property(COMPILE_OPTIONS "${extra_cppflags}" APPEND)
function(__project_component_dir component_dir) get_filename_component(component_dir "${component_dir}" ABSOLUTE) # Сама директория это допустимый компонент idf if(EXISTS ${component_dir}/CMakeLists.txt) idf_build_component(${component_dir}) else() # Иначе проверка, являются ли подпапки потенциальными компонентами idf file(GLOB component_dirs ${component_dir}/*) foreach(component_dir ${component_dirs}) if(IS_DIRECTORY ${component_dir}) __component_dir_quick_check(is_component ${component_dir}) if(is_component) idf_build_component(${component_dir}) endif() endif() endforeach() endif() endfunction()
# Добавление директорий компонента для сборки с учетом фильтров # компонентов, исключений, дополнительных директорий, и т. д., # переданных из корневого CMakeLists.txt. if(COMPONENT_DIRS) # Пользователь хочет полностью переопределить место извлечения компонентов. spaces2list(COMPONENT_DIRS) idf_build_set_property(__COMPONENT_TARGETS "") foreach(component_dir ${COMPONENT_DIRS}) __project_component_dir(${component_dir}) endforeach() else() if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/main") __project_component_dir("${CMAKE_CURRENT_LIST_DIR}/main") endif()
spaces2list(EXTRA_COMPONENT_DIRS) foreach(component_dir ${EXTRA_COMPONENT_DIRS}) __project_component_dir("${component_dir}") endforeach()
# Поиск компонентов в обычных местах: CMAKE_CURRENT_LIST_DIR/main, # дополнительных директориях компонентов, и CMAKE_CURRENT_LIST_DIR/components __project_component_dir("${CMAKE_CURRENT_LIST_DIR}/components") endif()
# Для компонентов загрузчика нам нужно только настроить файлы Kconfig. # Действительно, загрузчик в настоящее время компилируется как субпроект, # поэтому его компоненты не являются частью основного проекта. # Однако чтобы иметь возможность конфигурировать эти компоненты загрузчика # с помощью menuconfig, нам сейчас надо просмотреть файлы, относящиеся # к файлу Kconfig. file(GLOB bootloader_component_dirs "${CMAKE_CURRENT_LIST_DIR}/bootloader_components/*") list(SORT bootloader_component_dirs) foreach(bootloader_component_dir ${bootloader_component_dirs}) if(IS_DIRECTORY ${bootloader_component_dir}) __component_dir_quick_check(is_component ${bootloader_component_dir}) if(is_component) __kconfig_bootloader_component_add("${bootloader_component_dir}") endif() endif() endforeach()
spaces2list(COMPONENTS) spaces2list(EXCLUDE_COMPONENTS) idf_build_get_property(component_targets __COMPONENT_TARGETS) foreach(component_target ${component_targets}) __component_get_property(component_name ${component_target} COMPONENT_NAME) set(include 1) if(COMPONENTS AND NOT component_name IN_LIST COMPONENTS) set(include 0) endif() if(EXCLUDE_COMPONENTS AND component_name IN_LIST EXCLUDE_COMPONENTS) set(include 0) endif() if(include) list(APPEND components ${component_name}) endif() endforeach()
if(TESTS_ALL OR BUILD_TESTS OR TEST_COMPONENTS OR TEST_EXCLUDE_COMPONENTS) spaces2list(TEST_COMPONENTS) spaces2list(TEST_EXCLUDE_COMPONENTS) idf_build_get_property(component_targets __COMPONENT_TARGETS) foreach(component_target ${component_targets}) __component_get_property(component_dir ${component_target} COMPONENT_DIR) __component_get_property(component_name ${component_target} COMPONENT_NAME) if(component_name IN_LIST components) set(include 1) if(TEST_COMPONENTS AND NOT component_name IN_LIST TEST_COMPONENTS) set(include 0) endif() if(TEST_EXCLUDE_COMPONENTS AND component_name IN_LIST TEST_EXCLUDE_COMPONENTS) set(include 0) endif() if(include AND EXISTS ${component_dir}/test) __component_add(${component_dir}/test ${component_name}) list(APPEND test_components ${component_name}::test) endif() endif() endforeach() endif()
set(${components_var} "${components}" PARENT_SCOPE) set(${test_components_var} "${test_components}" PARENT_SCOPE) endfunction()

# Трюк для временного переопределения project(). Затем функции переназначаются
# в CMake, к оригиналам все еще можно получить доступ через нижнее подчеркивание
# перед функцией с таким же именем. Следующие строки гарантируют, что __project
# вызывает оригинальную project().
# См. https://cmake.org/pipermail/cmake/2015-October/061751.html.
function(project)
endfunction()
function(_project) endfunction() macro(project project_name) # Инициализация проекта, подготовка аргумента COMPONENTS для idf_build_process(), # вызываемого позже с использованием внешних COMPONENT_DIRS, COMPONENTS_DIRS, # EXTRA_COMPONENTS_DIR, EXTRA_COMPONENTS_DIRS, COMPONENTS, EXLUDE_COMPONENTS, # TEST_COMPONENTS, TEST_EXLUDE_COMPONENTS, TESTS_ALL, BUILD_TESTS __project_init(components test_components)
__target_set_toolchain()
if(CCACHE_ENABLE) find_program(CCACHE_FOUND ccache) if(CCACHE_FOUND) message(STATUS "ccache will be used for faster recompilation") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) else() message(WARNING "enabled ccache in build but ccache program not found") endif() endif()
# Фактически вызывает project() __project(${project_name} C CXX ASM)
# Генерирует compile_commands.json (нужен после вызова project()). set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Поскольку компоненты могут импортировать библиотеки сторонних разработчиков, # оригинальное определение project() должно быть восстановлено перед вызовом # для добавления компонентов к сборке. function(project) set(project_ARGV ARGV) __project(${${project_ARGV}})
# Установка переменных, которые нормально устанавливает project(), # задокументированных в документации по командам. # # https://cmake.org/cmake/help/v3.5/command/project.html # # Существует некоторый нюанс, когда дело доходит до установки переменных # версии с точки зрения того, имеет ли CMP0048 значение OLD или NEW. # Однако правильное поведение должно быть уже обработано оригинальным # вызовом project(), и мы просто эхом выводим значения, в которые были # установлены эти переменные. set(PROJECT_NAME "${PROJECT_NAME}" PARENT_SCOPE) set(PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}" PARENT_SCOPE) set(PROJECT_SOURCE_DIR "${PROJECT_SOURCE_DIR}" PARENT_SCOPE) set(PROJECT_VERSION "${PROJECT_VERSION}" PARENT_SCOPE) set(PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}" PARENT_SCOPE) set(PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR}" PARENT_SCOPE) set(PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}" PARENT_SCOPE) set(PROJECT_VERSION_TWEAK "${PROJECT_VERSION_TWEAK}" PARENT_SCOPE)
set(${PROJECT_NAME}_BINARY_DIR "${${PROJECT_NAME}_BINARY_DIR}" PARENT_SCOPE) set(${PROJECT_NAME}_SOURCE_DIR "${${PROJECT_NAME}_SOURCE_DIR}" PARENT_SCOPE) set(${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION}" PARENT_SCOPE) set(${PROJECT_NAME}_VERSION_MAJOR "${${PROJECT_NAME}_VERSION_MAJOR}" PARENT_SCOPE) set(${PROJECT_NAME}_VERSION_MINOR "${${PROJECT_NAME}_VERSION_MINOR}" PARENT_SCOPE) set(${PROJECT_NAME}_VERSION_PATCH "${${PROJECT_NAME}_VERSION_PATCH}" PARENT_SCOPE) set(${PROJECT_NAME}_VERSION_TWEAK "${${PROJECT_NAME}_VERSION_TWEAK}" PARENT_SCOPE) endfunction()
# Подготовка следующих аргументов для вызова idf_build_process() # с использованием следующих переменных пользователя: # # SDKCONFIG_DEFAULTS из внешней SDKCONFIG_DEFAULTS # SDKCONFIG из внешней SDKCONFIG # BUILD_DIR устанавливается в директорию бинарников проекта # # PROJECT_NAME берется из имени, переданного в вызов project() # PROJECT_DIR устанавливается в текущую директорию # PROJECT_VER из текста файла version, либо ревизии git текущего репозитория set(_sdkconfig_defaults "$ENV{SDKCONFIG_DEFAULTS}")
if(NOT _sdkconfig_defaults) if(EXISTS "${CMAKE_SOURCE_DIR}/sdkconfig.defaults") set(_sdkconfig_defaults "${CMAKE_SOURCE_DIR}/sdkconfig.defaults") else() set(_sdkconfig_defaults "") endif() endif()
if(SDKCONFIG_DEFAULTS) set(_sdkconfig_defaults "${SDKCONFIG_DEFAULTS}") endif()
foreach(sdkconfig_default ${_sdkconfig_defaults}) get_filename_component(sdkconfig_default "${sdkconfig_default}" ABSOLUTE) if(NOT EXISTS "${sdkconfig_default}") message(FATAL_ERROR "SDKCONFIG_DEFAULTS '${sdkconfig_default}' does not exist.") endif() list(APPEND sdkconfig_defaults ${sdkconfig_default}) endforeach()
if(SDKCONFIG) get_filename_component(sdkconfig "${SDKCONFIG}" ABSOLUTE) else() set(sdkconfig "${CMAKE_CURRENT_LIST_DIR}/sdkconfig") endif()
if(BUILD_DIR) get_filename_component(build_dir "${BUILD_DIR}" ABSOLUTE) if(NOT EXISTS "${build_dir}") message(FATAL_ERROR "BUILD_DIR '${build_dir}' does not exist.") endif() else() set(build_dir ${CMAKE_BINARY_DIR}) endif()
__project_get_revision(project_ver)
message(STATUS "Building ESP-IDF components for target ${IDF_TARGET}")
idf_build_process(${IDF_TARGET} SDKCONFIG_DEFAULTS "${sdkconfig_defaults}" SDKCONFIG ${sdkconfig} BUILD_DIR ${build_dir} PROJECT_NAME ${CMAKE_PROJECT_NAME} PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR} PROJECT_VER "${project_ver}" COMPONENTS "${components};${test_components}")
# Специальная обработка для компонента main стандартных проектов # (не часто системы сборки ядра) - зависит ли она от каждого # другого компонента в сборке. Это поведение стандартного проекта # сделано для удобства. Таким образом, это выполняется вне основной # системы сборки, так что обработка компонентов происходит аналогично. # # Это поведение должно быть только когда пользователь не установил # вручную REQUIRES/PRIV_REQUIRES. idf_build_get_property(build_components BUILD_COMPONENT_ALIASES) if(idf::main IN_LIST build_components) __component_get_target(main_target idf::main) __component_get_property(reqs ${main_target} REQUIRES) __component_get_property(priv_reqs ${main_target} PRIV_REQUIRES) idf_build_get_property(common_reqs __COMPONENT_REQUIRES_COMMON) if(reqs STREQUAL common_reqs AND NOT priv_reqs) # если пользователь не установил любые требования if(test_components) list(REMOVE_ITEM build_components ${test_components}) endif() list(REMOVE_ITEM build_components idf::main) __component_get_property(lib ${main_target} COMPONENT_LIB) set_property(TARGET ${lib} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${build_components}") get_property(type TARGET ${lib} PROPERTY TYPE) if(type STREQUAL STATIC_LIBRARY) set_property(TARGET ${lib} APPEND PROPERTY LINK_LIBRARIES "${build_components}") endif() endif() endif()
set(project_elf ${CMAKE_PROJECT_NAME}.elf)
# Создание пустого файла, чтобы удовлетворить требование CMake иметь # исходный файл при добавлении выполняемого кода. Это также используется # idf_size.py для детектирования target set(project_elf_src ${CMAKE_BINARY_DIR}/project_elf_src_${IDF_TARGET}.c) add_custom_command(OUTPUT ${project_elf_src} COMMAND ${CMAKE_COMMAND} -E touch ${project_elf_src} VERBATIM) add_custom_target(_project_elf_src DEPENDS "${project_elf_src}") add_executable(${project_elf} "${project_elf_src}") add_dependencies(${project_elf} _project_elf_src) if(__PROJECT_GROUP_LINK_COMPONENTS) target_link_libraries(${project_elf} "-Wl,--start-group") endif()
if(test_components) target_link_libraries(${project_elf} "-Wl,--whole-archive") foreach(test_component ${test_components}) if(TARGET ${test_component}) target_link_libraries(${project_elf} ${test_component}) endif() endforeach() target_link_libraries(${project_elf} "-Wl,--no-whole-archive") endif()
idf_build_get_property(build_components BUILD_COMPONENT_ALIASES) if(test_components) list(REMOVE_ITEM build_components ${test_components}) endif() target_link_libraries(${project_elf} ${build_components})
if(CMAKE_C_COMPILER_ID STREQUAL "GNU") set(mapfile "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.map") target_link_libraries(${project_elf} "-Wl,--cref" "-Wl,--Map=\"${mapfile}\"") endif()
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${mapfile}" "${project_elf_src}")
idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(python PYTHON)
set(idf_size ${python} ${idf_path}/tools/idf_size.py) if(DEFINED OUTPUT_JSON AND OUTPUT_JSON) list(APPEND idf_size "--json") endif()
# Добавление размера target-ов, зависит от map-файла, запуск idf_size.py add_custom_target(size DEPENDS ${mapfile} COMMAND ${idf_size} ${mapfile} ) add_custom_target(size-files DEPENDS ${mapfile} COMMAND ${idf_size} --files ${mapfile} ) add_custom_target(size-components DEPENDS ${mapfile} COMMAND ${idf_size} --archives ${mapfile} )
unset(idf_size)
# Добавление сборки DFU и прошивки target-ов __add_dfu_targets()
# Добавление target-ов сборки UF2 __add_uf2_targets()
idf_build_executable(${project_elf})
__project_info("${test_components}") endmacro()

COMPONENT_DIRS: директории для поиска компонентов. По умолчанию установлено в IDF_PATH/components, PROJECT_DIR/components и EXTRA_COMPONENT_DIRS. Переназначьте эту переменную, если нее хотите искать компоненты в этих местах.

EXTRA_COMPONENT_DIRS: опциональный список дополнительных директорий для поиска компонентов. Пути могут быть либо относительно директории проекта, либо абсолютными.

COMPONENTS: список имен компонентов для сборки с проектом. По умолчанию для всех компонентов, найденных в директориях COMPONENT_DIRS. Используйте эту переменную для "урезания" проекта, чтобы ускорить сборку. Обратите внимание, что компонент, который "требует" другой компонент через аргументы REQUIRES или PRIV_REQUIRES при регистрации компонента, будет автоматически добавлен в этот список, так что список COMPONENTS может быть очень коротким.

Любые пути в этих переменных могут быть абсолютными, либо установленными относительно директории проекта.

Для установки этих переменных используйте команду set утилиты cmake, например set(VARIABLE "VALUE"). Команды set() должны быть помещены после строки cmake_minimum(...), но перед строкой include(...).

Переименование компонента main. Система сборки предоставляет специальную обработку для компонента main. Это компонент, который будет автоматически добавлен в сборку при условии, что он находится в ожидаемом месте, в каталоге PROJECT_DIR/main. Все другие компоненты в сборке также добавляются как зависимости для 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.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)
idf_build_set_property(COMPILE_OPTIONS "-Wno-error" APPEND)

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

[Файлы CMakeLists компонента]

Каждый проект состоит из одного или большего количества компонентов. Компоненты могут быть частью ESP-IDF, частью директории components проекта, или добавлены из пользовательских директорий компонента (см. описание выше).

Компонентом является любая директория в списке COMPONENT_DIRS, которая содержит файл CMakeLists.txt.

Поиск компонентов. Список директорий в COMPONENT_DIRS просматривается для определения компонентов проекта. Директории в этом списке могут быть либо компонентами сами по себе (например, они могут содержать файл CMakeLists.txt), или они могут быть директориями верхнего уровня, у которых подкаталоги являются компонентами.

Когда CMake запущен для конфигурирования проекта, он выводит в лог компоненты, подключенные в сборку. Этот список может быть полезен для отладки подключения/исключения (inclusion/exclusion) определенных компонентов.

Несколько компонентов с одинаковым именем. Когда среда ESP-IDF собирает все компоненты для компиляции, она это делает в порядке, указанном в COMPONENT_DIRS; по умолчанию это означает, что сначала обрабатываются внутренние компоненты ESP-IDF (IDF_PATH/components), затем любые компоненты, указанные в EXTRA_COMPONENT_DIRS, и наконец компоненты проекта (PROJECT_DIR/components). Если две или большее количество этих директорий содержат подкаталоги с одинаковым именем, то используется компонент из последнего места в списке поиска. Это позволит, например, переназначить компоненты ESP-IDF модифицированной версией путем копирования этого компонента из директории components среды ESP-IDF в директорию components проекта, где их исходный код может быть затем изменен. Если поступить так, то сама директория ESP-IDF останется нетронутой.

Замечание: если в существующем проекте компонент переопределен путем его перемещения в новое местоположение, то проект не увидит автоматически новый путь компонента. Запустите idf.py reconfigure (или удалите папку build проекта), и выполните сборку заново.

Минимальный 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 это список директорий для добавления в глобальный путь поиска подключаемых файлов для любого компонента, который требует этот компонент, и также исходных файлов main.

REQUIRES: на самом деле это не требуется, однако очень часто необходимо для декларации, что другие компоненты будут использовать этот компонент. См. далее "Требования компонента".

Библиотека с именем компонента будет собрана и линкована в конечное приложение.

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

Есть и другие аргументы, которые можно передать в idf_component_register, см. далее idf-component-commands.

Для более полных примеров см. врезки "Пример требований компонента" и "Пример CMakeLists компонента".

Предварительная установка переменных компонента. Следующие переменные, специфичные для компонента, доступны внутри CMakeLists компонента, однако они не должны быть модифицированы:

COMPONENT_DIR: директория компонента. Вычисляется в абсолютный путь до директории, содержащей CMakeLists.txt. Путь до компонента не должен содержать пробелы. То же самое относится и к переменной CMAKE_CURRENT_SOURCE_DIR.
COMPONENT_NAME: имя компонента, оно совпадает с именем директории компонента.
COMPONENT_ALIAS: псевдоним библиотеки, внутренне созданной системой сборки для компонента.
COMPONENT_LIB: имя библиотеки, внутренне созданной системой сборки для компонента.

Следующие переменные устанавливаются на уровне проекта, однако доступны в CMakeLists компонента:

CONFIG_*: каждое значение в конфигурации проекта имеет соответствующую переменную, доступную в cmake. Все их имена начинаются с префикса CONFIG_ (дополнительную информацию см. в [3]).
ESP_PLATFORM: устанавливается в 1, когда файл CMake обрабатывается системой сборки ESP-IDF.

Переменные проекта/сборки. Ниже приведены некоторые переменные проекта/сборки, доступные в качестве свойств сборки, значения которых можно запросить с помощью idf_build_get_property из файла CMakeLists.txt компонента:

PROJECT_NAME: имя проекта, как оно установлено в файле CMakeLists.txt проекта.

PROJECT_DIR: абсолютный путь до директории проекта, содержащий CMakeLists проекта. Совпадает с переменной CMAKE_SOURCE_DIR.

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

IDF_VER: Git-версия ESP-IDF (генерируется командой git describe).

IDF_VERSION_MAJOR, IDF_VERSION_MINOR, IDF_VERSION_PATCH: компоненты версии ESP-IDF для использования в условных выражениях. Обратите внимание, что эта информация менее точна, чем информация в переменной IDF_VER. v4.0-dev-*, v4.0-beta1, v4.0-rc1 и v4.0 получат такие же значения, как и переменные IDF_VERSION_*, но другие значения IDF_VER.

IDF_TARGET: имя target, для которой собирается проект.

PROJECT_VER: версия проекта.

- Если установлена опция CONFIG_APP_PROJECT_VER_FROM_CONFIG, то будет использоваться значение CONFIG_APP_PROJECT_VER.
- Иначе, если установлена переменная PROJECT_VER в файле CMakeLists.txt, то будет использоваться её значение.
- Иначе, если существует файл PROJECT_DIR/version.txt, то его содержимое будет использоваться как PROJECT_VER.
- Иначе, если проект находится внутри репозитория Git, то будет использоваться вывод git describe.
- Иначе PROJECT_VER будет "1".

Другие свойства проекта описаны далее в секции idf-build-properties.

Управление компиляцией компонента. Для передачи компилятору опций, когда компилируются исходные файлы, принадлежащие определенному компоненту, используйте функцию 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
)

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

Когда используются эти команды, поместите их после вызова idf_component_register в файл CMakeLists компонента.

[Конфигурация компонента]

У каждого компонента может быть также файл Kconfig, вместе с файлом CMakeLists.txt. В файле Kconfig находятся настройки конфигурации для добавления в меню конфигурации этого компонента.

Эти настройки находятся в меню "Component Settings", когда запущен menuconfig.

Для создания файла Kconfig для компонента проще всего отталкиваться от одного из файлов Kconfig, поставляемых вместе с ESP-IDF.

Для примера см. далее "Добавление условной конфигурации" во врезке "Пример CMakeLists компонента".

[Определение препроцессора]

Система сборки ESP-IDF добавляет следующие определения препроцессора в командную строку:

ESP_PLATFORM: может использоваться для детектирования факта, что сборка происходит под управления ESP-IDF.
IDF_VER: определено для строки версии git. Например v2.0 для версии с тегами, или v1.0-275-g0efaa4f для произвольного commit.

[Требования компонента]

Когда компилируется каждый компонент, система сборки ESP-IDF рекурсивно вычисляет зависимости. Это значит, что каждый компонент должен объявлять компоненты, от которых он зависит ("requires", требует).

Когда пишется компонент. Пример регистрации компонента:

idf_component_register(...
                       REQUIRES mbedtls
                       PRIV_REQUIRES console spiffs)

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

• PRIV_REQUIRES должен быть установлен для всех компонентов, заголовочные файлы которых подключены директивой #include из любых исходных файлов в этом компоненте, если они уже не перечислены в REQUIRES. Также любой компонент, который требует линовки, чтобы этот компонент работал корректно.

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

• Нельзя установить ни одну, ни обе переменные REQUIRES. Если у компонента нет требований, кроме общих требований компонента, необходимых для RTOS, libc, и т. д.

Если компоненты поддерживают только некоторые чипы target (значения IDF_TARGET), то это можно указать переменной REQUIRED_IDF_TARGETS в вызове idf_component_register, чтобы выразить эти требования. В этом случае система сборки будет генерировать ошибку, если компонент включен в сборку, но не поддерживает выбранную target.

Замечание: в терминах CMake переменные REQUIRES и PRIV_REQUIRES это аппроксимированные обертки вокруг CMake-функций target_link_libraries(... PUBLIC ...) и target_link_libraries(... PRIVATE ...).

Представим компонент car, который использует компонент engine component, который в свою очередь использует компонент:

- 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. Заголовочный файл car.h это публичный интерфейс для компонента car. Этот заголовок подключает 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 дает список публичных директорий подключения для этого компонента. По той причине, что публичный интерфейс это car.h, здесь перечислен каталог, где находится car.h.

• REQUIRES предоставляет список компонентов, требуемых публичным интерфейсом этого компонента. Из-за того, что car.h это публичный заголовок, и в нем подключается заголовок от engine, мы сюда включаем 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 не требуют видимости директорий подключения spark_plug, чтобы они были добавлены в пути поиска компилятора. Это может ускорить компиляцию, и не даст разрастаться командным строкам компилятора сверх необходимости.

Компонент spark_plug. Этот компонент вообще ни от чего не зависит. У него есть публичный заголовочный файл spark_plug.h, но он не подключает заголовков от любого другого компонента.

Это означает, что файл spark_plug/CMakeLists.txt не нуждается ни в REQUIRES, ни в PRIV_REQUIRES:

idf_component_register(SRCS "spark_plug.c"
                  INCLUDE_DIRS ".")

[Директории подключения исходного файла]

Каждый исходный файл компонента компилируется с этими директориями поиска подключаемых файлов, как указано в аргументах idf_component_register:

idf_component_register(..
                       INCLUDE_DIRS "include"
                       PRIV_INCLUDE_DIRS "other")

• Директории подключения INCLUDE_DIRS и PRIV_INCLUDE_DIRS текущего компонента.
• Директории INCLUDE_DIRS, принадлежащие всем другим компонентам, перечислены в параметрах REQUIRES и PRIV_REQUIRES (например все public и private зависимости текущего компонента).
• Рекурсивно, все INCLUDE_DIRS этих компонентов REQUIRES (ТРЕБУЮТ) списков (т.е. все общедоступные зависимости зависимостей этого компонента рекурсивно расширены).

Требования компонента main. Компонент с именем main специальный, потому что он автоматически требует всех компоненты для своей сборки. Таким образом, не нужно передавать REQUIRES или PRIV_REQUIRES в этот компонент. См. выше секцию "Переименование компонента main" для описания, что должно быть изменено, если компонент main больше не используется.

Общие требования компонента. Чтобы избежать дублирования, каждый компонент автоматически требует некоторых общих (common) компонентов IDF, даже если они не упомянуты явно. Заголовки этих компонентов будут подключены всегда.

Вот список common-компонентов: cxx, newlib, freertos, esp_hw_support, heap, log, soc, hal, esp_rom, esp_common, esp_system, xtensa/riscv.

[Подключение компонентов в сборку]

• По умолчанию в сборку подключается каждый компонент.

• Если Вы установили переменную COMPONENTS в минимальный список компонентов, напрямую используемых проектом, то сборка будет расширена, и также будет включать требуемые компоненты. Полный список компонентов будет следующий:

- Компоненты, явно упомянутые в COMPONENTS.
- Требования (зависимости) этих компонентов (вычисляются рекурсивно).
- Компоненты "common", от которых зависит каждый компонент.

• Установка COMPONENTS на минимальный список требуемых компонентов может значительно уменьшить время компиляции.

Может получиться так, что для проекта нужен компонент A, который требует (через REQUIRES или PRIV_REQUIRES) компонента B, и компонент B требует компонента A. Эта ситуация известна как цикл зависимостей (dependency cycle), или кольцевая зависимость (circular dependency).

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)

Эта строка должна быть помещена в файл CMakeLists.txt после idf_component_register.

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

Обычно достаточно увеличить значение до 3 (по умолчанию 2), но если это не сработает, то попробуйте увеличить это значение дальше. Добавление этой опции увеличит командную строку линкера, и замедлит выполнение стадии линковки.

Продвинутый костыль: Undefined Symbols. Если один или два символа приводят к кольцевой зависимости, и все другие зависимости линейные, то есть альтернативный метод избежать ошибок линкера: указать конкретные символы, необходимые для "обратной" зависимости как неопределенные на стадии линковки.

Например, если компонент A зависит от компонента B, но компонент B также требует обратной ссылки на reverse_ops из компонента A (но больше ничего не требует), то для разрыва цикла зависимостей на стадии линковки Вы можете добавить строку в CMakeLists.txt компонента B наподобие следующей:

# Этот символ предоставлен компонентом A во время линковки
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u reverse_ops")

Аргумент -u означает, что линкер всегда будет подключать этот символ в линковку, независимо от порядка зависимостей.

Эта строка должна быть помещена в файл CMakeLists.txt компонента после idf_component_register.

Если компонент B не требует доступа ни к одному из заголовков компонента A, только линковки некоторого символа (символов), то эта строка может использоваться вместо любого REQUIRES от B к A. Это дополнительно упростит структуру компонентов в системе сборки.

См. документацию [4] для дополнительной информации по этой функции CMake.

[Требования в реализации системы сборки]

• На очень ранней стадии процесса конфигурации CMake работает скрипт expand_requirements.cmake. Этот скрипт делает частичный анализ всех файлов компонентов CMakeLists.txt, и строит граф требований компонентов (в этом графе могут быть циклы, см. врезку "Кольцевые зависимости"). Граф используется для генерации файла component_depends.cmake в директории build.
• Основной процесс CMake затем подключает этот файл, и использует его для определения списка компонентов для включения в сборку (внутренняя переменная BUILD_COMPONENTS). Переменная BUILD_COMPONENTS отсортирована так, что сначала перечислены зависимости, однако поскольку граф зависимостей компонентов имеет циклы, это не может гарантироваться для всех компонентов. Порядок должен быть детерминированным при одинаковом наборе компонентов и зависимостей компонентов.
• Значение BUILD_COMPONENTS выводится в лог утилитой CMake как "Component names: ".
• Затем конфигурация анализируется для компонентов, подключаемых в сборку.
• Каждый компонент обычно подключается в сборку, и файл CMakeLists.txt анализируется снова для добавления в сборку библиотек компонентов.

Порядок зависимости компонента. Порядок следования компонентов в переменной BUILD_COMPONENTS определяет другие упорядочивания сборки:

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

[Переопределение частей проекта]

project_include.cmake. Для компонентов с требованиями к построению, которые должны быть проанализированы перед анализом любого из файлов CMakeLists компонентов, Вы можете создать файл с именем project_include.cmake в директории проекта. Этот файл CMake подключается, когда project.cmake анализирует весь проект.

Файлы project_include.cmake используются внутри ESP-IDF для определения функций сборки проекта целиком, таких как аргументы командной строки утилиты esptool.py, и bootloader "special app".

В отличие от файлов 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 для файлов конфигурации компонента (файлов Component Configuration KConfig). Если Вы хотите подключить опции конфигурации на верхний уровень menuconfig вместо того, добавлять эти опции вставить в подменю "Component Configuration", то это может быть определено в файле KConfig.projbuild рядом с файлом CMakeLists.txt.

Будьте внимательны при добавлении значений конфигурации в этот файл, поскольку они будут подключены по всей конфигурации проекта. Когда возможно, лучше создать файл KConfig для подменю Component Configuration.

Файлы project_include.cmake используются внутри ESP-IDF для определения функций сборки на уровне проекта, таких как аргументы командной строки утилиты esptool.py и bootloader “special app”.

[Компоненты только для конфигурации]

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

[Отладка CMake]

Полную информацию по командам CMake и другим утилитам см. документацию CMake v3.5 [5].

Несколько советов для отладки системы сборки ESP-IDF, основанной на CMake:

• Когда работает CMake, она печатает довольно много диагностической информации, включая списки компонентов и пути компонентов.
• Запуск cmake с опцией -DDEBUG=1 сгенерирует более подробный вывод диагностики из системы сборки IDF.
• Запуск cmake с опцией --trace или --trace-expand предоставит некоторую информацию о потоке управления. См. документацию по командам cmake [5].

Когда файл project.cmake подключается из файла проекта CMakeLists, он определяет некоторые модули утилит и глобальные переменные, и затем установит IDF_PATH, если это не было установлено в системном окружении.

Он также определяет переопределенную пользовательскую версию функции, встроенной в CMake проекта. Эта функция переопределяется для добавления всей специфической для проекта функциональных возможностей ESP-IDF.

Warning On Undefined Variables. По умолчанию idf.py передает флаг --warn-uninitialized в CMake, чтобы выводились предупреждения, если в сборке были ссылки на не определенную переменную (undefined variable). Это может быть полезным для того, чтобы найти ошибки в файлах CMake.

Если Вы не хотите такого поведения, то его можно запретить передачей --no-warnings в 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 так, что если установлена опция CONFIG_FOO_ENABLE_BAR, то модуль добавляется (APPEND) в список перечисления.

Это также может использоваться для выбора или отключения реализации, вот так:

Kconfig:

config ENABLE_LCD_OUTPUT
    bool "Enable LCD output."
    help
        Select this if your board has a 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()

# Нам нужен font, если разрешена либо консоль, либо LCD
if(CONFIG_ENABLE_LCD_CONSOLE OR CONFIG_ENABLE_LCD_PLOT) list(APPEND srcs "font.c")
endif()
 
idf_component_register(SRCS "${srcs}"
                    ...)

Условия компиляции, которые зависят от target. Текущий target доступен для CMake через переменную IDF_TARGET.

Кроме того, если используется target-имя xyz (IDF_TARGET=xyz), то будет установлена Kconfig-переменная CONFIG_IDF_TARGET_XYZ.

Обратите внимание, что dependencies компонента могут зависеть от переменной IDF_TARGET, но не от переменных Kconfig. Также нельзя использовать переменные Kconfig в операторах include файлов CMake, но в таком контексте можно использовать IDF_TARGET.

Генерация исходного кода. Для некоторых компонентов может быть ситуация, когда файл исходного кода не поставляется самим компонентом, для компонента не предоставлен, но должен быть сгенерирован из другого файла. Например, у нашего компонента есть файл заголовка, который состоит из двоичных данных, полученных конвертацией файла BMP некоторой гипотетической утилитой bmp2h. Затем этот файл заголовка подключается в исходный файл 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_MAKE_CLEAN_FILES logo.h)

Этот рецепт был адаптирован из одного ответа CMake FAQ, где есть и другие примеры, которые будут также работать со сборками ESP-IDF. В этом примере logo.h генерируется в текущей директории (подкаталог build), когда logo.bmp поставляется с компонентом, и находится в пути расположения компонента. Из-за того, что logo.h генерируемый файл, он должен быть очищен, когда очищается проект. По этой причине он добавлен к свойству ADDITIONAL_MAKE_CLEAN_FILES.

Замечание: если генерация файлов часть файла проекта CMakeLists.txt file, не CMakeLists.txt компонента, то используйте свойство сборки PROJECT_DIR вместо ${COMPONENT_DIR} и ${PROJECT_NAME}.elf вместо ${COMPONENT_LIB}.)

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

Встраивание двоичных данных. Иногда есть файл с какими-нибудь двоичными или текстовыми данными, которые нужно сделать доступными в компоненте - но по какой-то причине Вы не хотите переформатировать этот файл как исходный код C. Например, это может быть актуально для встраивания в проект русского текста в кодировке Windows ANSI.

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

idf_component_register(...
                       EMBED_FILES server_root_cert.der)

Или если файл это строка, то можно использовать переменную EMBED_TXTFILES. Это будет встраивать содержимое текстового файла как строку, завершаемую нулем (null-terminated string):

idf_component_register(...
                       EMBED_TXTFILES server_root_cert.pem)

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

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 в символьное имя добавляется утилитой objcopy, и это делается одинаково и для текстовых, и для двоичных файлов.

Для встраивания файла в проект, вместо встраивания в компонент, можно вызвать функцию target_add_binary_data примерно так:

target_add_binary_data(myproject.elf "main/data.bin" TEXT)

Разместите эту строку после строки project() в файле CMakeLists.txt проекта. Замените myproject.elf на имя вашего проекта. Последний аргумент может быть TEXT для встраивания null-terminated строки, или BINARY для встраивания содержимого файла как есть.

Пример использования этой техники см. компонент "main" в примере file_serving, файл protocols/http_server/file_serving/main/CMakeLists.txt - в нем 2 файла загружаются во время сборки и линкуются в тело firmware.

Также можно встраивать генерируемый файл:

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, и затем встраивается в target.

Чтобы указать зависимость на target, используйте аргумент 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 гарантирует, что сначала выполнится target.

Размещение кода и данных (Code/Data Placements). В среде ESP-IDF есть функция генерации скрипта линкера (linker script generation), которая позволяет компонентам определять, где будет размещаться его код и данные в адресном пространстве памяти. Это реализуется через файлы фрагментов линкера, которые обрабатываются системой сборки и используются для расширения скрипта линкера, который обеспечивает линковку двоичного файла приложения. См. документацию [6] для руководства быстрого старта и подробного описания этого механизма.

Полное переназначение процесса сборки компонента. Очевидно, что существуют случаи, когда все эти рецепты недостаточны для определенного компонента. Например, когда компонент это в основном обертка вокруг другого компонента от стороннего разработчика, изначально не предназначенного для компиляции в этой системе сборки. В таком случае можно полностью отказаться от системы сборки ESP-IDF путем использования функции CMake, которая называется ExternalProject. Пример файла CMakeLists компонента:

# Внешний процесс сборки для quirc, запускается в директории исходного
# кода SOURCE_DIR, и генерирует 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 ""
    )
 
 # Добавление libquirc.a к процессу сборки
 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_MAKE_CLEAN_FILES
      "${COMPONENT_DIR}/quirc/libquirc.a")

Этот показанный выше файл CMakeLists.txt может использоваться для создания компонента с именем quirc, который собирается в своем проекте quirc под управлением собственного Makefile.

• externalproject_add определяет внешнюю систему сборки.

- SOURCE_DIR, CONFIGURE_COMMAND, BUILD_COMMAND и INSTALL_COMMAND всегда должны быть установлены. CONFIGURE_COMMAND может быть установлена в пустую строку, если у системы сборки нет шага "configure". Для сборок ESP-IDF переменная INSTALL_COMMAND будет обычно пуста.
- Установка BUILD_IN_SOURCE означает, что директория сборки та же, что и директория исходного кода. Иначе Вы можете установить BUILD_DIR.
- Проконсультируйтесь с документацией ExternalProject для дополнительной информации по externalproject_add().

• Второй набор команд добавляет target библиотеки, указывая на "imported" файл библиотеки, собранный внешней системой. Должны быть установлены некоторые свойства, чтобы добавить директории include, и сказать для CMake, где этот файл находится.

• И наконец, генерируемая библиотека добавляется к ADDITIONAL_MAKE_CLEAN_FILES. Это значит, что make clean удалит эту библиотеку (обратите внимание, что другие объектные файлы не будут удалены.

Замечание: когда используется внешний процесс сборки с PSRAM, помните про добавление -mfix-esp32-psram-cache-issue в аргументы командной строки компилятора C. Подробнее про этот флаг см. описание CONFIG_SPIRAM_CACHE_WORKAROUND в документации [3].

Зависимости ExternalProject, очистка сборок. CMake обладает неким необычным поведением, касающимся сборок внешних проектов:

ADDITIONAL_MAKE_CLEAN_FILES работает только когда в качестве системы сборки используется "make". Если используется система сборки Ninja или IDE, то эти файлы не будут удаляться при очистке.

• Однако команды configure и build для ExternalProject всегда будут перезапускаться после очистки (clean).

• Таким образом, рекомендуются 2 альтернативы для конфигурирования команды внешней сборки:

1. Чтобы внешняя команда BUILD_COMMAND выполнила полную очистку всех исходных файлов. Команда сборки будет выполнена, если изменилась любая из зависимостей, переданная в externalproject_add with 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 на верхнем уровне CMakeLists.txt. Если указываете несколько файлов, то используйте точку с запятой в качестве разделителя в списке имен файлов. Имена файлов, в которых не указан полный путь, обрабатываются относительно текущего проекта.

Некоторые примеры IDF подключают файл sdkconfig.ci. Это часть фреймворка тестирования непрерывной интеграции (continuous integration, CI), и она игнорируется обычным процессом сборки.

Умолчания sdkconfig, зависящие от target. В дополнение к файлу sdkconfig.defaults система сборки будет также загружать умолчания из файла sdkconfig.defaults.TARGET_NAME, где TARGET_NAME это значение IDF_TARGET. Например, для esp32 target настройки по умолчанию будут сначала взяты из sdkconfig.defaults, и затем из sdkconfig.defaults.esp32.

Если SDKCONFIG_DEFAULTS используется для переназначения имени файла/файлов умолчаний, то имя зависящего от target умолчаний берется из значения/значений SDKCONFIG_DEFAULTS с использованием правила выше.

[Аргументы процесса прошивки]

Существует несколько сценариев, которыми мы хотим прошить (flash) код firmware в плату target без IDF. Для такого случая мы захотим сохранить собранные бинарники, и использовать их как аргументы для утилит esptool.py и esptool write_flash. Довольно просто написать скрипт для сохранения бинарников и запуска esptool.py.

После запуска сборки проекта директория build содержит двоичные выходные файлы (.bin) для проекта, и также следующие файлы данных, относящиеся к прошивке:

• flash_project_args содержит аргументы для прошивки проекта полностью (app, bootloader, таблица разделов [8], данные PHY, если это сконфигурировано).
• flash_app_args содержит аргументы для прошивки только приложения.
• flash_bootloader_args содержит аргументы для прошивки только загрузчика.

Вы можете передать любые из этих аргументов в утилиту esptool.py следующим образом:

python esptool.py --chip esp32 write_flash @build/flash_project_args

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

Директория build также содержит генерируемый файл flasher_args.json, который содержит flash-информацию проекта в формате JSON. Этот файл используется idf.py, и может также применяться другими утилитами, которым нужна информация по сборке проекта.

[Сборка загрузчика]

Загрузчик (bootloader) это специальный "субпроект" в каталоге /components/bootloader/subproject. У него есть собственный файл CMakeLists.txt, и он собирается в отдельные файлы .ELF и .BIN для основного проекта. Однако он совместно использует свою конфигурацию и директорию сборки build вместе с основным проектом.

Субпроект вставлен как внешний проект из проекта внешнего уровня файлом /components/bootloader/project_include.cmake. Процесс сборки основного проекта (main) запускает CMake для субпроекта, который включает в себя распознавание компонентов (подмножество компонентов main), и генерирует конфиг, специфичный для загрузчика (получается из main sdkconfig).

[Написание Pure CMake компонентов]

Система сборки ESP-IDF "обертывает" CMake концепцией "components" и helper-функциями для автоматической интеграции этих компонентов в сборку проекта.

Однако под концепцией "components" понимается полная система сборки CMake. Однако можно создать компонент, который будет "чистым" (pure) CMake.

Ниже пример минимального файла CMakeLists "pure CMake" компонента с именем json:

add_library(json STATIC
cJSON/cJSON.c
cJSON/cJSON_Utils.c)
 
target_include_directories(json PUBLIC cJSON)

• Это фактически эквивалентно декларации IDF json-компонента /components/json/CMakeLists.txt.
• Этот файл очень простой, потому что состоит из небольшого количества исходных файлов. Для компонентов, у которых большое количество файлов, поведение логики компонентов ESP-IDF может упростить стиль CMakeLists компонента.
• Каждый раз, когда компонент добавляет target библиотеки с именем компонента, система сборки ESP-IDF автоматически добавит это в сборку, откроет публичные директории include, и т. д. Если компонент захочет добавить library с другим именем, зависимости должны быть добавлены вручную командами CMake.

[Использование с компонентами CMake-проектов сторонних разработчиков]

CMake используется для множества open-source проектов на C и C++, их код пользователи могут вставлять в свои приложения. Одно из достоинств, каким обладает система сборки CMake - возможность импорта этих сторонних проектов, иногда даже без какой-либо модификации! Это дает возможность пользователем получить функциональность, которая пока не предоставляется компонентом, или использовать другую библиотеку для достижения такой же функциональности.

Для гипотетической библиотеки foo импорт с целью использования в компоненте main может выглядеть так:

# Регистрация компонента
idf_component_register(...)
 
# Установка значений гипотетических переменных, которые управляют сборкой `foo`:
set(FOO_BUILD_STATIC OFF)
set(FOO_BUILD_TESTS OFF)
 
# Создание и импорт target-ов библиотеки
add_subdirectory(foo)
 
# Публичный линк `foo` в компонент `main`
target_link_libraries(main PUBLIC foo)

Для реального примера посмотрите build_system/cmake/import_lib. Обратите внимание, что для импорта библиотеки нужно выполнить действия, которые могут варьироваться. Рекомендуется прочитать документацию библиотеки для получения инструкций, как импортировать её из других проектов. Также поможет изучение CMakeLists.txt библиотеки и структуры сборки.

Таким же образом можно обернуть стороннюю библиотеку для использования её в качестве компонента. Компонент mbedtls это обертка форка Espressif над оригинальной mbedtls. См. CMakeLists.txt этого компонента.

CMake-переменная ESP_PLATFORM устанавливается всякий раз, когда используется система сборки ESP-IDF. Могут использоваться её проверки, такие как if (ESP_PLATFORM) в обычном коде CMake, если требуется специальная IDF-специфичная логика.

Использование компонентов ESP-IDF из внешних библиотек. Пример выше подразумевает, что внешняя библиотека foo (или tinyxml в случае примера import_lib) не нуждается в использовании ESP-IDF API, кроме общих 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)
  # В ESP-IDF файл bar.c должен подключить esp_spi_flash.h из компонента spi_flash
  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 - имя, которое может использоваться для ссылки на импортированную библиотеку, например при линковки с другими target-ами.
• lib_path - путь до предварительно собранной библиотеки; может быть абсолютным, или указан относительным к директории компонента.

Опциональные аргументы REQUIRES и PRIV_REQUIRES задают зависимость от других компонентов. У них такое же назначение, как у аргументов для idf_component_register.

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

Для полного примера см. build_system/cmake/import_prebuilt.

[Использование ESP-IDF в пользовательских проектах CMake]

ESP-IDF предоставляет шаблон проекта CMake для упрощения создания приложения. Однако в некоторых случаях у пользователя может быть уже готовый проект CMake, или он может захотеть создать свой проект. В этих случаях желательно иметь возможность использовать компоненты IDF в качестве библиотек для линковки к target-ам пользователя (библиотекам или исполняемому коду).

Это можно реализовать использованием API системы сборки, который предоставляется tools/cmake/idf.cmake. Например:

cmake_minimum_required(VERSION 3.5)
project(my_custom_app C)
 
# Подключение файла CMake, который предоставляет API системы сборки ESP-IDF CMake.
include($ENV{IDF_PATH}/tools/cmake/idf.cmake)
 
# Подключение компонентов ESP-IDF в сборку может рассматриваться как
# эквивалент add_subdirectory(), но с некоторой дополнительной обработкой
# и магией для специфического процесса сборки ESP-IDF.
idf_build_process(esp32)
 
# Создание исполняемого кода проекта и явное связывание с ним компонента newlib
# через псевдоним idf::newlib.
add_executable(${CMAKE_PROJECT_NAME}.elf main.c)
target_link_libraries(${CMAKE_PROJECT_NAME}.elf idf::newlib)
 
# Сообщает системе сборки, что представляет собой исполняемый код проекта
# для подсоединения большего количества target-ов, зависимостей и т. д.
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 в документации [3].

[API системы сборки ESP-IDF]

idf_build_get_property(var property [GENERATOR_EXPRESSION])

Извлекает свойство сборки property и сохраняет его в переменную var, доступную в текущем контексте. Указание GENERATOR_EXPRESSION будет извлекать строку выражения генератора для этого свойства вместо реального значения, которая может использоваться с командами CMake, которые поддерживают выражения генератора.

idf_build_set_property(property val [APPEND])

Установит свойство сборки property в значение val. Указание APPEND будет добавлять указанное значение к текущему значению свойства. Если свойство ранее не существовало, или сейчас пустое, то вместо этого значение val станет первым элементом.

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, таких как конфигурация компонента, создание библиотек, расширение и разрешение зависимостей (dependency expansion and resolution). Среди этих функций возможно наиболее важными с точки зрения пользователя будут создание библиотек путем вызова idf_component_register каждого компонента. Эта команда создает библиотеки для каждого компонента, который доступен через псевдонимы в форме idf::component_name. Эти псевдонимы могут использоваться для линковки компонентов в собственные target-ы пользователя, либо библиотеки, либо исполняемый код.

Вызов требует указания целевого чипа (target chip) в аргументе 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.target, если он существует.
• BUILD_DIR - директория для размещения артефактов ESP-IDF, относящихся к сборке, таких как генерируемые бинарники, текстовые файлы, компоненты; по умолчанию это CMAKE_BINARY_DIR.
• COMPONENTS - выбирает компоненты для обработки наряду с компонентами, о которых знает система сборки (добавленные через idf_build_component). Этот аргумент используется для урезания сборки. Другие компоненты добавляются автоматически, если они требуются в цепочке зависимостей, например public или private требования компонентов в этом списке добавляются автоматически, и в свою очередь их public и private требования, и так далее. Если не указано, то обрабатываются все компоненты, о которых знает система сборки.

idf_build_executable(executable)

Указывает исполняемый код executable для системы сборки ESP-IDF. Это подсоединеняет дополнительные target-ы, такие как зависимости для прошивки, генерация дополнительных двоичных файлов, и т. д. Нужно вызывать после idf_build_process.

idf_build_get_config(var config [GENERATOR_EXPRESSION])

Получит значение val указанного конфига config. Подобно свойствам сборки указание GENERATOR_EXPRESSION будет извлекать строку генератора выражения для этого конфига вместо реального значения, которая может использоваться с командами CMake, которые поддерживают выражения генератора. Однако реальные значения конфига будут известны только после вызова idf_build_process.

Эти свойства описывают сборку. Значения свойств сборки могут быть получены командой сборки idf_build_get_property. Например, чтобы узнать интерпретатор Python, используемый для сборки:

idf_build_get_property(python PYTHON)
message(STATUS "The Python intepreter is: ${python}")

• BUILD_DIR - директория сборки; устанавливается из аргумента BUILD_DIR вызова idf_build_process.
• BUILD_COMPONENTS - список компонентов, включенных в сборку; устанавливается вызовом idf_build_process.
• BUILD_COMPONENT_ALIASES - list of library alias of components included in the build; устанавливается вызовом idf_build_process.
• C_COMPILE_OPTIONS - опции компиляции, применяемые для всех исходных C-файлов компонента.
• COMPILE_OPTIONS - опции компиляции, применяемые для всех исходных файлов компонента, независимо от того, они C или C++.
• COMPILE_DEFINITIONS - определения компиляции, применяемые для всех исходных файлов компонента.
• CXX_COMPILE_OPTIONS - опции компиляции, применяемые для всех исходных файлов C++ компонента.
• EXECUTABLE - исполняемый код проекта; устанавливается вызовом idf_build_executable.
• EXECUTABLE_NAME - имя исполняемого кода проекта без расширения; устанавливается вызовом idf_build_executable.
• EXECUTABLE_DIR - путь, содержащий выходной исполняемый код.
• IDF_COMPONENT_MANAGER - по умолчанию разрешен менеджер компонента, однако если это свойство установлено в 0, то это было запрещено переменной окружения IDF_COMPONENT_MANAGER.
• IDF_PATH - путь ESP-IDF; устанавливается из переменной окружения IDF_PATH, иначе вычисляется из места расположения idf.cmake.
• IDF_TARGET - целевой чип, для которого выполняется сборка; устанавливается из аргумента target для idf_build_process.
• IDF_VER - версия ESP-IDF; устанавливается либо из файла version, либо из ревизии Git репозитория IDF_PATH.
• INCLUDE_DIRECTORIES - директории поиска подключаемых файлов для всех исходных файлов компонента.
• KCONFIGS - список файлов Kconfig, найденных в components сборки; устанавливается вызовом idf_build_process.
• KCONFIG_PROJBUILDS - список файлов Kconfig.projbuild, найденных в components сборки; устанавливается вызовом idf_build_process.
• PROJECT_NAME - имя проекта; устанавливается из аргумента PROJECT_NAME вызова idf_build_process.
• PROJECT_DIR - директория проекта; устанавливается из аргумента PROJECT_DIR вызова idf_build_process.
• PROJECT_VER - версия проекта; устанавливается из аргумента PROJECT_VER вызова idf_build_process.
• PYTHON - интерпретатор Python, используемый для сборки; устанавливается из переменной окружения PYTHON, если она доступна, иначе используется "python".
• SDKCONFIG - полный путь до выходного файла конфига; устанавливается из аргумента SDKCONFIG вызова idf_build_process.
• SDKCONFIG_DEFAULTS - список файлов, содержащих конфиг по умолчанию для использования в сборке; устанавливается из аргумента SDKCONFIG_DEFAULTS вызова idf_build_process.
• 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_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() CMake, это должно вызываться напрямую из 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. Обратите внимание, что на это накладываются globbing-ограничения в CMake. Исходные файлы, указанные в EXCLUDE_SRCS удаляются из globbed-файлов.
• INCLUDE_DIRS - пути относительно текущей директории, добавляемые к путям поиска подключаемых файлы для всех других компонентов, которые требуют текущий компонент.
• PRIV_INCLUDE_DIRS - пути директории, должны быть относительными к директории компонента. Добавятся к пути поиска подключаемых файлов только для исходных файлов этого компонента.
• REQUIRES - public-требования к компонентам со стороны этого компонента.
• PRIV_REQUIRES - private-требования к компонентам со стороны этого компонента; игнорируется в config-only компонентах.
• LDFRAGMENTS - файлы фрагментов линкера компонента.
• REQUIRED_IDF_TARGETS - указывает единственную target, которую поддерживает компонент.
• KCONFIG - переназначает файл Kconfig по умолчанию.
• KCONFIG_PROJBUILD - переназначает файл Kconfig.projbuild по умолчанию.
• WHOLE_ARCHIVE - если указано, то библиотека компонента окружается при линковке -Wl,--whole-archive, -Wl,--no-whole-archive. Это дает тот же эффект, что и установка свойства WHOLE_ARCHIVE компонента.

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

• EMBED_FILES - двоичные файлы для встраивания в компонент.
• EMBED_TXTFILES - текстовые файлы для встраивания в компонент.

Эти свойства описывают компонент. С помощью команды сборки 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, используемый для линковки компонента с внешними target-ами; устанавливается 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 - список двоичных файлов, встраиваемых в компонент; устанавливается из аргумента EMBED_FILES вызова idf_component_register.
• EMBED_TXTFILES - список двоичных файлов, встраиваемых в компонент; устанавливается из аргумента EMBED_TXTFILES вызова idf_component_register.
• INCLUDE_DIRS - список директорий поиска подключаемых заголовков; устанавливается из аргумента INCLUDE_DIRS вызова idf_component_register.
• KCONFIG - файл Kconfig компонента; устанавливается вызовом idf_build_component.
• KCONFIG_PROJBUILD - файл Kconfig.projbuild компонента; устанавливается вызовом idf_build_component.
• LDFRAGMENTS - список файлов фрагментов линкера компонента; устанавливается из аргумента LDFRAGMENTS вызова idf_component_register.
• MANAGED_PRIV_REQUIRES - список private-зависимостей компонента, добавленных менеджером компонентов IDF из зависимостей в файле манифеста idf_component.yml.
• MANAGED_REQUIRES - список public-зависимостей компонента, добавленных менеджером компонентов IDF из зависимостей в файле манифеста idf_component.yml.
• PRIV_INCLUDE_DIRS - список private-директорий подключения компонента; устанавливается из аргумента PRIV_INCLUDE_DIRS вызова idf_component_register для компонентов типа LIBRARY.
• PRIV_REQUIRES - список private-зависимостей компонента; устанавливается из значения аргумента PRIV_REQUIRES вызова idf_component_register и зависимостей в файле манифеста idf_component.yml.
• REQUIRED_IDF_TARGETS - список target-ов, поддерживаемых компонентом; устанавливается из аргумента REQUIRED_IDF_TARGETS вызова idf_component_register.
• REQUIRES - список public-зависимостей компонента; устанавливается из значения аргумента REQUIRES вызова idf_component_register и зависимостей в файле манифеста idf_component.yml.
• SRCS - список исходных файлов компонента; устанавливается из аргумента SRCS или SRC_DIRS/EXCLUDE_SRCS вызова idf_component_register.
• WHOLE_ARCHIVE - если это свойство установлено в TRUE (или любое значение "true" для CMake: 1, ON, YES, Y), то библиотека компонента окружается при линковке опциями -Wl,--whole-archive, -Wl,--no-whole-archive. Это может использоваться для принуждения линкера подключать каждый объектный файл в исполняемый код executable, даже если объектный файл не разрешает никакие ссылки от остальной части приложения. Это обычно используется, когда компонент содержит плагины или модули, которые полагаются на регистрацию во время линковки. По умолчанию это свойство FALSE. Оно может быть установлено в TRUE из файла CMakeLists.txt компонента.

[Подстановка файлов (File Globbing) и инкрементальные сборки]

Предпочтительный способ для подключения исходных файлов в компонент 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
                       ...)

Это использует закулисную подстановку (globbing) для поиска исходных файлов в указанных директориях. Однако имейте в виду, что если новый исходный файл был добавлен таким методом, то CMake не узнает, что надо автоматически перезапустить сборку, и этот новый файл не добавится в сборку.

Такой компромисс допустим, когда Вы добавляете исходный файл сами, потому что можете запустить clean сборки, или запустить idf.py reconfigure, чтобы вручную перезапустить CMake. Однако проблема усложняется, когда Вы делитесь своим проектом с другими разработчиками, кто может проверить на наличие новой версии (checkout), используя систему управления версиями наподобие Git...

Для компонентов, которые являются частью ESP-IDF, мы используем сторонний модуль интеграции Git и CMake (/tools/cmake/third_party/GetGitRevisionDescription.cmake), который автоматически перезапустит CMake каждый раз, когда меняется commit репозитория. Это значит, что если Вы сделаете checkout новой версии 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. Здесь также могут использоваться файлы flash_*_args для непосредственного использования с утилитой esptool.py. См. выше раздел "Аргументы процесса прошивки".
• CMakeCache.txt это файл кэша CMake, который содержит другую информацию о процессе CMake process, тулчейне, и т. п.
config/sdkconfig.json это JSON-версия значений конфигурации проекта.
config/kconfig_menus.json это JSON-версия меню в menuconfig для использования в интерфейсе пользователя сторонних IDE.

JSON Configuration Server. Предоставляется утилита confserver.py, чтобы упростить интеграцию IDE с логикой системы конфигурирования. Скрипт confserver.py разработан для запуска в фоновом режиме, и взаимодействия с вызвавшим процессом путем чтения и записи JSON через обработку stdin и stdout.

Вы можете запустить confserver.py из проекта через idf.py confserver или ninja confserver, или подобной target, запущенной из другого другого генератора сборки.

Для дополнительной информации о скрипте confserver.py, см. tools/kconfig_new/README.md.

[Внутреннее устройство системы сборки]

Скрипты сборки. Файлы для системы сборки находятся в директории /tools/cmake. Это следующие модули, которые реализуют ядро функционала системы сборки:

build.cmake - команды, относящиеся к сборке, например инициализация сборки, получение/установка свойств сборки, обработка сборки.
component.cmake - команды, относящиеся к компоненту, например добавление компонентов, получение/установка свойств компонента, регистрация компонентов.
kconfig.cmake - генерация файлов конфигурации (sdkconfig, sdkconfig.h, sdkconfig.cmake, и т. п.) из файлов Kconfig.
ldgen.cmake - генерация конечного скрипта линкера из файлов фрагментов линкера.
target.cmake - установка target сборки и файла тулчейна.
utilities.cmake - различные вспомогательные команды.

Кроме этих файлов, имеется два других важных скрипта CMake в директории /tools/cmake:

idf.cmake - настраивает сборку и подключает перечисленные выше модули ядра. Подключается в проектах CMake, чтобы получить доступ к функционалу системы сборки ESP-IDF.
project.cmake - подключает idf.cmake и предоставляет пользовательскую команду project(), которая заботится обо всех сложных действиях по сборки исполняемого кода. Подключается на верхнем уровне CMakeLists.txt стандартных проектов ESP-IDF.

Остальные файлы в /tools/cmake это скрипты поддержки или сторонние скрипты, используемые в процессе сборки.

Процесс сборки. Стандартный процесс сборки приложения в ESP-IDF разбит на 4 фазы:

ESP IDF Build System Process fig01

Рис. 1. Процесс системы сборки ESP-IDF.

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

• После включения idf.cmake в project.cmake выполняются следующие шаги:

- Устанавливается IDF_PATH из переменной окружения, или вычисляется из пути к project.cmake, подключаемого в CMakeLists.txt верхнего уровня.
- Добавляется /tools/cmake к CMAKE_MODULE_PATH, и запускаются модули ядра сборки плюс различные вспомогательные и сторонние скрипты.
- Устанавливаются инструменты сборки исполняемого кода (tools/executables), такие как интерпретатор Python по умолчанию.
- Извлекается ревизия git ESP-IDF, и сохраняется как IDF_VER.
- Устанавливаются глобальные спецификации сборки, например опции компиляции, определения компиляции, директории include для всех компонентов в сборке.
- В components добавляются компоненты для сборки.

• Начальная часть пользовательской команды project() выполняет следующие шаги:

- Устанавливается IDF_TARGET из переменной окружения, или из кэша CMake, и соответствующего используемого CMAKE_TOOLCHAIN_FILE.
- В EXTRA_COMPONENTS_DIRS добавляются компоненты для сборки.
- Из переменных, таких как COMPONENTS/EXCLUDE_COMPONENTS, SDKCONFIG, SDKCONFIG_DEFAULTS, подготавливаются аргументы для вызова команды idf_build_process().

Вызов команды idf_build_process() соответствует завершению этой фазы.

Enumeration. Эта фаза строит конечный список компонентов для обработки в сборке, и это выполняется в первой половине idf_build_process().

• Извлекаются public и private требования каждого компонента. Создается дочерний процесс, который обрабатывает CMakeLists.txt каждого компонента в режиме скрипта. Значения аргументов REQUIRES и PRIV_REQUIRES вызова idf_component_register возвращаются в родительский процесс сборки. Это называется ранним расширением (early expansion). Во время этого шага определяется переменная CMAKE_BUILD_EARLY_EXPANSION.

• Рекурсивно подключаются компоненты на основе public и private требований.

Processing. Эта фаза обрабатывает компоненты в сборке, и это вторая половина idf_build_process().

• Загружается конфигурация проекта из файла sdkconfig, и генерируются sdkconfig.cmake и заголовок sdkconfig.h. Это определяет переменные/макросы конфигурации, которые доступны соответственно из скриптов сборки и файлов исходного кода/заголовков C/C++.

• Подключается project_include.cmake каждого компонента.

• Каждый компонент добавляется как подкаталог, обрабатывается его CMakeLists.txt. Файл CMakeLists.txt компонента вызывает команду регистрации idf_component_register, которая добавляет исходные файлы, директории include, создает библиотеку компонента, линкует зависимости и т. д.

Finalization. К этой фазе относится все после idf_build_process().

• Создается исполняемый код, и к нему линкуются библиотеки.

• Генерируются файлы метаданных проекта, такие как project_description.json, и отображается соответствующая информация по сборке проекта.

Подробности см. в /tools/cmake/project.cmake.

Некоторые аспекты основанной на CMake системы сборки ESP-IDF очень похожи на старую систему сборки на основе GNU Make. Разработчику нужно предоставить значения директорий include, исходных файлов и т. д. Однако есть значительное различие, поскольку разработчику нужно передать это как аргументы в команду регистрации idf_component_register.

Automatic Conversion Tool. В релизах ESP-IDF v4.x доступен инструмент автоматического преобразования проекта (automatic project conversion tool) tools/cmake/convert_to_cmake.py. Этот скрипт был удален в v5.0 из-за его зависимости от системы сборки.

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

• COMPONENT_BUILD_DIR: вместо неё используйте CMAKE_CURRENT_BINARY_DIR.
• COMPONENT_LIBRARY: по умолчанию $(COMPONENT_NAME).a, однако имя библиотеки может быть переопределено компонентом. Имя библиотеки соответствует имени каталога, где находится компонент.
• CC, LD, AR, OBJCOPY: полные пути для каждого инструмента gcc xtensa cross-toolchain. Вместо этого используйте CMAKE_C_COMPILER, CMAKE_C_LINK_EXECUTABLE, CMAKE_OBJCOPY и т. д. (полный список см. в [7]).
• HOSTCC, HOSTLD, HOSTAR: полные имена каждого инструмента из host native toolchain. Они больше не предоставляются, внешние проекты должны вручную обнаружить любой требуемый тулчейн хоста.
• COMPONENT_ADD_LDFLAGS: используется для переназначения флагов линкера. Вместо этого используйте CMake-команду target_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_DIRS вызова idf_component_register (путь может быть относительным или абсолютным).
• COMPONENT_OBJS: ранее исходные файлы компонента могли быть определены как список объектных файлов. Теперь они могут быть указаны как список исходных файлов через аргумент SRCS вызова idf_component_register.
• COMPONENT_OBJEXCLUDE: должно быть заменено на аргумент EXCLUDE_SRCS вызова idf_component_register. Вместо этого указывайте исходные файлы (как абсолютные пути, или пути относительно директории компонента).
• COMPONENT_EXTRA_CLEAN: вместо этого установите свойство ADDITIONAL_MAKE_CLEAN_FILES, но имейте в виду, что у CMake для этого функционала есть некоторые ограничения.
• COMPONENT_OWNBUILDTARGET и COMPONENT_OWNCLEANTARGET: вместо этого используйте CMake ExternalProject. Для дополнительной информации см. выше секцию "Полное переназначение процесса сборки компонента".
• COMPONENT_CONFIG_ONLY: вместо этого вызовите idf_component_register без каких-либо аргументов. См. выше раздел "Компоненты только для конфигурации".
• CFLAGS, CPPFLAGS, CXXFLAGS: вместо этого используйте эквивалентные команды CMake. См. выше секцию "Управление компиляцией компонента".

Без значений по умолчанию. В отличие от старой системы сборки на основе Make, у следующего нет значений по умолчанию:

• Директории исходного кода (COMPONENT_SRCDIRS в Make, аргумент SRC_DIRS вызова idf_component_register в CMake).
• Директории поиска подключаемых файлов (COMPONENT_ADD_INCLUDEDIRS в Make, аргумент INCLUDE_DIRS вызова idf_component_register в CMake).

Что больше не нужно. В старой системе сборки на основе Make требуется также установить COMPONENT_SRCDIRS, если установлена COMPONENT_SRCS. В CMake соответствующий эквивалент не требуется, т. е. указание SRC_DIRS для вызова idf_component_register, если также указан SRCS (фактически SRCS игнорируется, если указана SRC_DIRS).

Прошивка из make. Команда make flash и подобные target-ы все еще работают для сборки и прошивки. Однако sdkconfig проекта больше не указывает последовательный порт и скорость для интерфейса прошивки. Для переопределения этих параметров могу использоваться переменные окружения. Дополнительную информацию см. выше в секции "Прошивка с помощью ninja или make".

[Полезные возможности menuconfig]

Назначение некоторых полезных горячих клавиш, что не всегда очевидно:

? Symbol info (информация о символе, от чего зависит. Для ввода ? нажимать Shift+/).
A Toggle show-all mode (показать/скрыть все разделы настроек).
/ Jump to symbol (быстрый переход на символ).
C Toggle show-name mode (покажет название символа опции).

[Ссылки]

1. ESP-IDF Build System site:docs.espressif.com.
2. ESP-IDF Frontend idf.py site:docs.espressif.com.
3. Конфигурация проекта ESP-IDF.
4. target_link_libraries site:cmake.org.
5. CMake Command-Line Tools site:cmake.org.
6. Linker Script Generation site:docs.espressif.com.
7. cmake-variables site:cmake.org.
8. ESP32: таблицы разделов.
9. ESP-IDF: утилита idf.py.

 

Комментарии  

 
0 #1 JD72 09.06.2023 03:05
Вместо "REQUIRES или PRIV_REQUIRES" должно быть "COMPONENT_REQUI RES или COMPONENT_PRIV_ REQUIRES", в 3-х местах.

microsin: вы это китайцам объясните.
Цитировать
 

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


Защитный код
Обновить

Top of Page