3

I have the following project layout:

project/
├─ CMakeLists.txt
├─ main.cpp
╘═ my_lib/
   ├─ CMakeLists.txt
   ╞═ some_package1/
   │  ├── header.hpp
   │  └── impl.cpp
   ╘═ some_package2/
      ├── header.hpp
      └── impl.cpp

my_lib is added as a subdirectory by the CMakeLists.txt from project.
What I would like to have is a target in my_lib CMakeLists.txt which, when built, executes a command that copies all headers in my_lib to a destination directory, while preserving directory structure. The destination directory would be defined by the project CMakeLists.txt.

Basically, if I could somehow get add_custom_command to do this:

file(COPY ${CMAKE_SOURCE_DIR}/my_lib DESTINATION ${COPY_DEST_DIR} FILES_MATCHING PATTERN "*.hpp")

I would be extremely happy. But I haven't found a way to do that. This is very similar to an install target, except I need it done before anything gets even compiled.

I tried doing the following:

set(MY_LIB_HEADER_FILES
    some_package1/header.hpp
    some_package2/header.hpp
)

add_custom_target(copy_headers)
add_custom_command(
    TARGET copy_headers
    COMMAND ${CMAKE_COMMAND} -E copy "${MY_LIB_HEADER_FILES}" ${COPY_DEST_DIR}
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/my_lib
)

But that doesn't preserve directory structure, which in just this example case would lead to one header being overwritten by the other. I haven't been able to find a workaround to that.

The only solution I can imagine is a hack in which I loop over every file in ${MY_LIB_HEADER_FILES}, remove whatever follows the last forward slash, create that directory, and then copy the file over to it. But that really isn't something I want to resort to and come to think of it, I'm not even sure how I'd go about it considering it must be done in an add_custom_command call.

Surely there is a solution, right?

4
  • Did you try ${CMAKE_COMMAND} -E copy_directories instead of copy? Commented Nov 26, 2020 at 18:35
  • 1
    I would end up copying source files along with headers, as copy_directories offers no option to match files whatsoever. However, as was suggested in an answer which is seemingly no longer here, I could then use file(GLOB_RECURSE) to find source files in the destination directory and delete them. This is far from ideal but for lack of a better solution that's probably what I'll end up doing. Commented Nov 26, 2020 at 18:41
  • I hope you find a good solution. But you might also consider re-organizing your directory structure. I find it useful to have "external" header files (files which end users need to use the compiled code) in a separate directory from the source and internal header files. That makes it much easier to use something like copy_directories to install them. Commented Nov 27, 2020 at 3:22
  • @EricBackus that's actually sort of what I was trying to achieve. That may be personal but I'm not fond of the idea of manually maintaining a separate location for library headers (unless I misunderstood what you meant), hence why I want a script doing it automatically. What this conundrum led me to, however, is to reorganize my whole code so that the "copy everything before anything is compiled"-part is no longer a requirement. Regardless, the accepted answer provides a nice way to do it. :) Commented Nov 27, 2020 at 21:27

1 Answer 1

3

One of the ways to achieve this is to write a "CMake script" file that will contain your file(COPY ....) command and to execute it within add_custom_command. In brief, your code snippet would look like this:

#create script file during configure phase...
file(WRITE ${CMAKE_BINARY_DIR}/cp.cmake
  "file(COPY ${CMAKE_SOURCE_DIR}/my_lib DESTINATION ${COPY_DEST_DIR} FILES_MATCHING PATTERN *.hpp)\n"
)

#execute the script during the build phase...
add_custom_command(
  TARGET my_lib
  POST_BUILD
  COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cp.cmake
)

file(WRITE .... statement will create a new cmake script file within the root build folder named cp.cmake.

Note 1: The $ preceding variables CMAKE_SOURCE_DIR and COPY_DEST_DIR are not escaped, meaning that the cp.cmake will contain their evaluated values.

Note 2: It is assumed that your library creates a target named my_lib and add_custom_command is added as a POST_BUILD event for that target.

In the end, here is the cmake documentation about add_custom_command, file command and running CMake in script mode

Sign up to request clarification or add additional context in comments.

1 Comment

That's perfect. Thank you loads. 👍

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.