target_link_libraries: Add PUBLIC/PRIVATE/INTERFACE keyword signature

Add a new signature to help populate INTERFACE_LINK_LIBRARIES and
LINK_LIBRARIES cleanly in a single call.  Add policy CMP0023 to control
whether the keyword signatures can be mixed with uses of the plain
signatures on the same target.
This commit is contained in:
Stephen Kelly 2013-06-04 16:21:33 +02:00 committed by Brad King
parent 828ddb6813
commit b655865bbf
30 changed files with 407 additions and 19 deletions

View File

@ -575,6 +575,32 @@ cmPolicies::cmPolicies()
"property for in-build targets, and ignore the old properties matching "
"(IMPORTED_)?LINK_INTERFACE_LIBRARIES(_<CONFIG>)?.",
2,8,11,20130516, cmPolicies::WARN);
this->DefinePolicy(
CMP0023, "CMP0023",
"Plain and keyword target_link_libraries signatures cannot be mixed.",
"CMake 2.8.12 introduced the target_link_libraries signature using "
"the PUBLIC, PRIVATE, and INTERFACE keywords to generalize the "
"LINK_PUBLIC and LINK_PRIVATE keywords introduced in CMake 2.8.7. "
"Use of signatures with any of these keywords sets the link interface "
"of a target explicitly, even if empty. "
"This produces confusing behavior when used in combination with the "
"historical behavior of the plain target_link_libraries signature. "
"For example, consider the code:\n"
" target_link_libraries(mylib A)\n"
" target_link_libraries(mylib PRIVATE B)\n"
"After the first line the link interface has not been set explicitly "
"so CMake would use the link implementation, A, as the link interface. "
"However, the second line sets the link interface to empty. "
"In order to avoid this subtle behavior CMake now prefers to disallow "
"mixing the plain and keyword signatures of target_link_libraries for "
"a single target."
"\n"
"The OLD behavior for this policy is to allow keyword and plain "
"target_link_libraries signatures to be mixed. "
"The NEW behavior for this policy is to not to allow mixing of the "
"keyword and plain signatures.",
2,8,11,20130724, cmPolicies::WARN);
}
cmPolicies::~cmPolicies()

View File

@ -73,6 +73,7 @@ public:
CMP0021, ///< Fatal error on relative paths in INCLUDE_DIRECTORIES
/// target property
CMP0022, ///< INTERFACE_LINK_LIBRARIES defines the link interface
CMP0023, ///< Disallow mixing keyword and plain tll signatures
/** \brief Always the last entry.
*

View File

@ -2493,6 +2493,57 @@ static std::string targetNameGenex(const char *lib)
return std::string("$<TARGET_NAME:") + lib + ">";
}
//----------------------------------------------------------------------------
bool cmTarget::PushTLLCommandTrace(TLLSignature signature)
{
bool ret = true;
if (!this->TLLCommands.empty())
{
if (this->TLLCommands.back().first != signature)
{
ret = false;
}
}
cmListFileBacktrace lfbt;
this->Makefile->GetBacktrace(lfbt);
this->TLLCommands.push_back(std::make_pair(signature, lfbt));
return ret;
}
//----------------------------------------------------------------------------
void cmTarget::GetTllSignatureTraces(cmOStringStream &s,
TLLSignature sig) const
{
std::vector<cmListFileBacktrace> sigs;
typedef std::vector<std::pair<TLLSignature, cmListFileBacktrace> > Container;
for(Container::const_iterator it = this->TLLCommands.begin();
it != this->TLLCommands.end(); ++it)
{
if (it->first == sig)
{
sigs.push_back(it->second);
}
}
if (!sigs.empty())
{
const char *sigString
= (sig == cmTarget::KeywordTLLSignature ? "keyword"
: "plain");
s << "The uses of the " << sigString << " signature are here:\n";
for(std::vector<cmListFileBacktrace>::const_iterator it = sigs.begin();
it != sigs.end(); ++it)
{
cmListFileBacktrace::const_iterator i = it->begin();
if(i != it->end())
{
cmListFileContext const& lfc = *i;
s << " * " << (lfc.Line? "": " in ") << lfc << std::endl;
++i;
}
}
}
}
//----------------------------------------------------------------------------
void cmTarget::AddLinkLibrary(cmMakefile& mf,
const char *target, const char* lib,

View File

@ -190,6 +190,12 @@ public:
void AddLinkLibrary(cmMakefile& mf,
const char *target, const char* lib,
LinkLibraryType llt);
enum TLLSignature {
KeywordTLLSignature,
PlainTLLSignature
};
bool PushTLLCommandTrace(TLLSignature signature);
void GetTllSignatureTraces(cmOStringStream &s, TLLSignature sig) const;
void MergeLinkLibraries( cmMakefile& mf, const char* selfname,
const LinkLibraryVectorType& libs );
@ -548,6 +554,8 @@ private:
// directories.
std::set<cmStdString> SystemIncludeDirectories;
std::vector<std::pair<TLLSignature, cmListFileBacktrace> > TLLCommands;
/**
* A list of direct dependencies. Use in conjunction with DependencyMap.
*/

