I have a library with two public header files that need to be installed: one in ${CMAKE_INSTALL_INCLUDEDIR} and one in ${CMAKE_INSTALL_INCLUDEDIR}/SUBDIR. If I do
but I got an error from CMake that the target must be an executable, library or module target.
The solution I came up with is
get_target_property(framework mylib FRAMEWORK)
if (NOT ${framework})
install(DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/SUBDIR/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/SUBDIR
)
else()
# For frameworks, headers are installed in a different place For
# this to work this file must be in the library source list.
set_source_files_properties(
SUBDIR/b.h
PROPERTIES MACOSX_PACKAGE_LOCATION Headers/SUBDIR
)
endif()
but it is ugly and not linked to installing mylib.
I remotely remember myself trying to install a “non-flat” directory of public headers via PUBLIC_HEADER target property, but as I recall that didn’t work, so I had to either go with install(DIRECTORY ...) (like you did), or iterate a list of headers and do install(FILES ..) for each (using its RELATIVE_PATH) (which is even worse, unless one needs to install only some headers, hence the list).
Indeed, that would not(?) be “linked” to installing the library’s target, but you could probably get around that by assigning the same COMPONENT to those install() commands and then install that component instead of the library target.
But actually you might want to try using FILE_SET instead (if your CMake version is 3.23 or newer), because it does support what you are describing. So if you have the following structure in your project:
$ tree ./include/
├── a.h
└── SUBDIR
└── b.h
then the following should work:
# ...
target_sources(mylib
# actual sources
# PRIVATE
# ...
# public headers
PUBLIC
FILE_SET public_headers
TYPE HEADERS
BASE_DIRS include
FILES
include/a.h
include/SUBDIR/b.h
)
install(TARGETS mylib
EXPORT mylibTargets
FILE_SET public_headers
)
# the rest of the installation (configs, etc)
# ...
Do I have to put all my sources in target_sources. They are currently listed in the add_library command. Or can I have just the PUBLIC FILE SET part there?
Can I add the FILE_SET to the existing install(TARGETS …) that installs the library?
My target is only a framework when building for iOS. How do I deal with the framework in this case? Putting an if(NOT framework) around things can be used to hide them but how do I get the files installed. Do I have to use the source file properties trick?
You only need to list your headers in the target_sources() call when you’re adding them there to a FILE_SET. You cannot define file sets in the add_library() call.
Yes. The install(TARGETS...) form accepts FILE_SET as one of the artifact-kind options (see the docs).
Yes, most likely. MACOSX_PACKAGE_LOCATION should work for the framework case.
You mentioned that your target is only a framework when building for iOS. I assume you are producing some kind of SDK that will be distributed and others will build against? If not, then why add the headers to the framework? They would only be needed if building against the framework, not to simply use it at runtime.
Thanks everyone for your help. I am now using FILE_SET while also setting MACOSX_PACKAGE_LOCATION for the files. I am fortunate that I had only 2 header directories to deal with and therefore only 2 set_source_files_properties() commands. I had to put if(NOT <framework>) around the code adding the file set to the target to stop CMake raising the error that FILE_SETS are not allowed in frameworks.
Why is a PUBLIC FILE_SET of type HEADERS not handled the same way as the PUBLIC_HEADER item in an install command? It would make things easier.