Running objcopy in CMAKE custom command causes error during make - cmake

I'm trying to add a post-build command to a small project which will automatically take my build output file (ELF) and convert it to an Intel HEX format for flashing on a microcontroller.
When I add this command however, the build fails. It repeats the command with all of the CMake variable strings substituted in that is run by the shell and post-fixes it with : not found.
When I run that exact line in the terminal after a normal successful build of the ELF, it works as expected. Is there a gotcha I'm missing somewhere with how CMake handles this?
I've added the target to my CMakeLists.txt as follows:
add_custom_command(
TARGET ${EXECUTABLE_NAME}
POST_BUILD
COMMAND "${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_NAME} ${PROJECT_NAME}.hex"
)
The command ends up resolving to <absolute-path>/avr-objcopy -O ihex test_blink.elf test_blink.hex which I can verify since it's printed by CMake out to the terminal.

This string is wrong:
COMMAND "${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_NAME} ${PROJECT_NAME}.hex"
You should use the ARGS keyword:
COMMAND ${CMAKE_OBJCOPY} ARGS -O ihex ${EXECUTABLE_NAME} ${PROJECT_NAME}.hex

Related

Force custom command to always run without first deleting the output

I have a setup where I use a custom command to check the current hash of a git repository so that other commands can clone it if it has updated
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
COMMAND ${GIT_EXECUTABLE} ls-remote ${MODULE_URL} master > ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
)
Of course this will only run once as CMake sees no reason to rerun it. I can force it to run by adding a second (dummy) output - CMake then recognises that this output doesn't exist and then reruns the rule. However the Makefile that this generates actually deletes module_VERSION.txt before running the command rendering the whole pursuit pointless (Ninja does not have this problem).
I am able to get this to work but in an extremely hacky way: creating another target that always runs and then generating a dependency on this.
# Use echo_append as a no-op
add_custom_command(
OUTPUT module_FORCERUN
COMMAND ${CMAKE_COMMAND} -E echo_append
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
COMMAND ${GIT_EXECUTABLE} ls-remote ${MODULE_URL} master > ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
DEPENDS module_FORCERUN
)
This seems just really hacky and like it could be relying on some corner cases in cmake which aren't guaranteed to be stable. Is there a better way to get this working?
I am using cmake 3.21.3
Use add_custom_target for implement "always run" functionality and via BYPRODUCTS keyword specify the file which it could produce/update:
add_custom_target(update_module_version
BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
COMMAND ${GIT_EXECUTABLE} ls-remote ${MODULE_URL} master > ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
)
That way, if any other target will depend on update_module_version one, the module_VERSION.txt file will be created/updated before evaluation of the target.
Such target-level dependency will be created automatically by CMake, if given file will be listed as dependency for target/command in the same directory, where target update_module_version is created:
add_custom_command(OUTPUT <...>
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
COMMAND <...>
)
From other directories the target-level dependency should be specified explicitly:
# If used in other directories
add_custom_command(OUTPUT <...>
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
update_module_version
COMMAND <...>
)

CMake do something when command fails

I use CMake. A custom build step saves error output when it fails.
FIND_PROGRAM (BASH bash HINTS /bin)
SET (BASH_CMD "my-script input.file >output.file 2>error.file")
ADD_CUSTOM_COMMAND (
OUTPUT output.file
COMMAND ${CMAKE_COMMAND} -E env ${BASH} -c "${BASH_CMD}"
...)
This works. If my-script fails for the given input.file then the stderr is saved in error.file, however when I run make and the target fails to build, the normal output does not make the location of error.file obvious. (The actual path of this file is generated in a tangly way.)
I don't want the noisy stderr to show in the terminal during make. I would like to do something like
MESSAGE ("input.file failed, see error.file")
(ideally coloured red or something) to be executed when the command for output.file failed.
Can I express this behaviour in CMakeLists.txt recipes?
Not sure about the highlighting, but you could create a cmake script file executing the command via execute_process, check it's error code and print a custom message in case there's an issue. The following example runs on windows, not on linux, but this should be sufficient for demonstration.
Some command that fails: script.bat
echo "some long message" 1>&2
exit 1
CMake script: execute_script_bat.cmake
execute_process(COMMAND script.bat RESULT_VARIBALE _EXIT_CODE ERROR_FILE error.log)
if (NOT _EXIT_CODE EQUAL 0)
message(FATAL_ERROR "command failed; output see ${CMAKE_SOURCE_DIR}/error.log")
endif()
CMakeLists.txt
add_custom_command(
OUTPUT output.file
COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_SOURCE_DIR}/execute_script_bat.cmake")
Additional info can be passed by adding -D "SOME_VARIABLE=some value" arguments after "${CMAKE_COMMAND}"

How do you make CMake print *all* commands not just build commands?

So you can use the VERBOSE option to get CMake to print all the compiler command lines to the console as it builds, but that doesn't seem to have any effect on other commands that aren't compiler commands, e.g. this execute_process command:
execute_process(
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" --gtest_list_tests
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
TIMEOUT ${_TEST_DISCOVERY_TIMEOUT}
OUTPUT_VARIABLE output
RESULT_VARIABLE result
)
It's actually part of the google test module in CMake. I had an issue where I needed to track the exact command line that was being executed. I was able to but only by manually hacking the files. If I had just been able to spew all the commands CMake was executing it would have been much much quicker.
Is there some way to do that in CMake?
To get the commands in execute_process printed, you can set where you want them printed individually, in each execute_process command using COMMAND_ECHO:
execute_process(
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" --gtest_list_tests
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
TIMEOUT ${_TEST_DISCOVERY_TIMEOUT}
OUTPUT_VARIABLE output
RESULT_VARIABLE result
COMMAND_ECHO STDOUT
)
or universally for all execute_process commands throughout your CMake project by setting this variable in your top-level CMake file:
set(CMAKE_EXECUTE_PROCESS_COMMAND_ECHO STDOUT)
These features are available in CMake versions 3.15 and above.
For what it's worth, you can also get the full printout of every command CMake runs (with expanded CMake variables) using the cmake command line option:
cmake --trace-expand ..
but this may be much more verbosity than you're looking for.

Have CMake not complain about non-existant file?

I know there probably isn't any workaround to this, but I need to generate a source file dllmain.c for my model.dll. This is done together with an executable that extracts some essential information for me, so my current CMakeLists looks like this
add_executable(main ${SOURCE} otherListSourcesGoHere)
add_custom_command(
TARGET main
POST_BUILD
COMMAND main.exe <--- Main built from above
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/jtox.py
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/dllgen.py <--- Python script generating a
sourcefile for my DLL
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_DIR}/build/dllmain.c ${PROJECT_DIR}
)
add_library(
${CMAKE_PROJECT_NAME}
SHARED
${PROJECT_DIR}/dllmain.c
otherListSoucrsGoHereAswell
)
But of course, as the dllmain.c file is non-existant before the exe has been completed and ran, I can't use this as it will return an error. Is is possible to execute this somehow without having to run two CMakeLists?
EDIT 1
With the help of #Angew I found out that you could specify the output of a file in a add_custom_command, so currently I have this instead
add_executable(main ${SOURCE} otherListSourcesGoHere)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dllmain.c
COMMAND main
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/jtox.py
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/dllgen.py -o ${CMAKE_CURRENT_BINARY_DIR}/dllmain.c
)
add_library(
${CMAKE_PROJECT_NAME}
SHARED
${CMAKE_CURRENT_BINARY_DIR}/dllmain.c
otherListSoucrsGoHereAswell
)
But I have the error
CMakeFiles\bil.dir\build.make:60: recipe for target '../dllmain.c' failed
CMakeFiles\Makefile2:66: recipe for target 'CMakeFiles/bil.dir/all' failed
makefile:82: recipe for target 'all' failed
I don't seem to find a solution. How can I go about debugging this issue?
EDIT 2
I fixed this issue by adding DEPENDS main on add_custom_command
add_custom_command(
OUTPUT ${PROJECT_DIR}/dllmain.c
DEPENDS main
COMMAND echo "Executing main.exe"
COMMAND main.exe
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND echo "JSON to XML with Python"
COMMAND python ${PYTHON_SOURCE_DIR}/jtox.py
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/dllgen.py -o ${PROJECT_DIR}
)
The correct way to do this is to tell CMake how to generate the file instead of doing it "manually" and keeping it secret from CMake. To do this, change your CMakeList like this:
add_executable(main ${SOURCE} otherListSourcesGoHere)
add_custom_command(
OUTPUT ${PROJECT_DIR}/dllmain.c
COMMAND main
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/jtox.py
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/dllgen.py
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_DIR}/build/dllmain.c ${PROJECT_DIR}
)
add_library(
${CMAKE_PROJECT_NAME}
SHARED
${PROJECT_DIR}/dllmain.c
otherListSoucrsGoHereAswell
)
This way, CMake will know that the file ${PROJECT_DIR}/dllmain.c is generated, and also how to generate it. It will correctly replace main with the executable built from target main, and introdce a proper build depencency.
Side note: you should consider modifying dllgen.py so that it's able to generate a file in a directory of your choice, and have it generate into the binary directory. That way, you will not pollute your source tree with build artefacts, which is a very desirable property: it allows you to revert to pristine state just by removing the binary dir. With that change, the CMakeList could look like this:
add_executable(main ${SOURCE} otherListSourcesGoHere)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dllmain.c
COMMAND main
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/jtox.py
COMMAND ${CMAKE_COMMAND} -E sleep 1
COMMAND python ${PYTHON_SOURCE_DIR}/dllgen.py -o ${CMAKE_CURRENT_BINARY_DIR}/dllmain.c
)
add_library(
${CMAKE_PROJECT_NAME}
SHARED
${CMAKE_CURRENT_BINARY_DIR}/dllmain.c
otherListSoucrsGoHereAswell
)

CMake command copy for symlink

I have following command:
add_custom_command(
TARGET Packaging POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
source_file
target_file )
Sometimes the source_file is a symlink. However, when it is copied, the referenced file is copied not the symlink.
How can I tell CMake command to copy the symlink as it is without de-referencing it?
It seems that there is no direct solution in CMake till now https://gitlab.kitware.com/cmake/cmake/issues/14609
A work around is to call a shell script that does the trick.
instead built-in "copy" you can use linux "cp" command with corresponded switches:
add_custom_command(
TARGET Packaging POST_BUILD
COMMAND cp -a -u
source_file
target_file )