Using generator expression in `cmake -E copy` command - cmake

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

Related

cmake3 external project add step loop

Currently I have External_Project_Add_Step to copy directory from "source dir" to "dest dir". Lately I have realized that I am copying around 4 GB for data in this process, while I would actually need to copy selective files ( many files but total size ~1 GB).
ExternalProject_Add_Step(${EXTERNAL_TARGET} lib_step
COMMAND ${CMAKE_COMMAND} -E make_directory ${TGT_DIR}
COMMAND ${CMAKE_COMMAND} -E copy_directory ${SRC_DIR} ${TGT_DIR}
COMMENT "Copying lib from ${SRC_DIR} to ${TGT_DIR}"
DEPENDEES install)
However I do have list of files which I want to copy. I was wondering if I can put loop inside External_Project_AddStep to have single step to copy all the files required.
Something as below (It doesn't work though)
ExternalProject_Add_Step(${EXTERNAL_TARGET} lib_step
COMMAND ${CMAKE_COMMAND} -E make_directory ${TGT_DIR}
foreach(copy_file ${ALL_FILES})
file(copy ${SRC_DIR}/${copy_file} ${TGT_DIR})
endforeach(copy_file)
COMMENT "Copying lib from ${SRC_DIR} to ${TGT_DIR}"
DEPENDEES install)
I believe probable solution could be have script being called via ExternalProject_Add_Step which does copy internally or have loop over ExternalProject_Add_Step with different step name. Not sure which is cleaner/better approach
In CMake functions calls and other constructions cannot be nested one into another. So, using foreach() command inside ExternalProject_Add_Step parameters is wrong.
But you may use foreach() for generate arguments for ExternalProject_Add_Step:
# This variable will contain list of 'COMMAND' clauses:
# COMMAND cmake -P copy <file-src> <file-dst>
set(COMMAND_COPY_FILES)
foreach(copy_file ${ALL_FILES})
list(APPEND COMMAND_COPY_FILES
COMMAND ${CMAKE_COMMAND} -E copy ${SRC_DIR}/${copy_file} ${TGT_DIR}/${copy_file}
)
endforeach(copy_file)
ExternalProject_Add_Step(${EXTERNAL_TARGET} lib_step
COMMAND ${CMAKE_COMMAND} -E make_directory ${TGT_DIR}
${COMMAND_COPY_FILES}
COMMENT "Copying lib from ${SRC_DIR} to ${TGT_DIR}"
DEPENDEES install)
In case of your files are direct childs of the source directory (that is, ALL_FILES does not contain subdirectories), you may use single invocation of copy:
# This variable will contain single 'COMMAND' clause but with many files:
# COMMAND cmake -P copy <src-files> <dest-dir>
set(COMMAND_COPY_FILES COMMAND ${CMAKE_COMMAND} -E copy)
foreach(copy_file ${ALL_FILES})
list(APPEND COMMAND_COPY_FILES ${SRC_DIR}/${copy_file})
endforeach(copy_file)
list(APPEND COMMAND_COPY_FILES ${TGT_DIR})

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 unzip automatically(while building) when the zip file changes

I am currently using execute_command to unzip some files before building.
I would like to unzip the folder that is in effect replace the old files with new file in the destination folder when the zip file in the source changes. Can anyone give me some suggestions?
Currently I am doing something like this,
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/abc.zip
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
I tried with add_custom_command() but I am executing it for every build.
I only want it to unzip if the source zip file changes.
add_custom_command( OUTPUT ${LibsList}
COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/libs.zip
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/libs.zip )
They mostly contain header files and a few shared libraries. I usually need the header files at build time and shared libraries at run time. If possible I would not give the list of files in OUTPUT as it is very large
I achieve this, at least for my scenario using the code below,
add_custom_target( unZip ALL)
add_custom_command(TARGET unZip PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/abc/
COMMAND ${CMAKE_COMMAND} -E tar xzf {CMAKE_SOURCE_DIR}/abc.zip
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/abc.zip
COMMENT "Unpacking abc.zip"
VERBATIM)
Now I made all target dependent on "unZip". This way when rebuilding unZip occurs or at build time if abc.zip changes, unZip occurs.

copy directory to another from add_custom_target

I have this target code:
add_custom_target (
dist
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}"
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/CMakeLists.txt ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/data ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/po ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}
COMMAND ${7Z} a -t7z ${PACKER_PACKAGE_FILE_NAME_EXT} ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}
COMMAND ${CMAKE_COMMAND} -E remove_directory "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}"
COMMENT "${PACKER_PACKAGE_FILE_NAME_EXT} created"
)
My goal is to copy directory (and its contents) to my directory ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-${PROJECT_VERSION}. The only file in the directory is CMakeLists.txt, the rest are just bunch of empty "src", "data" and "po" files, any ideas
You do it wrong. There is a better way to produce a distribution tarball.
Use install command and CPack module.
Here you may find a brief tutorial on CPack with example project.
If you want to copy a directory you should use cmake -E copy_directory.
But if you want to create a source package, please have a look into cpack, it can also create source packages.

CMake Custom Command copy multiple files