View File

@ -116,7 +116,7 @@ bool cmTargetLinkLibrariesCommand
{
if(args[i] == "LINK_INTERFACE_LIBRARIES")
{
this->CurrentProcessingState = ProcessingLinkInterface;
this->CurrentProcessingState = ProcessingPlainLinkInterface;
if(i != 1)
{
this->Makefile->IssueMessage(
@ -127,9 +127,26 @@ bool cmTargetLinkLibrariesCommand
return true;
}
}
else if(args[i] == "INTERFACE")
{
if(i != 1
&& this->CurrentProcessingState != ProcessingKeywordPrivateInterface
&& this->CurrentProcessingState != ProcessingKeywordPublicInterface
&& this->CurrentProcessingState != ProcessingKeywordLinkInterface)
{
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
"The INTERFACE option must appear as the second "
"argument, just after the target name."
);
return true;
}
this->CurrentProcessingState = ProcessingKeywordLinkInterface;
}
else if(args[i] == "LINK_PUBLIC")
{
if(i != 1 && this->CurrentProcessingState != ProcessingPrivateInterface)
if(i != 1
&& this->CurrentProcessingState != ProcessingPlainPrivateInterface)
{
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
@ -138,11 +155,28 @@ bool cmTargetLinkLibrariesCommand
);
return true;
}
this->CurrentProcessingState = ProcessingPublicInterface;
this->CurrentProcessingState = ProcessingPlainPublicInterface;
}
else if(args[i] == "PUBLIC")
{
if(i != 1
&& this->CurrentProcessingState != ProcessingKeywordPrivateInterface
&& this->CurrentProcessingState != ProcessingKeywordPublicInterface
&& this->CurrentProcessingState != ProcessingKeywordLinkInterface)
{
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
"The PUBLIC or PRIVATE option must appear as the second "
"argument, just after the target name."
);
return true;
}
this->CurrentProcessingState = ProcessingKeywordPublicInterface;
}
else if(args[i] == "LINK_PRIVATE")
{
if(i != 1 && this->CurrentProcessingState != ProcessingPublicInterface)
if(i != 1
&& this->CurrentProcessingState != ProcessingPlainPublicInterface)
{
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
@ -151,7 +185,23 @@ bool cmTargetLinkLibrariesCommand
);
return true;
}
this->CurrentProcessingState = ProcessingPrivateInterface;
this->CurrentProcessingState = ProcessingPlainPrivateInterface;
}
else if(args[i] == "PRIVATE")
{
if(i != 1
&& this->CurrentProcessingState != ProcessingKeywordPrivateInterface
&& this->CurrentProcessingState != ProcessingKeywordPublicInterface
&& this->CurrentProcessingState != ProcessingKeywordLinkInterface)
{
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
"The PUBLIC or PRIVATE option must appear as the second "
"argument, just after the target name."
);
return true;
}
this->CurrentProcessingState = ProcessingKeywordPrivateInterface;
}
else if(args[i] == "debug")
{
@ -184,7 +234,10 @@ bool cmTargetLinkLibrariesCommand
{
// The link type was specified by the previous argument.
haveLLT = false;
this->HandleLibrary(args[i].c_str(), llt);
if (!this->HandleLibrary(args[i].c_str(), llt))
{
return false;
}
}
else
{
@ -210,7 +263,10 @@ bool cmTargetLinkLibrariesCommand
llt = cmTarget::OPTIMIZED;
}
}
this->HandleLibrary(args[i].c_str(), llt);
if (!this->HandleLibrary(args[i].c_str(), llt))
{
return false;
}
}
}
@ -257,16 +313,69 @@ cmTargetLinkLibrariesCommand
}
//----------------------------------------------------------------------------
void
bool
cmTargetLinkLibrariesCommand::HandleLibrary(const char* lib,
cmTarget::LinkLibraryType llt)
{
cmTarget::TLLSignature sig =
(this->CurrentProcessingState == ProcessingPlainPrivateInterface
|| this->CurrentProcessingState == ProcessingPlainPublicInterface
|| this->CurrentProcessingState == ProcessingKeywordPrivateInterface
|| this->CurrentProcessingState == ProcessingKeywordPublicInterface
|| this->CurrentProcessingState == ProcessingKeywordLinkInterface)
? cmTarget::KeywordTLLSignature : cmTarget::PlainTLLSignature;
if (!this->Target->PushTLLCommandTrace(sig))
{
const char *modal = 0;
cmake::MessageType messageType = cmake::AUTHOR_WARNING;
switch(this->Makefile->GetPolicyStatus(cmPolicies::CMP0023))
{
case cmPolicies::WARN:
modal = "should";
case cmPolicies::OLD:
break;
case cmPolicies::REQUIRED_ALWAYS:
case cmPolicies::REQUIRED_IF_USED:
case cmPolicies::NEW:
modal = "must";
messageType = cmake::FATAL_ERROR;
}
if(modal)
{
cmOStringStream e;
// If the sig is a keyword form and there is a conflict, the existing
// form must be the plain form.
const char *existingSig
= (sig == cmTarget::KeywordTLLSignature ? "plain"
: "keyword");
e << this->Makefile->GetPolicies()
->GetPolicyWarning(cmPolicies::CMP0023) << "\n"
"The " << existingSig << " signature for target_link_libraries "
"has already been used with the target \""
<< this->Target->GetName() << "\". All uses of "
"target_link_libraries with a target " << modal << " be either "
"all-keyword or all-plain.\n";
this->Target->GetTllSignatureTraces(e,
sig == cmTarget::KeywordTLLSignature
? cmTarget::PlainTLLSignature
: cmTarget::KeywordTLLSignature);
this->Makefile->IssueMessage(messageType, e.str().c_str());
if(messageType == cmake::FATAL_ERROR)
{
return false;
}
}
}
// Handle normal case first.
if(this->CurrentProcessingState != ProcessingLinkInterface)
if(this->CurrentProcessingState != ProcessingKeywordLinkInterface
&& this->CurrentProcessingState != ProcessingPlainLinkInterface)
{
this->Makefile
->AddLinkLibraryForTarget(this->Target->GetName(), lib, llt);
if (this->CurrentProcessingState != ProcessingPublicInterface)
if (this->CurrentProcessingState != ProcessingKeywordPublicInterface
&& this->CurrentProcessingState != ProcessingPlainPublicInterface)
{
if (this->Target->GetType() == cmTarget::STATIC_LIBRARY)
{
@ -275,8 +384,9 @@ cmTargetLinkLibrariesCommand::HandleLibrary(const char* lib,
this->Target->GetDebugGeneratorExpressions(lib, llt) +
">").c_str());
}
// Not LINK_INTERFACE_LIBRARIES or LINK_PUBLIC, do not add to interface.
return;
// Not a 'public' or 'interface' library. Do not add to interface
// property.
return true;
}
}
@ -289,7 +399,7 @@ cmTargetLinkLibrariesCommand::HandleLibrary(const char* lib,
if (policy22Status != cmPolicies::OLD
&& policy22Status != cmPolicies::WARN)
{
return;
return true;
}
// Get the list of configurations considered to be DEBUG.
@ -327,4 +437,5 @@ cmTargetLinkLibrariesCommand::HandleLibrary(const char* lib,
}
}
}
return true;
}

