The paths you give to FILES are used to locate the files. You can think of the target_sources() command as using what you give there to work out the absolute path to each file. At generation time, CMake then looks at each file in turn and works out which of the BASE_DIRS that file sits under, and it removes the matching BASE_DIRS entry from the start of that file’s absolute path. What’s left is the path and file name that with be used when installing that file, treated as relative to the DESTINATION given in the install(TARGETS...) command.
Whether a file was originally specified as relative or absolute makes no difference to the above algorithm. Internally, it will effectively be converted to absolute anyway for the purposes of matching BASE_DIRS later.
In your examples, you use ${CMAKE_CURRENT_SOURCE_DIR} and ${CMAKE_SOURCE_DIR}. I’m not sure if you’re making errors or whether you did intend to use each of those. I would strongly discourage using ${CMAKE_SOURCE_DIR}. Imagine if some other project added yours as a vendored dependency. The ${CMAKE_SOURCE_DIR} is no longer going to be the same path relative to your project’s hierarchy as when the project is built standalone. I’m mentioning that because from what you posted, it was already not clear what path was intended by ${CMAKE_SOURCE_DIR}. It was only after downloading the zip file and looking at the project’s directory structure that I could work out what path was intended. For a command like target_sources(), try to avoid such “need to understand the whole project directory structure” variable usage. It will make the project easier to follow and less likely to have errors. I’ll also note that you have a similar problem with the usage of ${CMAKE_BINARY_DIR} instead of ${CMAKE_CURRENT_BINARY_DIR} in some places.
Focusing on some specific questions:
Yes, but perhaps $<BUILD_INTERFACE:...> and $<INSTALL_INTERFACE:...> might be special cases. The way BASE_DIRS is used, it doesn’t really make sense for its values to be different between build and install situations. The documentation could be updated to note that expressions like $<BUILD_INTERFACE:...> and $<INSTALL_INTERFACE:...> should not be used with BASE_DIRS.
I think this is more exposing that the way you’ve structured directories doesn’t quite match the expectations of file sets. Think of it as for each BASE_DIRS entry attached to a file set, it effectively adds a target_include_directories() call with that directory. That’s the first responsibility of BASE_DIRS. The second responsibility is constructing the relative path of files for installation, which I described in more detail above. Together, that implies that each header must be #include’d with a path under one of the BASE_DIRS.
For example, your original post specified the following:
target_sources(HelloWorld PUBLIC
FILE_SET generated_export_headers
TYPE HEADERS
BASE_DIRS
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/HelloWorld>
$<INSTALL_INTERFACE:${CMAKE_BINARY_DIR}>
FILES ${CMAKE_BINARY_DIR}/HelloWorld/helloworld_export.h)
That is capturing the confusion of what I think you’re falling victim to. The above is trying to say when building the project, there is no relative path to helloworld_export.h. Code would need to do a plain #include <helloworld_export.h> (angle brackets or quotes makes no difference here). However, once installed, the project is effectively trying to say (if $<INSTALL_INTERFACE:...> did actually work here) that code would need to use #include <HelloWorld/helloworld_export.h> instead. If that’s the intended path for the installed case, the project should use the exact same relative path when the project is being built too. It shouldn’t try to use different paths for the build versus install cases. I think that’s at the heart of your problems. If you address that, and avoid using ${CMAKE_SOURCE_DIR} or ${CMAKE_BINARY_DIR}, I think most of your issues will likely fall away and you’ll find no need to try to use generator expressions in BASE_DIRS.
You also might find it useful to be more explicit about your base directories by having a dedicated include directory in your directory structure. Part of your problem is that you’re trying to specify things that really belong in the parent directory of HelloWorld, but your project structure doesn’t really handle that well. Here’s what I recommend you try. I think you’ll then find things all fall out naturally and logically.
<topLevel>
+-- HelloWorld
+-- CMakeLists.txt
+-- include
| +-- CMakeLists.txt
| +-- HelloWorld
| +-- CMakeLists.txt
| +-- HelloWorld.h
+-- src
+-- CMakeLists.txt
+-- HelloWorld.cpp
HelloWorld/CMakeLists.txt:
add_executable(HelloWorld)
add_subdirectory(include)
add_subdirectory(src)
install(
TARGETS HelloWorld
EXPORT HelloWorldExport
FILE_SET HEADERS
)
HelloWorld/include/CMakeLists.txt:
target_sources(HelloWorld
PUBLIC
FILE_SET HEADERS
BASE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
add_subdirectory(HelloWorld)
HelloWorld/include/HelloWorld/CMakeLists.txt:
generate_export_header(HelloWorld)
target_sources(HelloWorld
PUBLIC
FILE_SET HEADERS
FILES
HelloWorld.h
${CMAKE_CURRENT_BINARY_DIR}/helloworld_export.h
)
HelloWorld/src/CMakeLists.txt:
target_sources(HelloWorld
PRIVATE
HelloWorld.cpp
)
I haven’t tested any of the above, but hopefully it sketches out enough for you to follow the idea. I use this structure with my consulting clients on very large projects, and it handles the file sets nicely.