FILE_SET include directory manipulation

I am trying to understand how to correctly install using FILE_SET for a very simple (i.e. flat) directory structure:

myproj/
|--- CMakeLists.txt
|--- foo.h
|--- foo.cpp

After installation, I want the file tree to be:

${PREFIX}/
|--- include/
|    |--- myproj/
|    |    |--- foo.h
|--- lib/
|    |--- libfoo.a

I tried doing this very simply (following cmake-buildsystem)

cmake_minimum_required(VERSION 3.23)

project(myproj CXX)

add_library(foo foo.cpp)
target_sources(
  foo PUBLIC
  FILE_SET HEADERS
    TYPE HEADERS
    FILES foo.h
)

install(
  TARGETS foo
  DESTINATION ${CMAKE_INSTALL_LIBDIR}
  INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  EXPORT myprojTargets
  FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/myproj
)

install(
  EXPORT myprojTargets
  NAMESPACE myproj::
  FILE myprojTargets.cmake
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj
)

This seems to put the files into the correct place, but if I then use this with find_package(myproj) and link against myproj::foo, I get ${PREFIX}/include/myproj in the include directories rather than simply ${PREFIX}/include.

How can I install header files (using FILE_SET) where I need to use ${CMAKE_CURRENT_SOURCE_DIR} itself as part of the installed path?

(For reference, I am trying to adapt an existing project where libraries were set up such that you could simply do a git clone directly into /usr/local/include and get the headers in the “right” place.)

EDIT: It seems as though the include path when imported is defined by FILE_SET DESTINATION.

Yes. The idea is that if myproj/ is part of the path to be included, it would be under the “BASE_DIRS” rather than straddling it. I think you want BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/.." FILES foo.h (with myproj coming from the name of the current directory) and then a DESTINATION include.

Yeah, I was just tinkering more and had come up with

cmake_path(GET CMAKE_CURRENT_SOURCE_DIR PARENT_PATH parent_path)
target_sources(
  foo PUBLIC
  FILE_SET HEADERS
    TYPE HEADERS
    BASE_DIRS "${parent_path}"
    FILES foo.h
)

install(
  TARGETS foo
  DESTINATION ${CMAKE_INSTALL_LIBDIR}
  INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  EXPORT myprojTargets
  FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

I think that’s equivalent? One could also do cmake_path(SET parent_path NORMALIZE "${CMAKE_CURRENT_SOURCE_DIR}/..") and should(?) get the same thing.

Yep, that should all be equivalent.

You shouldn’t use the name of the top level directory for your BASE_DIRS. That is not a directory name your project controls, the user could use a different name. If you’re using file sets, you pretty much MUST put your headers in a subdirectory.

Note also that you should avoid using your top level source directory as a BASE_DIRS too. It is quite common for CI systems and often CMake presets too to specify a build directory that is a subdirectory of the top level source directory. If you ever need to add a part of the build directory as a BASE_DIRS to a file set (which you would if you were using something like generate_export_header() from the GenerateExportHeader module), you won’t be able to if the top level source directory is already a base directory of the file set. File sets cannot have overlapping BASE_DIRS.

I don’t disagree, but this is a slow migration to CMake from a custom build system which made heavy use of Git submodules. It also made a point of not separating headers from sources, but instead grouped headers and sources into “libraries”. That is, a typical structure would be:

project/
|- Makefile
|- project.mk
|- libraries/
|  |- foo/
|  |  |- module.mk
|  |  |- foo1.h
|  |  |- foo1.cpp
|  |  |- foo2.h
|  |  |- foo2.cpp
|  |- bar/
|  |  |- module.mk
|  |  |- bar1.h
|  |  |- bar1.cpp
|  |  |- bar2.h
|  |  |- bar2.cpp
|- programs/
|  |- tools/
|  |  |- module.mk
|  |  |- tool1.cpp
|  |  |- tool2.cpp
|  |- utils/
|  |  |- module.mk
|  |  |- util1.cpp
|  |  |- util2.cpp

When one of the “libraries” (like foo or bar) got too big or needed to be used somewhere else, it would just get pulled out into its own repository and added to project/libraries/ as a (Git) submodule. Of course, that meant that now all of the headers and sources needed to be at top level of the new Git repository, since it had just been a subdirectory of a larger project.

EDIT: And, of course, it didn’t have a separate build tree, so object files would get scattered across all of those directories!

EDIT2: Even further, the old build system didn’t even have installing headers as an option – of course you would always build everything from scratch every time!