PlatformIO not running integrating espidf components with .cmake scripts

Hey all,

I think this might have gotten lost in another thread. So reposting in the hope of some visibility…sorry if this is not allowed. I have returned to the matter and am now a bit stuck still:

Brief Overview:

I am using espidf and platformIO. My set up is really gross with a load of stuff under src with a load of #include ../../crapI would like to break this into proper components. With this in mind, I created a component that runs a .cmake script at BUILD time (not configure time) to give me some compile time information. When I run it with idf.py build, it works but when I run it with pio, I can see in the compile_commands.json the path to where my generated file is there but it isn’t generated by platformIO. But I can’t understand why….

Reading:

I have read the platformIO docs for espidf framework and the espidf ones as well as the dev talk on the build system on YouTube. I wonder if I have missed something crucial or perhaps I just don’t get this.

Currently, my situation is this:

  1. I have a pio project with src_dir pointing to main. I renamed it so that I could build the project in espidf too (since espidf seems to insist on its own pre-defined layout)

    [platformio]
    
    src_dir = main   
    
    
  2. In PROJECT_DIR/main I have a load of subfolders, each pertaining to different peripherals and FreeRTOS task set up. They are very ugly and do a lot of #include ../blah/blah.h. I also have PROJECT_DIR/main/main.cpp. I want to get away from this ugly layout and modularise things properly into espidf components.

  3. My CMakeLists.txt at the root of the project looks like this:

    cmake_minimum_required(VERSION 3.16.0)
    
    include($ENV{IDF_PATH}/tools/cmake/project.cmake)
    
    project(<PROJECT_NAME>)
    
    
    
    set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH "Root of the project") # This is currently being used as the working dir of one of my build time generating .cmake scripts
    
    
    
    
  4. The CMakeLists.txt in main/ looks like this:

    
    
    FILE(GLOB_RECURSE app_sources ${COMPONENT_DIR}/*.*)
    
    message(STATUS "All application sources: ${app_sources}")
    
    
    
    
    idf_component_register(
    
        SRCS ${app_sources}
    
        INCLUDE_DIRS "."
    
        REQUIRES build_info
    
    )
    
    
  5. in PROJECT_DIR/components I have a component called build_info. It contains a CMakeLists.txt. It looks like this:

    set(GENERATED_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR})
    set(BUILD_INFO_HEADER ${GENERATED_BUILD_DIR}/build_info.h)
    
    idf_component_register(
        SRCS dummy.cpp
        INCLUDE_DIRS ${GENERATED_BUILD_DIR}
    )
    
    # Always-run generator
    add_custom_target(generate_build_info ALL
        COMMAND ${CMAKE_COMMAND}
            -DOUTPUT_FILE=${BUILD_INFO_HEADER}
            -DPROJECT_ROOT=${PROJECT_DIR}
            -P ${CMAKE_CURRENT_SOURCE_DIR}/generate_build_info.cmake
        BYPRODUCTS ${BUILD_INFO_HEADER}   # optional, helps CMake know the file exists
        VERBATIM
    )
    
    # Ensure component builds after header is generated
    add_dependencies(${COMPONENT_LIB} generate_build_info)
    
    # Expose include directory for consumers
    target_include_directories(${COMPONENT_LIB} PUBLIC ${GENERATED_BUILD_DIR})
    
    # Mark the file as GENERATED so CMake doesn’t warn
    set_source_files_properties(${BUILD_INFO_HEADER} PROPERTIES GENERATED TRUE)
    
    # Export path for other components
    set(BUILD_INFO_HEADER_PATH ${BUILD_INFO_HEADER} CACHE INTERNAL "Path to build_info.h")
    
    
    
    
  6. The key bit is that I am trying to do a basic example to get to grips with this build system. The idea is that this will run the platform independent .cmake file every time at BUILD time not CONFIGURATION time. This is because the build_info.h header is supposed to include details like git commit and the datetime of compilation and make it available to anything that depends on it.For reference the .cmake file looks like this:

  7. 
    execute_process(
        COMMAND git rev-parse --short HEAD
        WORKING_DIRECTORY ${PROJECT_ROOT}
        OUTPUT_VARIABLE GIT_COMMIT
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_QUIET
    )
    
    # Get build-time timestamp (THIS is now build-time, not configure-time)
    # this does appear to get generated at build time and not configure time AS LONG AS THE target file is not there
    string(TIMESTAMP BUILD_TIME "%Y-%m-%d %H:%M:%S")
    
    message(STATUS "Running build_info generation in script mode. Targetting git command in ${PROJECT_ROOT}")
    message(STATUS "Output file: ${OUTPUT_FILE}")
    message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
    
    if(NOT GIT_COMMIT)
        set(GIT_COMMIT "UNKNOWN")
    endif()
    
    file(WRITE ${OUTPUT_FILE}
    "#pragma once\n"
    "#define BUILD_GIT_COMMIT \"${GIT_COMMIT}\"\n"
    "#define BUILD_TIME \"${BUILD_TIME}\"\n"
    )
    
    
  8. If we build in a devcontainer using idf.py and look at the build/ dir and at the compile commands.json we can see that associated to main.cpp is an -I/workspace/build/esp-idf/build_info:

    -I/workspace/build/esp-idf/build_info -mlongcalls -Wno-frame-address  -fno-builtin-memcpy -fno-builtin-memset -fno-builtin-bzero -fno-builtin-stpcpy -fno-builtin-strncpy -ffunction-sections -fdata-sections -Wall -Werror=all -Wno-error=unused-function -Wno-error=unused-variable -Wno-error=unused-but-set-variable -Wno-error=deprecated-declarations -Wextra -Wno-error=extra -Wno-unused-parameter -Wno-sign-compare -Wno-enum-conversion -gdwarf-4 -ggdb -Og -fno-shrink-wrap -fmacro-prefix-map=/workspace=. -fmacro-prefix-map=/home/vscode/esp/esp-idf=/IDF -fstrict-volatile-bitfields -fno-jump-tables -fno-tree-switch-conversion -std=gnu++2b -fno-exceptions -fno-rtti -fuse-cxa-atexit -o esp-idf/main/CMakeFiles/__idf_main.dir/main.cpp.obj -c /workspace/main/main.cpp",
    
      "file": "/workspace/main/main.cpp"
    
    
  9. If we look in /workspace/build/esp-idf/build_info we can find build_info.h with full read and write permission for everyone. When we view it, it is correct:

    vscode@ef9b1cc41195:/workspace$ cat build/esp-idf/build_info/build_info.h
    #pragma once
    #define BUILD_GIT_COMMIT “c7a5c10”
    #define BUILD_TIME “2026-03-25 10:34:55”
    vscode@ef9b1cc41195:/workspace$

and yet when we try and compile:

main/main.cpp:19:10: fatal error: build_info.h: No such file or directory




Looking for build_info.h dependency? Check our library registry!



CLI  > platformio lib search “header:build_info.h”

Web  > https://registry.platformio.org/search?q=header:e[me[Kbuild_info.h






19 | #include “build_info.h”
|          ^~~~~~~~~~~~~~
compilation terminated.

If we investigate in platformIO we can see that the file is not there:

.pio\build\esp32_debug\esp-idf\build_info

# Mode                 LastWriteTime         Length Name
# ----                 -------------         ------ ----
# d----          25/03/2026    14:40                CMakeFiles
# -a---          25/03/2026    16:48           1629 cmake_install.cmake

But if this works in espidf shouldn’t I be able to build my project in platformIO. Afterall, isn’t the espidf pio platform supposed to abstract over this?