I am attempting to copy multiple files using the ${CMAKE_COMMAND} -E copy <from> <to> format, but I was wondering if there was a way to provide a number of files to copy to a specific directory. It seems the cmake copy only allows for one file to be copied at a time. I really don't want to use the copy command repeatedly when I would rather provide a list of files to copy as the first argument.
I'm thinking the easiest solution is to use the platform dependent "cp" command. While this definitely is not good for portability, our system is guaranteed to be built on Linux. A simple, platform independent solution would be better.
Copying multiple files is available from CMake 3.5
cmake -E copy <file>... <destination>
"cmake -E copy" support for multiple files
Command-Line Tool Mode
I did it with a loop
# create a list of files to copy
set( THIRD_PARTY_DLLS
C:/DLLFOLDER/my_dll_1.dll
C:/DLLFOLDER/my_dll_2.dll
)
# do the copying
foreach( file_i ${THIRD_PARTY_DLLS})
add_custom_command(
TARGET ${VIEWER_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND}
ARGS -E copy ${file_i} "C:/TargetDirectory"
)
endforeach( file_i )
A relatively simple workaround would be to use ${CMAKE_COMMAND} -E tar to bundle the sources, move the tarball and extract it in the destination directory.
This could be more trouble than it's worth if your sources are scattered across many different directories, since extracting would retain the original directory structure (unlike using cp). If all the files are in one directory however, you could achieve the copy in just 2 add_custom_command calls.
Say your sources to be moved are all in ${CMAKE_SOURCE_DIR}/source_dir, the destination is ${CMAKE_SOURCE_DIR}/destination_dir and your list of filenames (not full paths) are in ${FileList}. You could do:
add_custom_command(
TARGET MyExe POST_BUILD
COMMAND ${CMAKE_COMMAND} -E tar cfj ${CMAKE_BINARY_DIR}/temp.tar ${FileList}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/source_dir)
add_custom_command(
TARGET MyExe POST_BUILD
COMMAND ${CMAKE_COMMAND} -E rename ${CMAKE_BINARY_DIR}/temp.tar temp.tar
COMMAND ${CMAKE_COMMAND} -E tar xfj temp.tar ${FileList}
COMMAND ${CMAKE_COMMAND} -E remove temp.tar
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/destination_dir)
Since I had more or less exactly the same issue and didn't like the solutions above I eventually came up with this. It does more than just copy files, but I thought I would post the whole thing as it shows the flexibility of the the technique in conjunction with generator expressions that allow different files and directories depending on the build variant. I believe the COMMAND_EXPAND_LISTS is critical to the functionality here.
This function not only copies some files to a new directory but then runs a command on each of them. In this case it uses the microsoft signtool program to add digital signatures to each file.
cmake_minimum_required (VERSION 3.12)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
SET(ALL_3RD_PARTY_DLLS_DEBUG "${CMAKE_CURRENT_SOURCE_DIR}/file1.dll" "${CMAKE_CURRENT_SOURCE_DIR}/file2.dll")
SET(ALL_3RD_PARTY_DLLS_RELEASE "${CMAKE_CURRENT_SOURCE_DIR}/file3.dll" "${CMAKE_CURRENT_SOURCE_DIR}/file4.dll")
STRING(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}" ";${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug" ALL_OUTPUT_3RD_PARTY_DLLS_DEBUG ${ALL_3RD_PARTY_DLLS_DEBUG})
LIST(REMOVE_AT ALL_OUTPUT_3RD_PARTY_DLLS_DEBUG 0)
STRING(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}" ";${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release" ALL_OUTPUT_3RD_PARTY_DLLS_RELEASE ${ALL_3RD_PARTY_DLLS_RELEASE})
LIST(REMOVE_AT ALL_OUTPUT_3RD_PARTY_DLLS_RELEASE 0)
FILE(TO_NATIVE_PATH "C:\\Program\ Files\ (x86)\\Windows\ Kits\\10\\bin\\10.0.17763.0\\x86\\signtool.exe" SIGNTOOL_COMMAND)
add_custom_target(Copy3rdPartyDLLs ALL
COMMENT "Copying and signing 3rd Party DLLs"
VERBATIM
COMMAND_EXPAND_LISTS
COMMAND ${CMAKE_COMMAND} -E
make_directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<$<CONFIG:Release>:Release>$<$<CONFIG:Debug>:Debug>/"
COMMAND ${CMAKE_COMMAND} -E
copy_if_different
"$<$<CONFIG:Release>:${ALL_3RD_PARTY_DLLS_RELEASE}>"
"$<$<CONFIG:Debug>:${ALL_3RD_PARTY_DLLS_DEBUG}>"
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<$<CONFIG:Release>:Release>$<$<CONFIG:Debug>:Debug>/"
COMMAND ${SIGNTOOL_COMMAND} sign
"$<$<CONFIG:Release>:${ALL_OUTPUT_3RD_PARTY_DLLS_RELEASE}>"
"$<$<CONFIG:Debug>:${ALL_OUTPUT_3RD_PARTY_DLLS_DEBUG}>"
)
I hope this saves someone the day or so it took me to figure this out.