I am calling make-commands of an SDK inside CMake.
They will build static libraries which are IMPORTRED targets in CMake, which I use for linking our implementation against.
The sdk is contained in a single directory and contains a freertos-library which can only be built via make.
I have a multicore-application, where each target uses the same SDK and has its own FreeRTOS instance.
I did manage to build all libraries and everything runs fine, but now I have the case, that I may want to have different settings for the FreeRTOS per core, e.g. different amount of priorities.
This would mean I need a different FreeRTOSConfig.h. The sdk does not provide a solution to pass my own FreeRTOSConfig.h, so there is no way, without modification of the make-scripts of the sdk, to "inject" my own FreeRTOSConfig.h.
I now wanted to replace the FreeRTOSConfig.h inside the SDK via add_custom_command by temporarily storing the current FreeRTOSConfig.h of the SDK, replacing it with my header, compile, and replace the header with the temporarily stored one afterwards.
I used the recommended technique described here: https://cmake.org/cmake/help/latest/command/add_custom_command.html#example-generating-files-for-multiple-targets
But now both cmake-targets for each core use both the same cmake-function which produces a make-call for building the sdk-freertos-library and thus the same generated file. This makes totally sense to me and I completely understand, why ninja complains that multiple rules generate the same file.
I try to strap down my code, since it's a bit more complex and the minmal example would probably not work, but shows how I implemented this:
function(add_sdk_package EXTENSION PATH_TO_FREERTOS_HEADER)
add_custom_command(
OUTPUT ${SDK_PATH}/source/kernel/freertos/config/FreeRTOSConfig.h
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SDK_PATH}/source/kernel/freertos/config/FreeRTOSConfig.h ${CMAKE_CURRENT_BINARY_DIR}/temp/FreeRTOSConfig.h
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PATH_TO_FREERTOS_HEADER}/FreeRTOSConfig.h ${SDK_PATH}/source/kernel/freertos/config/FreeRTOSConfig.h
COMMENT "copy temporary modified header"
)
add_custom_target(copy-freertos-config-header-${EXTENSION} ALL
DEPENDS ${SDK_PATH}/source/kernel/freertos/config/FreeRTOSConfig.h
)
add_custom_command(
TARGET copy-freertos-config-header-${EXTENSION}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/temp/FreeRTOSConfig.h ${SDK_PATH}/source/kernel/freertos/config/FreeRTOSConfig.h
POST_BUILD
COMMENT "copy temporary modified header"
)
# create make-target and store output into binary dir
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/freertos.${CMAKE_BUILD_TYPE}.lib
COMMAND make -s -f makefile freertos PROFILE=${CMAKE_BUILD_TYPE}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SDK_PATH}/source/kernel/freertos/lib/freertos.${CMAKE_BUILD_TYPE}.lib ${CMAKE_CURRENT_BINARY_DIR}/freertos.${CMAKE_BUILD_TYPE}.lib
WORKING_DIRECTORY ${SDK_PATH})
set(LIB_NAME freertos-${EXTENSION})
# creating a target for cmake, which depends on add_custom_command output
add_custom_target(${LIB_NAME}-temp DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/freertos.${CMAKE_BUILD_TYPE}.lib)
add_library(${LIB_NAME} STATIC IMPORTED)
add_dependencies(${LIB_NAME} ${LIB_NAME}-temp)
# lib should have a dependency to target if set, so the target builds first
add_dependencies(${LIB_NAME} copy-freertos-config-header-${EXTENSION})
set_target_properties(${LIB_NAME} PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/freertos.${CMAKE_BUILD_TYPE}.lib
INTERFACE_INCLUDE_DIRECTORIES "${SDK_PATH}/source"
INTERFACE_LINK_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}")
add_library(sdk-package-${EXTENSION} INTERFACE EXCLUDE_FROM_ALL
)
target_link_libraries(sdk-package-${EXTENSION} INTERFACE
freertos-${EXTENSION}
)
endfunction()
And somewhere else both cores call
add_sdk_package(core0 ${CMAKE_SOURCE_DIR}/core0/config)
add_sdk_package(core1 ${CMAKE_SOURCE_DIR}/core1/config)
add_library(myTarget-core0
...
)
add_library(myTarget-core1
...
)
target_link_library(myTarget-core0 PRIVATE sdk-package-core0)
target_link_library(myTarget-core1 PRIVATE sdk-package-core1)
And exactly this will produce the issue.
ninja: error: build.ninja:34219: multiple rules generate ../sdk/freertos/config/FreeRTOSConfig.h [-w dupbuild=err]
I understand as well that this could be an issue regarding other libraries including exactly this header, since it is not known from which target it was created.
Previously we modified the sdk's FreeRTOSConfig.h by including another header Config.h in the header, which was not part of the sdk. We then created a separate bash-script which did copy our Config.h next to FreeRTOSConfig.h, build the libraries one after each other all before CMake was run and it was only a imported target in cmake, without any calls to the make-scripts from cmake side, only from the bash-script. The header was also given to any other targets in cmake, which want to include a FreeRTOS.h-header and thus FreeRTOSConfig.h-header and if searching for the Config.h the path was always given to the right Config.h of the target.
This way it was no problem, since the compilation was done in sequence and the correct header was used in the inclusion-hierarchy.
When I wanted to reimplement the same functionality, so at least modify the FreeRTOSConfig.h permanently inside the sdk and try to copy our Config.h-header next to it via CMake, the same way as above, just without any temp-copy, I will get exactly the same error, since it's again the same file in the same place for two different targets.
So the message looks similar:
ninja: error: build.ninja:34219: multiple rules generate ../sdk/freertos/config/Config.h [-w dupbuild=err]
Is there a way to prevent this behaviour and to make it possible to ensure there is no concurrent execution?
The only way I can currently think about is to clone the whole sdk two times into the binary-folder and build them separately. There must be some better solution.
add_custom_command(OUTPUT)twice in the same directory with the same output file. You should avoid calling this command the second time (and other times). E.g. when you call that command inside the functionadd_sdk_packageyou could define some variable. And check existence of that variable in futher function's calls. (Note, that for making a variable visible outside of the single function's call, you need tosetthis variable with PARENT_SCOPE modifier.)COMMANDcopying the original file into a temp folder and replacing the sdk-file, after calling the make-function, and after that anotherCOMMANDwhich replaces the original file. all in one add_custom_command, so the one you already see in my initial question. Is this a legit solution? It seems to work so far.