larsjr
larsjr

Reputation: 675

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?

Upvotes: 4

Views: 2146

Answers (1)

Kevin
Kevin

Reputation: 18313

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

Upvotes: 2

Related Questions