View File

@ -113,6 +113,16 @@ public:
"directory of the framework will also be processed as a \"usage "
"requirement\". This has the same effect as passing the framework "
"directory as an include directory."
" target_link_libraries(<target>\n"
" <PRIVATE|PUBLIC|INTERFACE> <lib> ...\n"
" [<PRIVATE|PUBLIC|INTERFACE> <lib> ... ] ...])\n"
"The PUBLIC, PRIVATE and INTERFACE keywords can be used to specify "
"both the link dependencies and the link interface in one command. "
"Libraries and targets following PUBLIC are linked to, and are "
"made part of the link interface. Libraries and targets "
"following PRIVATE are linked to, but are not made part of the "
"link interface. Libraries following INTERFACE are appended "
"to the link interface and are not used for linking <target>."
"\n"
" target_link_libraries(<target> LINK_INTERFACE_LIBRARIES\n"
" [[debug|optimized|general] <lib>] ...)\n"
@ -120,7 +130,8 @@ public:
"to the INTERFACE_LINK_LIBRARIES target property instead of using them "
"for linking. If policy CMP0022 is not NEW, then this mode also "
"appends libraries to the LINK_INTERFACE_LIBRARIES and its "
"per-configuration equivalent. "
"per-configuration equivalent. This signature "
"is for compatibility only. Prefer the INTERFACE mode instead. "
"Libraries specified as \"debug\" are wrapped in a generator "
"expression to correspond to debug builds. If policy CMP0022 is not "
"NEW, the libraries are also appended to the "
@ -139,7 +150,9 @@ public:
" [<LINK_PRIVATE|LINK_PUBLIC>\n"
" [[debug|optimized|general] <lib>] ...])\n"
"The LINK_PUBLIC and LINK_PRIVATE modes can be used to specify both "
"the link dependencies and the link interface in one command. "
"the link dependencies and the link interface in one command. This "
"signature is for compatibility only. Prefer the PUBLIC or PRIVATE "
"keywords instead. "
"Libraries and targets following LINK_PUBLIC are linked to, and are "
"made part of the INTERFACE_LINK_LIBRARIES. If policy CMP0022 is not "
"NEW, they are also made part of the LINK_INTERFACE_LIBRARIES. "
@ -185,14 +198,17 @@ private:
cmTarget* Target;
enum ProcessingState {
ProcessingLinkLibraries,
ProcessingLinkInterface,
ProcessingPublicInterface,
ProcessingPrivateInterface
ProcessingPlainLinkInterface,
ProcessingKeywordLinkInterface,
ProcessingPlainPublicInterface,
ProcessingKeywordPublicInterface,
ProcessingPlainPrivateInterface,
ProcessingKeywordPrivateInterface
};
ProcessingState CurrentProcessingState;
void HandleLibrary(const char* lib, cmTarget::LinkLibraryType llt);
bool HandleLibrary(const char* lib, cmTarget::LinkLibraryType llt);
};

