25

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.

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
jluzwick
  • 2,005
  • 1
  • 15
  • 23

4 Answers4

15

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

lukee
  • 843
  • 9
  • 12
13

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 )
Knitschi
  • 2,822
  • 3
  • 32
  • 51
  • 1
    This would work, but wouldn't this create a massive makefile when attempting to copy hundreds of files? – jluzwick Jun 09 '13 at 23:29
  • 1
    I guess so, but you did not write that you had massive amounts of files. Sorry that I could not help you. – Knitschi Jun 27 '13 at 18:01
  • If your files are in a source directory you should concat each file name to ${CMAKE_CURRENT_SOURCE_DIR}. Also if you want to copy to the current bin dir. For example ARGS -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${file_i} ${CMAKE_CURRENT_BINARY_DIR} – aled Mar 26 '14 at 16:02
  • 3
    On Windows (at least) this actually gets concatenated into a very long single command line, sufficiently long that cmd.exe can actually complain and refuses to execute it. CMake really needs a better way of doing this. – Alastair Maw Jun 17 '14 at 19:05
10

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)
Fraser
  • 74,704
  • 20
  • 238
  • 215
  • While I like your approach here, I'm still looking for something a bit more simple than using tar. Now maybe putting this into a CMake function in some include, that might work. – jluzwick Jan 18 '13 at 23:19
  • Yeah - I was hoping someone else would have a better answer. You could indeed create a CMake function which would do this internally, including getting just the final component of each source file's path (using [`get_filename_component( FileName NAME)`](http://www.cmake.org/cmake/help/v2.8.10/cmake.html#command:get_filename_component)) to better simulate the behaviour of `cp`. – Fraser Jan 18 '13 at 23:46
3

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.

David
  • 51
  • 3
  • I like your example, @David. But I do not grok it yet. What do you call the `$<$:${ALL_3RD_PARTY_DLLS_RELEASE}>` syntax, so that can keep searching for documentation about what it does? I understand that `${ALL_3RD_PARTY_DLLS_RELEASE}` is the list of file paths to iterate thru. But, I do not understand what, in general, should be in the tokens before that. – Mike Finch Oct 14 '19 at 23:02
  • @MikeFinch , `${ALL_3RD_PARTY_DLLS_RELEASE}` and `${ALL_3RD_PARTY_DLLS_DEBUG}` contain the list of files to copy for respective configurations. The conditional statement like `$<$:${ALL_3RD_PARTY_DLLS_RELEASE}>` substitutes the list only for Release configuration - this part will expand to empty string for Debug configuration. – AntonK Sep 17 '22 at 20:31