install(EXPORT): May generate invalid INTERFACE_SOURCES with $<BUILD_INTERFACE> genex
When creating an interface library, sometimes it is useful to have a target_sources()
call for the build but not for the install. You end up with something like:
: : :
add_library(foo INTERFACE)
target_sources(foo INTERFACE $<BUILD_INTERFACE:${FOO_SOURCES}>)
: : :
install(
TARGETS foo
EXPORT foo-targets
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
)
: : :
When you do that, however, you end up with an entry in foo-targets.cmake
that will include something like:
set_target_properties(foo::foo PROPERTIES
INTERFACE_COMPILE_FEATURES "cxx_std_23"
INTERFACE_COMPILE_OPTIONS "/permissive-"
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
INTERFACE_SOURCES "C:/Users/awesome_coder/source/repos/foo/"
)
Note the INTERFACE_SOURCES
entry. This shows up because the $<BUILD_INTERFACE:...>
generator generates an empty string instead of nothing, the target_sources()
call includes that as a file (prepending with the directory), and the install(TARGETS...)
uses the invalid INTERFACE_SOURCES
property that contains a directory.
Needless to say, the result is wrong but since the failure could be considered in any of the three places, target_sources()
, $<BUILD_INTERFACE:...>
, or install(TARGET...)
. I suspect the best place to fix the issue is with target_sources()
requiring a "." if a directory is actually desired.
My current mitigation is to include something like the following as the last install()
entry:
set(FOO_TARGETS_FILE "${CMAKE_INSTALL_PREFIX}/lib/cmake/foo/foo-targets.cmake")
install(CODE
"file (STRINGS \"${FOO_TARGETS_FILE}\" FOO_TARGETS_FILE_LINES)
file(WRITE \"${FOO_TARGETS_FILE}\" \"\")
foreach(FOO_TARGETS_FILE_LINE IN LISTS FOO_TARGETS_FILE_LINES)
if (NOT FOO_TARGETS_FILE_LINE MATCHES \"^[ \t]*INTERFACE_SOURCES[ \t].*$\")
file(APPEND \"${FOO_TARGETS_FILE}\" \"\${FOO_TARGETS_FILE_LINE}\\n\")
endif()
endforeach()")
This reads the targets file into memory, clears the targets file, and then recreates it skipping the offending line.