View File

@ -124,3 +124,9 @@ add_library(libConsumer empty.cpp)
target_link_libraries(libConsumer debug depA)
add_subdirectory(cmp0022)
add_executable(newsignature1 newsignature1.cpp)
target_link_libraries(newsignature1 PRIVATE depC INTERFACE depD PUBLIC depB PRIVATE subdirlib INTERFACE INTERFACE PUBLIC)
assert_property(newsignature1 INTERFACE_LINK_LIBRARIES "depD;depB")
assert_property(newsignature1 LINK_LIBRARIES "depC;depB;subdirlib")

View File

@ -0,0 +1,19 @@
#include "depB.h"
#include "depC.h"
#include "depIfaceOnly.h"
#include "subdirlib.h"
int main(int, char **)
{
DepA a;
DepB b;
DepC c;
DepIfaceOnly iface_only;
SubDirLibObject sd;
return a.foo() + b.foo() + c.foo() + iface_only.foo() + sd.foo();
}

View File

@ -115,3 +115,4 @@ if("${CMAKE_TEST_GENERATOR}" MATCHES "Visual Studio [^6]")
endif()
add_RunCMake_test(File_Generate)
add_RunCMake_test(target_link_libraries)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,16 @@
CMake Error at CMP0023-NEW-2.cmake:11 \(target_link_libraries\):
Policy CMP0023 is not set: Plain and keyword target_link_libraries
signatures cannot be mixed. Run "cmake --help-policy CMP0023" for policy
details. Use the cmake_policy command to set the policy and suppress this
warning.
The plain signature for target_link_libraries has already been used with
the target "foo". All uses of target_link_libraries with a target must be
either all-keyword or all-plain.
The uses of the plain signature are here:
\* CMP0023-NEW-2.cmake:10 \(target_link_libraries\)
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,11 @@
project(CMP0022-WARN)
cmake_policy(SET CMP0023 NEW)
add_library(foo SHARED empty_vs6_1.cpp)
add_library(bar SHARED empty_vs6_2.cpp)
add_library(bat SHARED empty_vs6_3.cpp)
target_link_libraries(foo bar)
target_link_libraries(foo LINK_PRIVATE bat)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,16 @@
CMake Error at CMP0023-NEW.cmake:11 \(target_link_libraries\):
Policy CMP0023 is not set: Plain and keyword target_link_libraries
signatures cannot be mixed. Run "cmake --help-policy CMP0023" for policy
details. Use the cmake_policy command to set the policy and suppress this
warning.
The plain signature for target_link_libraries has already been used with
the target "foo". All uses of target_link_libraries with a target must be
either all-keyword or all-plain.
The uses of the plain signature are here:
\* CMP0023-NEW.cmake:10 \(target_link_libraries\)
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,11 @@
project(CMP0022-WARN)
cmake_policy(SET CMP0023 NEW)
add_library(foo SHARED empty_vs6_1.cpp)
add_library(bar SHARED empty_vs6_2.cpp)
add_library(bat SHARED empty_vs6_3.cpp)
target_link_libraries(foo bar)
target_link_libraries(foo PRIVATE bat)

