Commit 43919131 authored by Brad King's avatar Brad King
Browse files

Add INTERFACE libraries to generated buildsystem if they have SOURCES

INTERFACE libraries were created with the intention of collecting usage
requirements for use by other targets via `target_link_libraries`.
Therefore they were not allowed to have SOURCES and were not included in
the generated buildsystem.  In practice, this has become limiting:

* Header-only libraries do have sources, they just do not compile.
  Developers should be able to edit those sources (the header files)
  in their IDE.

* Header-only libraries may need to generate some of their header
  files via custom commands.

Some projects work around these limitations by pairing each interface
library with an `add_custom_target` that makes the header files and
custom commands appear in the generated buildsystem and in IDEs.

Lift such limitations by allowing INTERFACE libraries to have SOURCES.
For those with sources, add a corresponding build target to the
generated buildsystem.

Fixes: #19145
parent afb99870
......@@ -118,8 +118,35 @@ target using the commands:
and then it is used as an argument to :command:`target_link_libraries`
like any other target.
An interface library has no source files itself and is not included
as a target in the generated buildsystem.
An interface library created with the above signature has no source files
itself and is not included as a target in the generated buildsystem.
Since CMake 3.19, an interface library target may be created with
source files:
.. code-block:: cmake
add_library(<name> INTERFACE [<source>...] [EXCLUDE_FROM_ALL])
Source files may be listed directly in the ``add_library`` call or added
later by calls to :command:`target_sources` with the ``PRIVATE`` or
``PUBLIC`` keywords.
If an interface library has source files (i.e. the :prop_tgt:`SOURCES`
target property is set), it will appear in the generated buildsystem
as a build target much like a target defined by the
:command:`add_custom_target` command. It does not compile any sources,
but does contain build rules for custom commands created by the
:command:`add_custom_command` command.
.. note::
In most command signatures where the ``INTERFACE`` keyword appears,
the items listed after it only become part of that target's usage
requirements and are not part of the target's own settings. However,
in this signature of ``add_library``, the ``INTERFACE`` keyword refers
to the library type only. Sources listed after it in the ``add_library``
call are ``PRIVATE`` to the interface library and do not appear in its
:prop_tgt:`INTERFACE_SOURCES` target property.
Imported Libraries
^^^^^^^^^^^^^^^^^^
......
......@@ -922,8 +922,8 @@ property from it:
Interface Libraries
-------------------
An ``INTERFACE`` target has no :prop_tgt:`LOCATION` and is mutable, but is
otherwise similar to an :prop_tgt:`IMPORTED` target.
An ``INTERFACE`` library target does not compile sources and does not
produce a library artifact on disk, so it has no :prop_tgt:`LOCATION`.
It may specify usage requirements such as
:prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES`,
......@@ -937,11 +937,22 @@ Only the ``INTERFACE`` modes of the :command:`target_include_directories`,
:command:`target_sources`, and :command:`target_link_libraries` commands
may be used with ``INTERFACE`` libraries.
Since CMake 3.19, an ``INTERFACE`` library target may optionally contain
source files. An interface library that contains source files will be
included as a build target in the generated buildsystem. It does not
compile sources, but may contain custom commands to generate other sources.
Additionally, IDEs will show the source files as part of the target for
interactive reading and editing.
A primary use-case for ``INTERFACE`` libraries is header-only libraries.
.. code-block:: cmake
add_library(Eigen INTERFACE)
add_library(Eigen INTERFACE
src/eigen.h
src/vector.h
src/matrix.h
)
target_include_directories(Eigen INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:include/Eigen>
......@@ -980,7 +991,12 @@ to must be installed separately:
.. code-block:: cmake
add_library(Eigen INTERFACE)
set(Eigen_headers
src/eigen.h
src/vector.h
src/matrix.h
)
add_library(Eigen INTERFACE ${Eigen_headers})
target_include_directories(Eigen INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:include/Eigen>
......@@ -990,9 +1006,6 @@ to must be installed separately:
install(EXPORT eigenExport NAMESPACE Upstream::
DESTINATION lib/cmake/Eigen
)
install(FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/eigen.h
${CMAKE_CURRENT_SOURCE_DIR}/src/vector.h
${CMAKE_CURRENT_SOURCE_DIR}/src/matrix.h
install(FILES ${Eigen_headers}
DESTINATION include/Eigen
)
......@@ -14,3 +14,11 @@ Properties starting with ``INTERFACE_`` or ``IMPORTED_`` are not allowed as
they are reserved for internal CMake use.
Properties containing generator expressions are also not allowed.
.. note::
Since CMake 3.19, :ref:`Interface Libraries` may have arbitrary
target properties. If a project exports an interface library
with custom properties, the resulting package may not work with
dependents configured by older versions of CMake that reject the
custom properties.
build-interface-targets
-----------------------
* :ref:`Interface Libraries` may now have source files added via
:command:`add_library` or :command:`target_sources`. Those
with sources will be generated as part of the build system.
......@@ -2,8 +2,6 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmAddLibraryCommand.h"
#include <cmext/algorithm>
#include "cmExecutionStatus.h"
#include "cmGeneratorExpression.h"
#include "cmGlobalGenerator.h"
......@@ -111,20 +109,10 @@ bool cmAddLibraryCommand(std::vector<std::string> const& args,
"INTERFACE library specified with conflicting ALIAS type.");
return false;
}
if (excludeFromAll) {
status.SetError(
"INTERFACE library may not be used with EXCLUDE_FROM_ALL.");
return false;
}
++s;
type = cmStateEnums::INTERFACE_LIBRARY;
haveSpecifiedType = true;
} else if (*s == "EXCLUDE_FROM_ALL") {
if (type == cmStateEnums::INTERFACE_LIBRARY) {
status.SetError(
"INTERFACE library may not be used with EXCLUDE_FROM_ALL.");
return false;
}
++s;
excludeFromAll = true;
} else if (*s == "IMPORTED") {
......@@ -143,10 +131,6 @@ bool cmAddLibraryCommand(std::vector<std::string> const& args,
}
if (type == cmStateEnums::INTERFACE_LIBRARY) {
if (s != args.end()) {
status.SetError("INTERFACE library requires no source arguments.");
return false;
}
if (importGlobal && !importTarget) {
status.SetError(
"INTERFACE library specified as GLOBAL, but not as IMPORTED.");
......@@ -302,8 +286,6 @@ bool cmAddLibraryCommand(std::vector<std::string> const& args,
}
}
std::vector<std::string> srclists;
if (type == cmStateEnums::INTERFACE_LIBRARY) {
if (!cmGeneratorExpression::IsValidTargetName(libName) ||
libName.find("::") != std::string::npos) {
......@@ -311,14 +293,10 @@ bool cmAddLibraryCommand(std::vector<std::string> const& args,
cmStrCat("Invalid name for INTERFACE library target: ", libName));
return false;
}
mf.AddLibrary(libName, type, srclists, excludeFromAll);
return true;
}
cm::append(srclists, s, args.end());
mf.AddLibrary(libName, type, srclists, excludeFromAll);
std::vector<std::string> srcs(s, args.end());
mf.AddLibrary(libName, type, srcs, excludeFromAll);
return true;
}
......@@ -1099,6 +1099,10 @@ bool cmGeneratorTarget::IsInBuildSystem() const
case cmStateEnums::GLOBAL_TARGET:
return true;
case cmStateEnums::INTERFACE_LIBRARY:
// An INTERFACE library is in the build system if it has SOURCES.
if (!this->SourceEntries.empty()) {
return true;
}
case cmStateEnums::UNKNOWN_LIBRARY:
break;
}
......@@ -1543,7 +1547,6 @@ std::vector<BT<std::string>> cmGeneratorTarget::GetSourceFilePaths(
std::string const& config) const
{
std::vector<BT<std::string>> files;
assert(this->GetType() != cmStateEnums::INTERFACE_LIBRARY);
if (!this->LocalGenerator->GetGlobalGenerator()->GetConfigureDoneCMP0026()) {
// At configure-time, this method can be called as part of getting the
......@@ -1735,9 +1738,11 @@ void cmGeneratorTarget::ComputeKindedSources(KindedSources& files,
std::string ext = cmSystemTools::LowerCase(sf->GetExtension());
if (sf->GetCustomCommand()) {
kind = SourceKindCustomCommand;
// XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
// NOLINTNEXTLINE(bugprone-branch-clone)
} else if (this->Target->GetType() == cmStateEnums::UTILITY) {
} else if (this->Target->GetType() == cmStateEnums::UTILITY ||
this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY
// XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
// NOLINTNEXTLINE(bugprone-branch-clone)
) {
kind = SourceKindExtra;
} else if (this->IsSourceFilePartOfUnityBatch(sf->ResolveFullPath())) {
kind = SourceKindUnityBatched;
......
......@@ -1093,6 +1093,7 @@ void cmGlobalNinjaGenerator::AppendTargetOutputs(
}
// FALLTHROUGH
case cmStateEnums::GLOBAL_TARGET:
case cmStateEnums::INTERFACE_LIBRARY:
case cmStateEnums::UTILITY: {
std::string path =
cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
......@@ -1105,7 +1106,6 @@ void cmGlobalNinjaGenerator::AppendTargetOutputs(
break;
}
case cmStateEnums::INTERFACE_LIBRARY:
case cmStateEnums::UNKNOWN_LIBRARY:
break;
}
......
......@@ -1204,6 +1204,7 @@ bool cmGlobalXCodeGenerator::CreateXCodeTarget(
}
if (gtgt->GetType() == cmStateEnums::UTILITY ||
gtgt->GetType() == cmStateEnums::INTERFACE_LIBRARY ||
gtgt->GetType() == cmStateEnums::GLOBAL_TARGET) {
cmXCodeObject* t = this->CreateUtilityTarget(gtgt);
if (!t) {
......@@ -2536,7 +2537,7 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateUtilityTarget(
this->XCodeObjectMap[gtgt] = target;
// Add source files without build rules for editing convenience.
if (gtgt->GetType() == cmStateEnums::UTILITY &&
if (gtgt->GetType() != cmStateEnums::GLOBAL_TARGET &&
gtgt->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET) {
std::vector<cmSourceFile*> sources;
if (!gtgt->GetConfigCommonSourceFiles(sources)) {
......
......@@ -629,9 +629,10 @@ void cmLocalVisualStudio7Generator::WriteConfiguration(
break;
case cmStateEnums::UTILITY:
case cmStateEnums::GLOBAL_TARGET:
case cmStateEnums::INTERFACE_LIBRARY:
configType = "10";
CM_FALLTHROUGH;
default:
case cmStateEnums::UNKNOWN_LIBRARY:
targetBuilds = false;
break;
}
......@@ -1638,7 +1639,8 @@ bool cmLocalVisualStudio7Generator::WriteGroup(
std::string source = sf->GetFullPath();
if (source != libName || target->GetType() == cmStateEnums::UTILITY ||
target->GetType() == cmStateEnums::GLOBAL_TARGET) {
target->GetType() == cmStateEnums::GLOBAL_TARGET ||
target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
// Look up the source kind and configs.
std::map<cmSourceFile const*, size_t>::const_iterator map_it =
sources.Index.find(sf);
......@@ -1937,6 +1939,7 @@ void cmLocalVisualStudio7Generator::WriteProjectStartFortran(
const char* keyword = p ? p->c_str() : "Console Application";
const char* projectType = 0;
switch (target->GetType()) {
case cmStateEnums::OBJECT_LIBRARY:
case cmStateEnums::STATIC_LIBRARY:
projectType = "typeStaticLibrary";
if (keyword) {
......@@ -1958,7 +1961,8 @@ void cmLocalVisualStudio7Generator::WriteProjectStartFortran(
break;
case cmStateEnums::UTILITY:
case cmStateEnums::GLOBAL_TARGET:
default:
case cmStateEnums::INTERFACE_LIBRARY:
case cmStateEnums::UNKNOWN_LIBRARY:
break;
}
if (projectType) {
......
......@@ -71,6 +71,7 @@ std::unique_ptr<cmMakefileTargetGenerator> cmMakefileTargetGenerator::New(
case cmStateEnums::OBJECT_LIBRARY:
result = cm::make_unique<cmMakefileLibraryTargetGenerator>(tgt);
break;
case cmStateEnums::INTERFACE_LIBRARY:
case cmStateEnums::UTILITY:
result = cm::make_unique<cmMakefileUtilityTargetGenerator>(tgt);
break;
......
......@@ -51,6 +51,7 @@ std::unique_ptr<cmNinjaTargetGenerator> cmNinjaTargetGenerator::New(
return cm::make_unique<cmNinjaNormalTargetGenerator>(target);
case cmStateEnums::UTILITY:
case cmStateEnums::INTERFACE_LIBRARY:
case cmStateEnums::GLOBAL_TARGET:
return cm::make_unique<cmNinjaUtilityTargetGenerator>(target);
......
......@@ -401,12 +401,10 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
#endif
}
if (this->GetType() != cmStateEnums::INTERFACE_LIBRARY) {
initProp("FOLDER");
initProp("FOLDER");
if (this->GetGlobalGenerator()->IsXcode()) {
initProp("XCODE_GENERATE_SCHEME");
}
if (this->GetGlobalGenerator()->IsXcode()) {
initProp("XCODE_GENERATE_SCHEME");
}
// Setup per-configuration property default values.
......@@ -521,24 +519,21 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
initProp("DOTNET_TARGET_FRAMEWORK_VERSION");
}
if (this->GetType() != cmStateEnums::INTERFACE_LIBRARY) {
// check for "CMAKE_VS_GLOBALS" variable and set up target properties
// if any
const char* globals = mf->GetDefinition("CMAKE_VS_GLOBALS");
if (globals) {
const std::string genName = mf->GetGlobalGenerator()->GetName();
if (cmHasLiteralPrefix(genName, "Visual Studio")) {
std::vector<std::string> props = cmExpandedList(globals);
const std::string vsGlobal = "VS_GLOBAL_";
for (const std::string& i : props) {
// split NAME=VALUE
const std::string::size_type assignment = i.find('=');
if (assignment != std::string::npos) {
const std::string propName = vsGlobal + i.substr(0, assignment);
const std::string propValue = i.substr(assignment + 1);
initPropValue(propName, propValue.c_str());
}
// check for "CMAKE_VS_GLOBALS" variable and set up target properties
// if any
const char* globals = mf->GetDefinition("CMAKE_VS_GLOBALS");
if (globals) {
const std::string genName = mf->GetGlobalGenerator()->GetName();
if (cmHasLiteralPrefix(genName, "Visual Studio")) {
std::vector<std::string> props = cmExpandedList(globals);
const std::string vsGlobal = "VS_GLOBAL_";
for (const std::string& i : props) {
// split NAME=VALUE
const std::string::size_type assignment = i.find('=');
if (assignment != std::string::npos) {
const std::string propName = vsGlobal + i.substr(0, assignment);
const std::string propValue = i.substr(assignment + 1);
initPropValue(propName, propValue.c_str());
}
}
}
......
......@@ -123,7 +123,7 @@ bool cmTargetPropCommandBase::ProcessContentArgs(
}
if (!content.empty()) {
if (this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY &&
scope != "INTERFACE") {
scope != "INTERFACE" && this->Property != "SOURCES") {
this->SetError("may only set INTERFACE properties on INTERFACE targets");
return false;
}
......
......@@ -315,8 +315,7 @@ std::ostream& cmVisualStudio10TargetGenerator::Elem::WriteString(
void cmVisualStudio10TargetGenerator::Generate()
{
// do not generate external ms projects
if (this->GeneratorTarget->GetType() == cmStateEnums::INTERFACE_LIBRARY ||
this->GeneratorTarget->GetProperty("EXTERNAL_MSPROJECT")) {
if (this->GeneratorTarget->GetProperty("EXTERNAL_MSPROJECT")) {
return;
}
const std::string ProjectFileExtension =
......@@ -437,7 +436,7 @@ void cmVisualStudio10TargetGenerator::Generate()
e1.Element("ProjectGuid", "{" + this->GUID + "}");
if ((this->MSTools || this->Android) &&
this->GeneratorTarget->GetType() <= cmStateEnums::GLOBAL_TARGET) {
this->GeneratorTarget->IsInBuildSystem()) {
this->WriteApplicationTypeSettings(e1);
this->VerifyNecessaryFiles();
}
......@@ -605,11 +604,11 @@ void cmVisualStudio10TargetGenerator::Generate()
}
break;
case cmStateEnums::UTILITY:
case cmStateEnums::INTERFACE_LIBRARY:
case cmStateEnums::GLOBAL_TARGET:
outputType = "Utility";
break;
case cmStateEnums::UNKNOWN_LIBRARY:
case cmStateEnums::INTERFACE_LIBRARY:
break;
}
e1.Element("OutputType", outputType);
......@@ -1157,6 +1156,7 @@ void cmVisualStudio10TargetGenerator::WriteProjectConfigurationValues(Elem& e0)
}
break;
case cmStateEnums::UTILITY:
case cmStateEnums::INTERFACE_LIBRARY:
case cmStateEnums::GLOBAL_TARGET:
if (this->NsightTegra) {
// Tegra-Android platform does not understand "Utility".
......@@ -1166,7 +1166,6 @@ void cmVisualStudio10TargetGenerator::WriteProjectConfigurationValues(Elem& e0)
}
break;
case cmStateEnums::UNKNOWN_LIBRARY:
case cmStateEnums::INTERFACE_LIBRARY:
break;
}
}
......@@ -2152,7 +2151,7 @@ void cmVisualStudio10TargetGenerator::WriteSource(Elem& e2,
void cmVisualStudio10TargetGenerator::WriteAllSources(Elem& e0)
{
if (this->GeneratorTarget->GetType() > cmStateEnums::UTILITY) {
if (this->GeneratorTarget->GetType() == cmStateEnums::GLOBAL_TARGET) {
return;
}
......
add_library(headeronly INTERFACE)
set(headeronly_headers headeronly/headeronly.h)
add_library(headeronly INTERFACE ${headeronly_headers})
set_property(TARGET headeronly PROPERTY INTERFACE_INCLUDE_DIRECTORIES
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/headeronly>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include/headeronly>"
)
set_property(TARGET headeronly PROPERTY INTERFACE_COMPILE_DEFINITIONS "HEADERONLY_DEFINE")
add_custom_command(OUTPUT headergen/headergen.h
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/headergen.h.in
${CMAKE_CURRENT_BINARY_DIR}/headergen/headergen.h
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/headergen.h.in
VERBATIM)
add_library(headergen INTERFACE headergen/headergen.h)
set_property(TARGET headergen PROPERTY INTERFACE_INCLUDE_DIRECTORIES
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/headergen>"
)
set_property(TARGET headergen PROPERTY PUBLIC_HEADER
${CMAKE_CURRENT_BINARY_DIR}/headergen/headergen.h)
add_library(pch_iface INTERFACE)
target_precompile_headers(pch_iface INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/pch/pch.h>"
......@@ -54,6 +69,11 @@ install(TARGETS headeronly sharediface use_auto_type use_c_restrict source_targe
pch_iface cmakeonly
EXPORT expInterface
)
install(TARGETS headergen
EXPORT expInterface
PUBLIC_HEADER DESTINATION include/headergen
INCLUDES DESTINATION include/headergen
)
install(TARGETS sharedlib
EXPORT expInterface
RUNTIME DESTINATION bin
......@@ -63,7 +83,7 @@ install(TARGETS sharedlib
BUNDLE DESTINATION Applications
)
install(FILES
headeronly/headeronly.h
${headeronly_headers}
DESTINATION include/headeronly
)
install(FILES
......
......@@ -12,6 +12,9 @@ set_property(TARGET define_iface PROPERTY
add_executable(headeronlytest_bld headeronlytest.cpp)
target_link_libraries(headeronlytest_bld bld::headeronly)
add_executable(headergentest_bld headergentest.cpp)
target_link_libraries(headergentest_bld bld::headergen)
set_property(TARGET bld::sharediface APPEND PROPERTY INTERFACE_LINK_LIBRARIES define_iface)
add_executable(interfacetest_bld interfacetest.cpp)
......@@ -93,6 +96,9 @@ target_compile_definitions(source_target_test_exp PRIVATE USE_FROM_INSTALL_DIR)
add_executable(headeronlytest_exp headeronlytest.cpp)
target_link_libraries(headeronlytest_exp exp::headeronly)
add_executable(headergentest_exp headergentest.cpp)
target_link_libraries(headergentest_exp exp::headergen)
set_property(TARGET exp::sharediface APPEND PROPERTY INTERFACE_LINK_LIBRARIES define_iface)
add_executable(interfacetest_exp interfacetest.cpp)
......
#include "headergen.h"
#ifndef HEADERGEN_H
# error Expected HEADERGEN_H
#endif
int main()
{
return 0;
}
......@@ -44,6 +44,7 @@ add_executable(InterfaceLibrary definetestexe.cpp)
target_link_libraries(InterfaceLibrary
iface_nodepends
headeriface
iface_genheader
subiface
intermediate
......
......@@ -15,6 +15,12 @@
# error Expected IFACE_HEADER_BUILDDIR
#endif
#include "iface_genheader.h"
#ifndef IFACE_GENHEADER
# error Expected IFACE_GENHEADER
#endif
extern int obj();
extern int sub();
extern int item();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment