Execute Command Post Build - CMake

User avatar
egionet
Posts: 39
Joined: Thu Sep 26, 2024 4:34 am
Location: Atlantic Canada

Execute Command Post Build - CMake

Postby egionet » Sat Mar 29, 2025 7:02 am

I am trying to execute two commands after a component is built, and I can't figure out how to set the TARGET properly for the CMake add custom command. I am using PlatformIO and realize that I can execute scripts post-build by configuring the .ini file, but I am trying to leverage CMake instead of a Python or Powershell script.

The CMakeList.txt in the component directory pre-build updates the component's version number in the header, idf_component.yml, and library.json files through templates. The last step that I am trying to figure out is publishing the components to PlatformIO library and ESP-IDF component registries via commands. Any help with this would be greatly appreciated.

CMakeList.txt

Code: Select all

#
# Versioning Information for ESP-IDF Components with GitHub, GitVersion and CMake
#
# Inspired by: https://www.esp32.com/viewtopic.php?f=2&t=45054&p=146150#p146150
#
# Install Git-Version via command prompt: dotnet tool install --global GitVersion.Tool
# Create a GitVersion.yml file in the root of your project with the following content:
#
#   major-version-bump-message: '\+semver:\s?(breaking|major)'
#   minor-version-bump-message: '\+semver:\s?(feature|minor)'
#   patch-version-bump-message: '\+semver:\s?(fix|patch)'
#   commit-message-incrementing: Enabled
#
# Download CMake JSON-Parser: https://github.com/sbellus/json-cmake/blob/master/JSONParser.cmake
# Copy the CMake JSONParser.cmake file to the tools/cmake directory of your ESP-IDF installation.
# i.e. C:\Users\user\.platformio\packages\framework-espidf\tools\cmake
#
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
include($ENV{IDF_PATH}/tools/cmake/JSONParser.cmake RESULT_VARIABLE JSONPARSER_FOUND)

# string compare JSONParser library availability
string(COMPARE NOTEQUAL "${JSONPARSER_FOUND}" "NOTFOUND" STR_CMP_RESULT)