View File

@ -0,0 +1,16 @@
CMake Warning \(dev\) at CMP0023-WARN-2.cmake:9 \(target_link_libraries\):
Policy CMP0023 is not set: Plain and keyword target_link_libraries
signatures cannot be mixed. Run "cmake --help-policy CMP0023" for policy
details. Use the cmake_policy command to set the policy and suppress this
warning.
The plain signature for target_link_libraries has already been used with
the target "foo". All uses of target_link_libraries with a target should
be either all-keyword or all-plain.
The uses of the plain signature are here:
\* CMP0023-WARN-2.cmake:8 \(target_link_libraries\)
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,9 @@
project(CMP0022-WARN)
add_library(foo SHARED empty_vs6_1.cpp)
add_library(bar SHARED empty_vs6_2.cpp)
add_library(bat SHARED empty_vs6_3.cpp)
target_link_libraries(foo bar)
target_link_libraries(foo LINK_PRIVATE bat)

View File

@ -0,0 +1,16 @@
CMake Warning \(dev\) at CMP0023-WARN.cmake:9 \(target_link_libraries\):
Policy CMP0023 is not set: Plain and keyword target_link_libraries
signatures cannot be mixed. Run "cmake --help-policy CMP0023" for policy
details. Use the cmake_policy command to set the policy and suppress this
warning.
The plain signature for target_link_libraries has already been used with
the target "foo". All uses of target_link_libraries with a target should
be either all-keyword or all-plain.
The uses of the plain signature are here:
\* CMP0023-WARN.cmake:8 \(target_link_libraries\)
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,9 @@
project(CMP0022-WARN)
add_library(foo SHARED empty_vs6_1.cpp)
add_library(bar SHARED empty_vs6_2.cpp)
add_library(bat SHARED empty_vs6_3.cpp)
target_link_libraries(foo bar)
target_link_libraries(foo PRIVATE bat)

View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 2.8)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,5 @@
CMake Error at MixedSignature.cmake:6 \(target_link_libraries\):
The PUBLIC or PRIVATE option must appear as the second argument, just after
the target name.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,6 @@
add_library(foo empty_vs6_1.cpp)
add_library(bar empty_vs6_2.cpp)
add_library(bat empty_vs6_3.cpp)
target_link_libraries(foo LINK_PUBLIC bar PRIVATE bat)

View File

@ -0,0 +1,8 @@
include(RunCMake)
run_cmake(CMP0023-WARN)
run_cmake(CMP0023-NEW)
run_cmake(CMP0023-WARN-2)
run_cmake(CMP0023-NEW-2)
run_cmake(MixedSignature)
run_cmake(Separate-PRIVATE-LINK_PRIVATE-uses)

View File

@ -0,0 +1,9 @@
enable_language(CXX)
add_library(foo empty_vs6_1.cpp)
add_library(bar empty_vs6_2.cpp)
add_library(bat empty_vs6_3.cpp)
target_link_libraries(foo LINK_PRIVATE bar)
target_link_libraries(foo PRIVATE bat)

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
{
return 0;
}

View File

@ -0,0 +1 @@
#include "empty.cpp"

View File

@ -0,0 +1 @@
#include "empty.cpp"

View File

@ -0,0 +1 @@
#include "empty.cpp"