Creating a git commit id file in the project build folder using CMake - cmake

First, I define the COMMIT_ID variable:
execute_process(COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE COMMIT_ID )
If you specify the COMMIT_ID variable the project is not built:
add_custom_command(TARGET ${APP_NAME} POST_BUILD
WORKING_DIRECTORY
$<TARGET_FILE_DIR:${APP_NAME}>
DEPENDS
${COMMIT_ID}
COMMAND
${CMAKE_COMMAND} -E echo ${COMMIT_ID} > ./version.md
COMMENT
"Generating file version.md"
VERBATIM)
But, if you specify a static string the project is built without errors:
add_custom_command(TARGET ${APP_NAME} POST_BUILD
WORKING_DIRECTORY
$<TARGET_FILE_DIR:${APP_NAME}>
DEPENDS
${COMMIT_ID}
COMMAND
${CMAKE_COMMAND} -E echo "COMMIT_ID" > ./version.md
COMMENT
"Generating file version.md"
VERBATIM)

The issue with using the ${COMMIT_ID} variable is that it may contain trailing whitespace or newlines from its creation in execute_process. You can add the OUTPUT_STRIP_TRAILING_WHITESPACE argument to cleanup the output of execute_process before it is used in the variable:
execute_process(COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE COMMIT_ID
OUTPUT_STRIP_TRAILING_WHITESPACE
)

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 <...>
)

Passing environment variable to the COMMAND in CMake execute_process

I have the following CMake snippet that runs COMMAND in WORKING_DIRECTORY. I tried different ways to pass the environment variable (MBEDTLS_INCLUDE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/../mbedtls/mbedtls/include) but without success.
The snippet that works (without env variable):
set(BUILD_CMD cargo build --features parsec-client/no-fs-permission-check)
set(WORKING_DIR "${CMAKE_CURRENT_SOURCE_DIR}/parsec_se_driver")
execute_process( COMMAND ${BUILD_CMD}
RESULT_VARIABLE CMD_ERROR
WORKING_DIRECTORY ${WORKING_DIR} )
if(NOT ${CMD_ERROR} MATCHES "0")
MESSAGE(SEND_ERROR "BUILD_CMD STATUS:" ${CMD_ERROR})
endif()
How can I pass the env variable to the execute_process?
If I write something like this:
execute_process( COMMAND MBEDTLS_INCLUDE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/../mbedtls/mbedtls/include cargo build --features parsec-client/no-fs-permission-check
RESULT_VARIABLE CMD_ERROR
WORKING_DIRECTORY ${WORKING_DIR} )
or taking different parts to variables, or adding quotes, I get:
BUILD_CMD STATUS:No such file or directory
As recommended in the CMake mailing list here, your solution using set(ENV ...) is perfectly valid:
set(ENV{MBEDTLS_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../mbedtls/mbedtls/include)
execute_process(
COMMAND ${BUILD_CMD}
RESULT_VARIABLE CMD_ERROR
WORKING_DIRECTORY ${WORKING_DIR}
)
You could also use CMake's command line utility to run the command in a modified environment using cmake -E env:
execute_process(
COMMAND ${CMAKE_COMMAND} -E env
MBEDTLS_INCLUDE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../mbedtls/mbedtls/include" ${BUILD_CMD}
RESULT_VARIABLE CMD_ERROR
WORKING_DIRECTORY ${WORKING_DIR}
)

How to move a file download from configure step to build step?

Let's say I have the block below in my CMakeLists.txt.
file (DOWNLOAD http://.../file.zip "${CMAKE_BINARY_DIR}/file.zip")
execute_process (
COMMAND "${CMAKE_COMMAND}" -E tar -xf file.zip
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
)
add_custom_command (
OUTPUT output.txt
COMMAND "${MY_COMMAND}" file-found-in-zip.txt output.txt
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
)
The basic steps are:
Download.
Extract.
Add a custom command that uses a file that was extracted in step 2.
During the build step, the custom command may or may not be executed, but the download and extraction always will. How can I make the download and extraction conditional, such that it happens only if the custom command that needs it will be executed?
I guess something along:
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/download.cmake
"file(DOWNLOAD http://.../file.zip ${CMAKE_CURRENT_BINARY_DIR}/file.zip"
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file.zip
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/download.cmake
VERBATIM
)
execute_process (
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file-found-in-zip.txt
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/file.zip
COMMAND ${CMAKE_COMMAND} -E tar -xf file.zip
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM
)
add_custom_command (
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/output.txt
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/file-found-in-zip.txt
COMMAND "${MY_COMMAND}" file-found-in-zip.txt output.txt
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
VERBATIM
)
Or just:
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/download.cmake
"file(DOWNLOAD http://.../file.zip ${CMAKE_CURRENT_BINARY_DIR}/file.zip"
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/output.txt
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/download.cmake
COMMAND ${CMAKE_COMMAND} -E tar -xf file.zip
COMMAND "${MY_COMMAND}" file-found-in-zip.txt output.txt
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM
)