# validate JSONParser library, version.h.in, pio_lib_sync.py, esp_cmp_sync.py, 
# library.json.in, and idf_component.yml.in files are available for preprocessing
if(STR_CMP_RESULT EQUAL 1 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in" 
    AND EXISTS "${COMPONENT_DIR}/include/library.json.in" AND EXISTS "${COMPONENT_DIR}/include/idf_component.yml.in")

    # Get latest versioning information from git repository with GitVersion
    execute_process(
        COMMAND dotnet-gitversion
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        OUTPUT_VARIABLE GIT_VERSION_OUTPUT
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    # Instantiate json variable
    sbeParseJson(GIT_VERSION_JSON GIT_VERSION_OUTPUT)

    # Parse versioning variables from json output
    set(GIT_VERSION_DATE  ${GIT_VERSION_JSON.CommitDate})
    set(GIT_SEM_VERSION   ${GIT_VERSION_JSON.MajorMinorPatch})
    set(GIT_VERSION_MAJOR ${GIT_VERSION_JSON.Major})
    set(GIT_VERSION_MINOR ${GIT_VERSION_JSON.Minor})
    set(GIT_VERSION_PATCH ${GIT_VERSION_JSON.Patch})
    set(GIT_FULL_SEM_VER  ${GIT_VERSION_JSON.FullSemVer})
    set(GIT_SHORT_SHA     ${GIT_VERSION_JSON.ShortSha})

    # Release json variable
    sbeClearJson(GIT_VERSION_JSON)

    # Parse component name from component path
    get_filename_component(COMPONENT_NAME "${COMPONENT_DIR}" NAME)

    # Components should be named as "esp_<component_name>"
    string(FIND "${COMPONENT_NAME}" "esp_" ESP_PREFIX)

    # Check if the component name starts with "esp_"
    if(ESP_PREFIX EQUAL -1)
        # Use the component name as is
        string(CONCAT COMPONENT_HEADER_NAME "" "${COMPONENT_NAME}")
    else()
        # Parse component file name from component name
        string(REPLACE "esp_" "" COMPONENT_HEADER_NAME "${COMPONENT_NAME}")
    endif()

    # Set the component header name to upper case
    string(TOUPPER "${COMPONENT_HEADER_NAME}" COMPONENT_HEADER_NAME_UPPER)

    # Generate C header file from template with versioning information
    configure_file( "${CMAKE_SOURCE_DIR}/include/version.h.in" "${COMPONENT_DIR}/include/${COMPONENT_HEADER_NAME}_version.h" @ONLY )

    # Generate json library file from template with versioning information
    configure_file( "${COMPONENT_DIR}/include/library.json.in" "${COMPONENT_DIR}/library.json" @ONLY )

    # Generate yml idf component file from template with versioning information
    configure_file( "${COMPONENT_DIR}/include/idf_component.yml.in" "${COMPONENT_DIR}/idf_component.yml" @ONLY )
endif()

# Register the component
idf_component_register(
    SRCS uuid.c
    INCLUDE_DIRS include
)

# Get global variables from idf build property
idf_build_get_property(cmp_reg_sync COMPONENT_REGISTRY_SYNC)
idf_build_get_property(cmp_reg_o COMPONENT_REGISTRY_OWNER)

# Validate if the component should be synchronized with esp-idf and platformio registries
string(COMPARE EQUAL "${cmp_reg_sync}" "ENABLED" STR_CMP_RESULT)

# Register the component with the ESP-IDF components and platformio registries
if(STR_CMP_RESULT EQUAL 1)

    #add_library(myPlugin MODULE uuid.c)

    #if(NOT CMAKE_BUILD_EARLY_EXPANSION) 
        #add_custom_command(
        #    TARGET uuid.c
        #    POST_BUILD
        #    COMMAND powershell compote component register --name ${COMPONENT_NAME} --project-dir '${COMPONENT_DIR}' --dest-dir '${CMAKE_CURRENT_SOURCE_DIR}/log'
        #)
    #endif()

    # Package the component and upload it to the ESP-IDF component registry
    if(NOT CMAKE_BUILD_EARLY_EXPANSION) 
    execute_process(
        COMMAND powershell compote component upload --name "${COMPONENT_NAME}" --project-dir '"${COMPONENT_DIR}"' --dest-dir '"${CMAKE_CURRENT_SOURCE_DIR}/log"'
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/log/esp_cmp_sync.log"
    )
    endif()
    # Package the components and upload it to the platformio library registry
    #execute_process(
    #    COMMAND powershell pio pkg publish '"${COMPONENT_DIR}"' --owner "${cmp_reg_o}" --type library --no-interactive
    #    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    #    OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/log/pio_lib_sync.log"
    #)
endif()
Regards,

Eric Gionet P.Tech.(Eng.), CIOTP, SMIEEE

MicroController
Posts: 2663
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Execute Command Post Build - CMake

Postby MicroController » Sat Mar 29, 2025 8:24 am

You should be able to reference the target of the current component via ${COMPONENT_LIB}.

User avatar
egionet
Posts: 39
Joined: Thu Sep 26, 2024 4:34 am
Location: Atlantic Canada

Re: Execute Command Post Build - CMake

Postby egionet » Sat Mar 29, 2025 9:01 am

Thanks for the suggestion. I just gave it a go and got the following results.

If I register the component before calling add_custom_command, I do not get any errors, but the command isn't triggering an action.

Code: Select all

#
# Versioning Information for ESP-IDF Components with GitHub, GitVersion and CMake
#
# Inspired by: https://www.esp32.com/viewtopic.php?f=2&t=45054&p=146150#p146150
#
# Install Git-Version via command prompt: dotnet tool install --global GitVersion.Tool
# Create a GitVersion.yml file in the root of your project with the following content:
#
#   major-version-bump-message: '\+semver:\s?(breaking|major)'
#   minor-version-bump-message: '\+semver:\s?(feature|minor)'
#   patch-version-bump-message: '\+semver:\s?(fix|patch)'
#   commit-message-incrementing: Enabled
#
# Download CMake JSON-Parser: https://github.com/sbellus/json-cmake/blob/master/JSONParser.cmake
# Copy the CMake JSONParser.cmake file to the tools/cmake directory of your ESP-IDF installation.
# i.e. C:\Users\user\.platformio\packages\framework-espidf\tools\cmake
#
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
include($ENV{IDF_PATH}/tools/cmake/JSONParser.cmake RESULT_VARIABLE JSONPARSER_FOUND)

# string compare JSONParser library availability
string(COMPARE NOTEQUAL "${JSONPARSER_FOUND}" "NOTFOUND" STR_CMP_RESULT)

# validate JSONParser library, version.h.in, pio_lib_sync.py, esp_cmp_sync.py, 
# library.json.in, and idf_component.yml.in files are available for preprocessing
if(STR_CMP_RESULT EQUAL 1 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in" 
    AND EXISTS "${COMPONENT_DIR}/include/library.json.in" AND EXISTS "${COMPONENT_DIR}/include/idf_component.yml.in")

    # Get latest versioning information from git repository with GitVersion
    execute_process(
        COMMAND dotnet-gitversion
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        OUTPUT_VARIABLE GIT_VERSION_OUTPUT
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    # Instantiate json variable
    sbeParseJson(GIT_VERSION_JSON GIT_VERSION_OUTPUT)

    # Parse versioning variables from json output
    set(GIT_VERSION_DATE  ${GIT_VERSION_JSON.CommitDate})
    set(GIT_SEM_VERSION   ${GIT_VERSION_JSON.MajorMinorPatch})
    set(GIT_VERSION_MAJOR ${GIT_VERSION_JSON.Major})
    set(GIT_VERSION_MINOR ${GIT_VERSION_JSON.Minor})
    set(GIT_VERSION_PATCH ${GIT_VERSION_JSON.Patch})
    set(GIT_FULL_SEM_VER  ${GIT_VERSION_JSON.FullSemVer})
    set(GIT_SHORT_SHA     ${GIT_VERSION_JSON.ShortSha})

    # Release json variable
    sbeClearJson(GIT_VERSION_JSON)

    # Parse component name from component path
    get_filename_component(COMPONENT_NAME "${COMPONENT_DIR}" NAME)

    # Components should be named as "esp_<component_name>"
    string(FIND "${COMPONENT_NAME}" "esp_" ESP_PREFIX)

    # Check if the component name starts with "esp_"
    if(ESP_PREFIX EQUAL -1)
        # Use the component name as is
        string(CONCAT COMPONENT_HEADER_NAME "" "${COMPONENT_NAME}")
    else()
        # Parse component file name from component name
        string(REPLACE "esp_" "" COMPONENT_HEADER_NAME "${COMPONENT_NAME}")
    endif()

    # Set the component header name to upper case
    string(TOUPPER "${COMPONENT_HEADER_NAME}" COMPONENT_HEADER_NAME_UPPER)

    # Generate C header file from template with versioning information
    configure_file( "${CMAKE_SOURCE_DIR}/include/version.h.in" "${COMPONENT_DIR}/include/${COMPONENT_HEADER_NAME}_version.h" @ONLY )

    # Generate json library file from template with versioning information
    configure_file( "${COMPONENT_DIR}/include/library.json.in" "${COMPONENT_DIR}/library.json" @ONLY )

    # Generate yml idf component file from template with versioning information
    configure_file( "${COMPONENT_DIR}/include/idf_component.yml.in" "${COMPONENT_DIR}/idf_component.yml" @ONLY )
endif()

# Get global variables from idf build property
idf_build_get_property(cmp_reg_sync COMPONENT_REGISTRY_SYNC)
idf_build_get_property(cmp_reg_o COMPONENT_REGISTRY_OWNER)

# Validate if the component should be synchronized with esp-idf and platformio registries
string(COMPARE EQUAL "${cmp_reg_sync}" "ENABLED" STR_CMP_RESULT)

# Register the component with the ESP-IDF components and platformio registries
if(STR_CMP_RESULT EQUAL 1)

    if(NOT CMAKE_BUILD_EARLY_EXPANSION) 
        # Package the component and upload it to the ESP-IDF component registry
        add_custom_command(
            TARGET ${COMPONENT_LIB}
            POST_BUILD
            COMMAND powershell compote component register --name ${COMPONENT_NAME} --project-dir '${COMPONENT_DIR}' --dest-dir '${CMAKE_CURRENT_SOURCE_DIR}/log'
        )
    endif()

    # Package the component and upload it to the ESP-IDF component registry
    #if(NOT CMAKE_BUILD_EARLY_EXPANSION) 
    #execute_process(
    #    COMMAND powershell compote component upload --name "${COMPONENT_NAME}" --project-dir '"${COMPONENT_DIR}"' --dest-dir '"${CMAKE_CURRENT_SOURCE_DIR}/log"'
    #    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    #    OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/log/esp_cmp_sync.log"
    #)
    #endif()
    # Package the components and upload it to the platformio library registry
    #execute_process(
    #    COMMAND powershell pio pkg publish '"${COMPONENT_DIR}"' --owner "${cmp_reg_o}" --type library --no-interactive
    #    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    #    OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/log/pio_lib_sync.log"
    #)
endif()

# Register the component
idf_component_register(
    SRCS uuid.c
    INCLUDE_DIRS include
)
If I register the component at the end, per the above, I get an `add_custom_command Wrong syntax. TARGET or OUTPUT must be specified` error. And if I don't add the "if(NOT CMAKE_BUILD_EARLY_EXPANSION)" condition I get a "add_custom_command is not scriptable" error.
Regards,

Eric Gionet P.Tech.(Eng.), CIOTP, SMIEEE

User avatar
egionet
Posts: 39
Joined: Thu Sep 26, 2024 4:34 am
Location: Atlantic Canada

Re: Execute Command Post Build - CMake

Postby egionet » Sat Mar 29, 2025 2:28 pm

I am starting to suspect PlatformIO as the culprit. I disabled Make, CMake, and ESP-IDF extensions and I am no longer getting compile errors, but the custom triggers are not getting asserted as expected.

Code: Select all

#
# Versioning Information for ESP-IDF Components with GitHub, GitVersion and CMake
#
# Inspired by: https://www.esp32.com/viewtopic.php?f=2&t=45054&p=146150#p146150
#
# Install Git-Version via command prompt: dotnet tool install --global GitVersion.Tool
# Create a GitVersion.yml file in the root of your project with the following content:
#
#   major-version-bump-message: '\+semver:\s?(breaking|major)'
#   minor-version-bump-message: '\+semver:\s?(feature|minor)'
#   patch-version-bump-message: '\+semver:\s?(fix|patch)'
#   commit-message-incrementing: Enabled
#
# Download CMake JSON-Parser: https://github.com/sbellus/json-cmake/blob/master/JSONParser.cmake
# Copy the CMake JSONParser.cmake file to the tools/cmake directory of your ESP-IDF installation.
# i.e. C:\Users\user\.platformio\packages\framework-espidf\tools\cmake
#
include( $ENV{IDF_PATH}/tools/cmake/version.cmake )
include( $ENV{IDF_PATH}/tools/cmake/JSONParser.cmake RESULT_VARIABLE JSONPARSER_FOUND )

# string compare JSONParser library availability
string( COMPARE NOTEQUAL "${JSONPARSER_FOUND}" "NOTFOUND" STR_CMP_RESULT )

# validate JSONParser library, version.h.in, pio_lib_sync.py, esp_cmp_sync.py, 
# library.json.in, and idf_component.yml.in files are available for preprocessing
if(STR_CMP_RESULT EQUAL 1 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in" 
    AND EXISTS "${COMPONENT_DIR}/include/library.json.in" AND EXISTS "${COMPONENT_DIR}/include/idf_component.yml.in")

    # Get latest versioning information from git repository with GitVersion
    execute_process(
        COMMAND dotnet-gitversion
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        OUTPUT_VARIABLE GIT_VERSION_OUTPUT
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    # Instantiate json variable
    sbeParseJson( GIT_VERSION_JSON GIT_VERSION_OUTPUT )

    # Parse versioning variables from json output
    set( GIT_VERSION_DATE  ${GIT_VERSION_JSON.CommitDate} )
    set( GIT_SEM_VERSION   ${GIT_VERSION_JSON.MajorMinorPatch} )
    set( GIT_VERSION_MAJOR ${GIT_VERSION_JSON.Major} )
    set( GIT_VERSION_MINOR ${GIT_VERSION_JSON.Minor} )
    set( GIT_VERSION_PATCH ${GIT_VERSION_JSON.Patch} )
    set( GIT_FULL_SEM_VER  ${GIT_VERSION_JSON.FullSemVer} )
    set( GIT_SHORT_SHA     ${GIT_VERSION_JSON.ShortSha} )

    # Release json variable
    sbeClearJson( GIT_VERSION_JSON )

    # Parse component name from component path
    get_filename_component( COMPONENT_NAME "${COMPONENT_DIR}" NAME )

    # Components should be named as "esp_<component_name>"
    string( FIND "${COMPONENT_NAME}" "esp_" ESP_PREFIX )

    # Check if the component name starts with "esp_"
    if(ESP_PREFIX EQUAL -1)
        # Use the component name as is
        string( CONCAT COMPONENT_HEADER_NAME "" "${COMPONENT_NAME}" )
    else()
        # Parse component file name from component name
        string( REPLACE "esp_" "" COMPONENT_HEADER_NAME "${COMPONENT_NAME}" )
    endif()

    # Set the component header name to upper case
    string( TOUPPER "${COMPONENT_HEADER_NAME}" COMPONENT_HEADER_NAME_UPPER )

    # Generate C header file from template with versioning information
    configure_file( "${CMAKE_SOURCE_DIR}/include/version.h.in" "${COMPONENT_DIR}/include/${COMPONENT_HEADER_NAME}_version.h" @ONLY )

    # Generate json library file from template with versioning information
    configure_file( "${COMPONENT_DIR}/include/library.json.in" "${COMPONENT_DIR}/library.json" @ONLY )

    # Generate yml idf component file from template with versioning information
    configure_file( "${COMPONENT_DIR}/include/idf_component.yml.in" "${COMPONENT_DIR}/idf_component.yml" @ONLY )
endif()


# Register the component
idf_component_register(
    SRCS uuid.c
    INCLUDE_DIRS include
)

# Get global variables from idf build property
idf_build_get_property( cmp_reg_sync COMPONENT_REGISTRY_SYNC )
idf_build_get_property( cmp_reg_o COMPONENT_REGISTRY_OWNER )

# Validate if the component should be synchronized with esp-idf and platformio registries
string( COMPARE EQUAL "${cmp_reg_sync}" "ENABLED" STR_CMP_RESULT )

# Register the component with the ESP-IDF components and platformio registries
if(STR_CMP_RESULT EQUAL 1)

    add_custom_target(
        cmp_reg_sync ALL
        COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/stg/pio_lib_sync.py OK
        COMMENT "python testing with add_custom_command..."
    )
    add_dependencies( ${COMPONENT_LIB} cmp_reg_sync )


    add_custom_command(
        TARGET ${COMPONENT_LIB}
        POST_BUILD
        COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/stg/pio_lib_sync.py OK
        COMMENT "python testing with add_custom_command..."
    )

endif()
Regards,

Eric Gionet P.Tech.(Eng.), CIOTP, SMIEEE

User avatar
egionet
Posts: 39
Joined: Thu Sep 26, 2024 4:34 am
Location: Atlantic Canada

Re: Execute Command Post Build - CMake

Postby egionet » Sun Mar 30, 2025 5:50 am

I implemented an example in VS Code with the ESP-IDF extension and encountered the same behavior i.e. commands are not getting asserted at specified build milestones. Additional details are available here: https://github.com/K0I05/cmake_components_20250329. Open any suggestions. In my case, I need the command or process to get asserted post-build.
Regards,

Eric Gionet P.Tech.(Eng.), CIOTP, SMIEEE

MicroController
Posts: 2663
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Execute Command Post Build - CMake

Postby MicroController » Sun Mar 30, 2025 8:01 am

You may find some useful info here.

Btw, the "cmake way" to do what you want would be to define a dedicated target for the upload/... step and have it depend on the component build target.

User avatar
egionet
Posts: 39
Joined: Thu Sep 26, 2024 4:34 am
Location: Atlantic Canada

Re: Execute Command Post Build - CMake

Postby egionet » Fri Apr 04, 2025 8:58 am

I posted the same question in the CMake forum, and someone clarified a condition that has to be met: the target has to be in the same directory as the caller (i.e. CMakeLists.txt). VS-Code with PlatformIO builds the project in a cached folder; therefore, it will not trigger as expected. However, a suggestion was made to utilize a custom target with the custom command.
Regards,

Eric Gionet P.Tech.(Eng.), CIOTP, SMIEEE

Who is online

Users browsing this forum: Bytespider and 4 guests