FILE_SET functionality is unaware of frameworks
The new FILE_SET functionality in CMake 3.23 provides the ability to install headers with paths relative to a base directory. This makes the approach superior to the capabilities provided by PUBLIC_HEADER
and PRIVATE_HEADER
, which flatten directory structures. Unfortunately, it appears that file sets do not mix well with framework library targets. When a HEADER file set is defined on a framework target, CMake has all the information needed to copy PUBLIC and INTERFACE header files to a framework's Headers
directory, and copy PRIVATE header files to a framework's PrivateHeaders
directory. This is something that should happen at build time, not install time. A framework may later be installed using install(TARGET)
, which installs the whole framework, including its headers (this was already the pre-existing behavior before file sets came along). At the moment, file sets do not copy headers into a framework at build time, which seems like an oversight.
If a FILE_SET
is listed in the install(TARGETS)
command and the target is a framework, there's a good argument against that file set's headers being explicitly installed. Any headers already in the framework will be installed as part of the FRAMEWORK
block of the install()
command. Unfortunately, as of CMake 3.23.0, such file sets will be installed, but the install destination is not relative to the framework but rather to the main base install location. For a framework header, the actual install location should be under paths of the form SomeName.framework/Version/A/Headers
or SomeName.framework/Version/A/PrivateHeaders
(note that the A
could be something else, controlled by the FRAMEWORK_VERSION
target property). For added complexity, if the project is cross-platform and the target will be an ordinary library on non-Apple platforms, then the file set will very likely have SomeName
at the start of its relative paths. But frameworks have special rules around how #include
works. Consider the following example:
#include <SomeName/blah.h>
If the compiler command line has a -F
option pointing at the parent of the SomeName.framework
directory, then an #include
line like the above would find a header located at SomeName.framework/Headers/blah.h
(there are usually symlinks created which would map SomeName.framework/Headers
to SomeName.framework/Versions/Current/Headers
). If header file sets did copy the headers into the framework at build time, they would also need to strip off the leading SomeName
, if present, to make this expected pattern work.
As issues like #16739 highlight, being able to preserve paths for headers copied into frameworks is definitely something users have wanted. While it is not "the Apple way", it is nonetheless very desirable (and valid) for cross-platform projects. The PUBLIC_HEADER and PRIVATE_HEADER functionality doesn't provide for that, which forces projects to do things manually with the MACOSX_PACKAGE_LOCATION
source file property instead. That seems like a wart that file sets should have been able to remove, but we appear to have overlooked frameworks in the design and implementation. That decreases the value of file sets for multi-platform projects that provide libraries as frameworks on Apple platforms.
I'm not sure what our options are from here. On the one hand, I would consider these things to be bugs in the file sets implementation. They actively do the wrong thing for framework targets. But now that 3.23.0 has been released, projects may start implementing workarounds to try to (ab)use file sets to still install headers into frameworks as at least a partial solution. Those workarounds might have the potential to break if we fix the problems identified above. I suspect we're stuck with this for 3.23 and would need a policy to provide the more complete and consistent behavior in 3.24.