Using generator expression in `cmake -E copy` command

I am trying to copy dll files from my bin folder to a different folder. I want to copy files from bin/Debug when building in Debug and from bin/Release when building in Release. This is what I currently use (and which does not work).
file(GLOB library_files_debug ${outputdirectory_root}/Debug/*.dll)
file(GLOB library_files_release ${outputdirectory_root}/Release/*.dll)
add_custom_target(copy_dlls_to_wheel ALL
DEPENDS setup.py
COMMAND ${CMAKE_COMMAND} -E echo "Debug files: $<$<CONFIG:Debug>:${library_files_debug}>"
COMMAND ${CMAKE_COMMAND} -E echo "Release files: $<$<CONFIG:Release>:${library_files_release}>"
COMMAND ${CMAKE_COMMAND} -E echo "Destination dir: ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
COMMAND ${CMAKE_COMMAND} -E copy $<$<CONFIG:Debug>:${library_files_debug}> $<$<CONFIG:Release>:${library_files_release}> ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}
)
I am running on Windows 10, and use Visual Studio to build. When the above target copy_dlls_to_wheel is built in Debug, the first echo statement prints out the correct dll files, and the second echo is empty. However, no files are copied. Instead I get the error message The system cannot find the path specified.
I have also tried to replace the last line with
COMMAND ${CMAKE_COMMAND} -E copy $<$<CONFIG:Debug>:${library_files_debug}> ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}
, but I get the same result.
However, when I remove the generator expression, and use
COMMAND ${CMAKE_COMMAND} -E copy ${library_files_debug} ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}
the files are copied correctly to my output folder. I am pretty confident my generator expression is correct, since I get the expected output from the echo commands. Are generator expressions not supported when using cmake -E copy, or is there something else I am doing wrong?
CMake's command line copy is able to process multiple files when you simply provide a list, which is why this works:
COMMAND ${CMAKE_COMMAND} -E copy ${library_files_debug} ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}
This is expanded to a space-separated list of files when the copy command is ultimately executed, which is the expected syntax.
cmake.exe -E copy mylib1.dll mylib2.dll /your/binary/dir/python/proj
However, when wrapped in a generator expression, the list will not be interpreted correctly by CMake. While the generator expression will be evaluated correctly, the list will be kept as a semicolon-separated list of files, which is the incorrect syntax:
cmake.exe -E copy "mylib1.dll;mylib2.dll" /your/binary/dir/python/proj
This causes the copy command to fail.
To work-around this issue, you could loop over each DLL file you want to copy, if there aren't too many. Something like this could work:
# Loop through the Debug files.
foreach(cur_file ${library_files_debug})
get_filename_component(file_name ${cur_file} NAME)
add_custom_target(copy_dlls_to_wheel_debug_${file_name} ALL
DEPENDS setup.py
COMMAND ${CMAKE_COMMAND} -E echo "DLL file: ${cur_file}"
COMMAND ${CMAKE_COMMAND} -E echo "Destination dir: ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
COMMAND ${CMAKE_COMMAND} -E copy $<$<CONFIG:Debug>:${cur_file}> ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}
)
endforeach()
# Loop through the Release files.
foreach(cur_file ${library_files_release})
get_filename_component(file_name ${cur_file} NAME)
add_custom_target(copy_dlls_to_wheel_release_${file_name} ALL
DEPENDS setup.py
COMMAND ${CMAKE_COMMAND} -E echo "DLL file: ${cur_file}"
COMMAND ${CMAKE_COMMAND} -E echo "Destination dir: ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
COMMAND ${CMAKE_COMMAND} -E copy $<$<CONFIG:Release>:${cur_file}> ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}
)
endforeach()
A quicker solution might be to bundle up your DLLs, using CMake's tar command line utility, copy them, then extract them, as suggested in this answer. CMake's tar command does not seem to accept lists wrapped in generator expressions either, so the list of files to bundle together is written to a file.
file(GLOB library_files_debug ${outputdirectory_root}/Debug/*.dll)
file(GLOB library_files_release ${outputdirectory_root}/Release/*.dll)
# Write the filenames (not full path) of the files to pack to file
set(debug_content "")
set(release_content "")
foreach(lib_file ${library_files_debug})
get_filename_component(file_name ${lib_file} NAME)
set(debug_content "${debug_content}${file_name}\n")
endforeach(lib_file ${library_files_debug})
foreach(lib_file ${library_files_release})
get_filename_component(file_name ${lib_file} NAME)
set(release_content "${release_content}${file_name}\n")
endforeach(lib_file ${library_files_release})
set(filenames_debug ${outputdirectory_root}/debug_files.txt)
set(filenames_release ${outputdirectory_root}/release_files.txt)
file(WRITE ${filenames_debug} ${debug_content})
file(WRITE ${filenames_release} ${release_content})
# Read either the list of debug or release files, and pack files
add_custom_command(
TARGET bdist PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E tar "cfj" ${outputdirectory_root}/temp.tar --files-from="$<IF:$<CONFIG:Debug>,${filenames_debug},${filenames_release}>"
WORKING_DIRECTORY ${outputdirectory_root}/$<CONFIG>)
# Unpack the files in the folder building the python wheel
add_custom_command(
TARGET bdist PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E rename ${outputdirectory_root}/temp.tar temp.tar
COMMAND ${CMAKE_COMMAND} -E tar "xfj" temp.tar
COMMAND ${CMAKE_COMMAND} -E remove temp.tar
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME})

Add files in sub folders to target

I'd like to add output files in subdirectory to a build target.
I wrote in CMakeLists.txt like below:
file(GLOB srcfiles "src/*.txt")
add_custom_target(subtask ALL)
set(dest_dir ${PROJECT_SOURCE_DIR}/sub/)
foreach(srcfile ${srcfiles})
string(REGEX REPLACE "^.*/(.*).txt$" filename ${srcfile})
add_custom_command(OUTPUT ${dest_dir}/${filename}.txt
COMMAND ${CMAKE_COMMAND} ARGS -E make_directory ${dest_dir}
COMMAND ${PROJECT_SOURCE_DIR}/process.sh ARGS ${srcfile}
MAIN_DEPENDENCY ${PROJECT_SOURCE_DIR}/process.sh)
add_dependencies(subtask ${dest_dir}/${filename}.txt)
endforeach(srcfile)
And executed:
mkdir build && cmake .. && make
But sub/*.txt are not created after build.
How should I do to build all commands on build?
updated (2017/2/4)
I solved the issue: use add_custom_command for each target and then declare add_custom_target that depends on all targets of add_custom_command.
set(TARGET_FILES "")
file(GLOB SRC_FILES "src/*.txt")
foreach(SRC_FILE ${SRC_FILES})
string(REGEX REPLACE "^.*/(.*).txt$" "\\1-foo.txt" TARGET_FILE ${SRC_FILE})
add_custom_command(
OUTPUT ${PROJECT_SOURCE_DIR}/sub/${TARGET_FILE}
COMMAND ${CMAKE_COMMAND} ARGS -E make_directory ${PROJECT_SOURCE_DIR}/sub
COMMAND ${PROJECT_SOURCE_DIR}/process.sh ARGS ${SRC_FILE}
MAIN_DEPENDENCY ${PROJECT_SOURCE_DIR}/process.sh)
list(APPEND TARGET_FILES "${PROJECT_SOURCE_DIR}/sub/${TARGET_FILE}")
endforeach()
add_custom_target(foo_txt ALL DEPENDS ${TARGET_FILES})
It's a little unclear what you are trying to achieve, but just from looking at it I would say it should be:
set(dest_dir ${PROJECT_SOURCE_DIR}/sub)
add_custom_target(
subtask ALL
COMMAND ${CMAKE_COMMAND} -E make_directory ${dest_dir}
COMMAND ${PROJECT_SOURCE_DIR}/process.sh
)
Only if the output.txt is an input for something else, you need a custom command:
set(dest_dir ${PROJECT_SOURCE_DIR}/sub)
add_custom_command(
OUTPUT ${dest_dir}/output.txt
COMMAND ${CMAKE_COMMAND} -E make_directory ${dest_dir}
COMMAND ${PROJECT_SOURCE_DIR}/process.sh
MAIN_DEPENDENCY ${PROJECT_SOURCE_DIR}/process.sh
)
add_custom_target(
subtask ALL
DEPENDS ${dest_dir}/output.txt
)
Note that the default working directory for those commands is CMAKE_CURRENT_BINARY_DIR.
Edit: I think the problem in your code is the use of add_dependencies() for file level dependencies. But add_dependencies() can only be used to declare target dependencies.
Edit: With a foreach() you can either collect the dependencies or APPEND them with to a dummy output. The first looks something like this:
file(GLOB srcfiles "src/*.txt")
set(dest_dir "${PROJECT_SOURCE_DIR}/sub")
file(MAKE_DIRECTORY "${dest_dir}")
foreach(srcfile ${srcfiles})
get_filename_component(filename "${srcfile}" NAME_WE)
add_custom_command(
OUTPUT "${dest_dir}/${filename}.txt"
COMMAND ${PROJECT_SOURCE_DIR}/process.sh ${srcfile}
MAIN_DEPENDENCY "${srcfile}"
DEPENDS "${PROJECT_SOURCE_DIR}/process.sh"
WORKING_DIRECTORY "${dest_dir}"
)
list(APPEND subtask_deps "${dest_dir}/${filename}.txt")
endforeach(srcfile)
add_custom_target(
subtask ALL
DEPENDS ${subtask_deps}
)