Do generators expression work with BASE_DIRS in target_sources

I can post a longer example of this issue if needed but I wanted to see if what I am doing is just not expected to work. I have a CMakeLists.txt like

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)

When I use this it seems whatever I do the BUILD_INTERFACE is used in the install step. That is the install happens in the root install include directory never in a subdirectory called “HelloWorld” which is what I would have expected.

I assume I am doing something very dumb but in my defense BASE_DIRS is barley documented at all.

So should I expect generator expressions to work in BASE_DIRS?

I don’t think it makes sense to have separate build/install interfaces for BASE_DIRS. What is the goal here? To require helloworld_export.h when including in the source tree, but HelloWorld/helloworld_export.h when it is installed? Given that the header is meant to be included by public headers, how are both spellings going to be satisfied by the files that include it?

Some background. I was upgrading a project to use FILE_SET hopefully to simplify things.
I have attached an example project. I build into an out of source build directory and generate the Visual Studio solution using cmake -G "Visual Studio 17 2022" ../src
Note I can make all this work using some target_include_directories and the like but I was really trying to use FILE_SET as much as possible to learn stuff about it. This might just be pushing FILE_SET usage beyond what it is intended for?

So there is one project HelloWorld that lives in a sub directory HelloWorld The project uses

target_sources(HelloWorld PUBLIC 
   FILE_SET HEADERS 
   BASE_DIRS 
       ${CMAKE_SOURCE_DIR} 
   FILES HelloWorld.h)

for it’s one header. This all works well.
On install the HelloWorld.h header if install in include/HelloWorld/HelloWorld.h which is what I expect. This is using

install(
    TARGETS HelloWorld
    EXPORT HelloWorldExport
    FILE_SET HEADERS
)

The HelloWorld CMake also generates an export header using

generate_export_header(HelloWorld)

The helloworld_export.h is generated in the ${CMAKE_CURRENT_BINARY_DIR} as expected.
Now in HelloWorld.cpp this header is included as

#include "helloworld_export.h"

Using the double quotes in the #include here is the way I have seen in every project I have worked on. So to find this file I need ${CMAKE_CURRENT_BINARY_DIR} in the include path so I can do the following to achieve this

target_sources(HelloWorld PUBLIC 
   FILE_SET generated_export_headers 
   TYPE HEADERS
   ${CMAKE_CURRENT_BINARY_DIR}
   FILES ${CMAKE_CURRENT_BINARY_DIR}/helloworld_export.h)

So I add this file set to the install to get

install(
    TARGETS HelloWorld
    EXPORT HelloWorldExport
    FILE_SET HEADERS
    FILE_SET generated_export_headers
)

However the install is then

1>-- Up-to-date: .../install/include/HelloWorld/HelloWorld.h
1>-- Up-to-date: .../install/include/helloworld_export.h

However this is not what I want. I want the helloworld_export.h to be in the HelloWorld directory beside the HelloWorld.h file. So the install path I want includes the HelloWorld directory. This can be done using the following

target_sources(HelloWorld PUBLIC 
   FILE_SET generated_export_headers 
   TYPE HEADERS
   BASE_DIRS ${CMAKE_BINARY_DIR}
   FILES ${CMAKE_CURRENT_BINARY_DIR}/helloworld_export.h)

This does what I want on install

1>-- Up-to-date: .../install/include/HelloWorld/HelloWorld.h
1>-- Up-to-date: .../install/include/HelloWorld/helloworld_export.h

However that makes the build fail as it can not longer find helloworld_export.h. So I thought I would use a different BASE_DIR for build and install like so

target_sources(HelloWorld PUBLIC 
   FILE_SET generated_export_headers 
   TYPE HEADERS
   BASE_DIRS $<BUILD_LOCAL_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> $<INSTALL_INTERFACE:${CMAKE_BINARY_DIR}>
   FILES ${CMAKE_CURRENT_BINARY_DIR}/helloworld_export.h)

However that still installs

1>-- Up-to-date: .../install/include/HelloWorld/HelloWorld.h
1>-- Up-to-date: .../install/include/helloworld_export.h

So it seems like the generator expressions aren’t working like the BUILD_LOCAL_INTERFACE still triggers in an install command.

Example.zip (1.8 KB)

Try this:

install(
    TARGETS HelloWorld
    EXPORT HelloWorldExport
    FILE_SET HEADERS
    FILE_SET generated_export_headers
      DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/HelloWorld"
)

It’s not clear to me why the default destination for HEADERS would be different than any other type-HEADERS file set though…

Ah, this is the problem. The path to HelloWorld.h from the basedir is HelloWorld/HelloWorld.h, so it is installed as such. Either use ${CMAKE_CURRENT_SOURCE_DIR} as a base directory or do:

Basically, the path from the containing BASE_DIR to the file is a “cononical name” for the header and should be included as such.