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}
)
Related
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
)
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
)
Suppose I have a custom target in CMake for unit tests like the below
add_custom_target(
test
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ATest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/BTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/DTest)
but I want to add an additional test to the target based on whether an external dependency is found. Currently, I did it with
if(EXTERNAL_FOUND)
add_custom_target(
test
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ATest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/BTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/DTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ETest)
else()
add_custom_target(
test
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ATest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/BTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/DTest)
endif()
This is not very elegant and it quickly becomes unmanageable when there are multiple conditions. Is there something like append to custom target so we can write the below instead?
add_custom_target(
test
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ATest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/BTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/DTest)
if(EXTERNAL_FOUND)
# I can't seem to find something like this
append_custom_target(test COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ETest)
else()
Or is there a better way to do this?
You can use add_custom_command and use it as dependency to your target. With the custom command you can APPEND commands with same OUTPUT:
add_custom_target(
test
DEPENDS test-cmd
)
add_custom_command(
OUTPUT test-cmd
COMMAND ${CMAKE_COMMAND} -E echo "ATest"
COMMAND ${CMAKE_COMMAND} -E echo "BTest"
COMMAND ${CMAKE_COMMAND} -E echo "CTest"
COMMAND ${CMAKE_COMMAND} -E echo "DTest"
)
if(EXTERNAL_FOUND)
add_custom_command(
OUTPUT test-cmd APPEND
COMMAND ${CMAKE_COMMAND} -E echo "ETest"
)
endif()
# test-cmd is not actually generated so set it to symbolic
set_source_files_properties(test-cmd PROPERTIES SYMBOLIC "true")
See SYMBOLIC for the artifical source file property.
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
)
I can not get environment at custom target shell.
CMakeList.txt
set( ENV{TEST_VAR} "Hello" )
add_custom_target( test
COMMAND ./test.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} )
test.sh
echo test:${TEST_VAR}
when try to "make test", shell can't get ${TEST_VAR}.
Thank you.
You have to use a trick because environment variables SET in the CMakeLists.txt only take effect for cmake itself, so you cannot use this method to set an environment variable that a custom command might need:
test.cmake
set( ENV{TEST_VAR} "Hello" )
execute_process(
COMMAND ./test.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} )
CMakeLists.txt
add_custom_target( test
COMMAND ${CMAKE_COMMAND} -P test.cmake )