summaryrefslogtreecommitdiffstats
path: root/Source/cmTargetLinkLibrariesCommand.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmTargetLinkLibrariesCommand.cxx')
-rw-r--r--Source/cmTargetLinkLibrariesCommand.cxx526
1 files changed, 526 insertions, 0 deletions
diff --git a/Source/cmTargetLinkLibrariesCommand.cxx b/Source/cmTargetLinkLibrariesCommand.cxx
new file mode 100644
index 0000000..3883b52
--- /dev/null
+++ b/Source/cmTargetLinkLibrariesCommand.cxx
@@ -0,0 +1,526 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmTargetLinkLibrariesCommand.h"
+
+#include <sstream>
+#include <string.h>
+
+#include "cmGeneratorExpression.h"
+#include "cmGlobalGenerator.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmPolicies.h"
+#include "cmState.h"
+#include "cmStateTypes.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmake.h"
+
+class cmExecutionStatus;
+
+const char* cmTargetLinkLibrariesCommand::LinkLibraryTypeNames[3] = {
+ "general", "debug", "optimized"
+};
+
+// cmTargetLinkLibrariesCommand
+bool cmTargetLinkLibrariesCommand::InitialPass(
+ std::vector<std::string> const& args, cmExecutionStatus&)
+{
+ // Must have at least one argument.
+ if (args.empty()) {
+ this->SetError("called with incorrect number of arguments");
+ return false;
+ }
+ // Alias targets cannot be on the LHS of this command.
+ if (this->Makefile->IsAlias(args[0])) {
+ this->SetError("can not be used on an ALIAS target.");
+ return false;
+ }
+
+ // Lookup the target for which libraries are specified.
+ this->Target =
+ this->Makefile->GetCMakeInstance()->GetGlobalGenerator()->FindTarget(
+ args[0]);
+ if (!this->Target) {
+ const std::vector<cmTarget*>& importedTargets =
+ this->Makefile->GetOwnedImportedTargets();
+ for (cmTarget* importedTarget : importedTargets) {
+ if (importedTarget->GetName() == args[0]) {
+ this->Target = importedTarget;
+ break;
+ }
+ }
+ }
+ if (!this->Target) {
+ MessageType t = MessageType::FATAL_ERROR; // fail by default
+ std::ostringstream e;
+ e << "Cannot specify link libraries for target \"" << args[0] << "\" "
+ << "which is not built by this project.";
+ // The bad target is the only argument. Check how policy CMP0016 is set,
+ // and accept, warn or fail respectively:
+ if (args.size() < 2) {
+ switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0016)) {
+ case cmPolicies::WARN:
+ t = MessageType::AUTHOR_WARNING;
+ // Print the warning.
+ e << "\n"
+ << "CMake does not support this but it used to work accidentally "
+ << "and is being allowed for compatibility."
+ << "\n"
+ << cmPolicies::GetPolicyWarning(cmPolicies::CMP0016);
+ break;
+ case cmPolicies::OLD: // OLD behavior does not warn.
+ t = MessageType::MESSAGE;
+ break;
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::REQUIRED_ALWAYS:
+ e << "\n" << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0016);
+ break;
+ case cmPolicies::NEW: // NEW behavior prints the error.
+ break;
+ }
+ }
+ // Now actually print the message.
+ switch (t) {
+ case MessageType::AUTHOR_WARNING:
+ this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, e.str());
+ break;
+ case MessageType::FATAL_ERROR:
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ cmSystemTools::SetFatalErrorOccured();
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ // Having a UTILITY library on the LHS is a bug.
+ if (this->Target->GetType() == cmStateEnums::UTILITY) {
+ std::ostringstream e;
+ const char* modal = nullptr;
+ MessageType messageType = MessageType::AUTHOR_WARNING;
+ switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0039)) {
+ case cmPolicies::WARN:
+ e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0039) << "\n";
+ modal = "should";
+ case cmPolicies::OLD:
+ break;
+ case cmPolicies::REQUIRED_ALWAYS:
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::NEW:
+ modal = "must";
+ messageType = MessageType::FATAL_ERROR;
+ }
+ if (modal) {
+ e << "Utility target \"" << this->Target->GetName() << "\" " << modal
+ << " not be used as the target of a target_link_libraries call.";
+ this->Makefile->IssueMessage(messageType, e.str());
+ if (messageType == MessageType::FATAL_ERROR) {
+ return false;
+ }
+ }
+ }
+
+ // But we might not have any libs after variable expansion.
+ if (args.size() < 2) {
+ return true;
+ }
+
+ // Keep track of link configuration specifiers.
+ cmTargetLinkLibraryType llt = GENERAL_LibraryType;
+ bool haveLLT = false;
+
+ // Start with primary linking and switch to link interface
+ // specification if the keyword is encountered as the first argument.
+ this->CurrentProcessingState = ProcessingLinkLibraries;
+
+ // Add libraries, note that there is an optional prefix
+ // of debug and optimized that can be used.
+ for (unsigned int i = 1; i < args.size(); ++i) {
+ if (args[i] == "LINK_INTERFACE_LIBRARIES") {
+ this->CurrentProcessingState = ProcessingPlainLinkInterface;
+ if (i != 1) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "The LINK_INTERFACE_LIBRARIES option must appear as the second "
+ "argument, just after the target name.");
+ return true;
+ }
+ } else if (args[i] == "INTERFACE") {
+ if (i != 1 &&
+ this->CurrentProcessingState != ProcessingKeywordPrivateInterface &&
+ this->CurrentProcessingState != ProcessingKeywordPublicInterface &&
+ this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "The INTERFACE, PUBLIC or PRIVATE 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 != ProcessingPlainPrivateInterface &&
+ this->CurrentProcessingState != ProcessingPlainPublicInterface) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "The LINK_PUBLIC or LINK_PRIVATE option must appear as the second "
+ "argument, just after the target name.");
+ return true;
+ }
+ this->CurrentProcessingState = ProcessingPlainPublicInterface;
+ } else if (args[i] == "PUBLIC") {
+ if (i != 1 &&
+ this->CurrentProcessingState != ProcessingKeywordPrivateInterface &&
+ this->CurrentProcessingState != ProcessingKeywordPublicInterface &&
+ this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "The INTERFACE, 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 != ProcessingPlainPublicInterface &&
+ this->CurrentProcessingState != ProcessingPlainPrivateInterface) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "The LINK_PUBLIC or LINK_PRIVATE option must appear as the second "
+ "argument, just after the target name.");
+ return true;
+ }
+ this->CurrentProcessingState = ProcessingPlainPrivateInterface;
+ } else if (args[i] == "PRIVATE") {
+ if (i != 1 &&
+ this->CurrentProcessingState != ProcessingKeywordPrivateInterface &&
+ this->CurrentProcessingState != ProcessingKeywordPublicInterface &&
+ this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "The INTERFACE, 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") {
+ if (haveLLT) {
+ this->LinkLibraryTypeSpecifierWarning(llt, DEBUG_LibraryType);
+ }
+ llt = DEBUG_LibraryType;
+ haveLLT = true;
+ } else if (args[i] == "optimized") {
+ if (haveLLT) {
+ this->LinkLibraryTypeSpecifierWarning(llt, OPTIMIZED_LibraryType);
+ }
+ llt = OPTIMIZED_LibraryType;
+ haveLLT = true;
+ } else if (args[i] == "general") {
+ if (haveLLT) {
+ this->LinkLibraryTypeSpecifierWarning(llt, GENERAL_LibraryType);
+ }
+ llt = GENERAL_LibraryType;
+ haveLLT = true;
+ } else if (haveLLT) {
+ // The link type was specified by the previous argument.
+ haveLLT = false;
+ if (!this->HandleLibrary(args[i], llt)) {
+ return false;
+ }
+ } else {
+ // Lookup old-style cache entry if type is unspecified. So if you
+ // do a target_link_libraries(foo optimized bar) it will stay optimized
+ // and not use the lookup. As there may be the case where someone has
+ // specified that a library is both debug and optimized. (this check is
+ // only there for backwards compatibility when mixing projects built
+ // with old versions of CMake and new)
+ llt = GENERAL_LibraryType;
+ std::string linkType = args[0];
+ linkType += "_LINK_TYPE";
+ const char* linkTypeString = this->Makefile->GetDefinition(linkType);
+ if (linkTypeString) {
+ if (strcmp(linkTypeString, "debug") == 0) {
+ llt = DEBUG_LibraryType;
+ }
+ if (strcmp(linkTypeString, "optimized") == 0) {
+ llt = OPTIMIZED_LibraryType;
+ }
+ }
+ if (!this->HandleLibrary(args[i], llt)) {
+ return false;
+ }
+ }
+ }
+
+ // Make sure the last argument was not a library type specifier.
+ if (haveLLT) {
+ std::ostringstream e;
+ e << "The \"" << cmTargetLinkLibrariesCommand::LinkLibraryTypeNames[llt]
+ << "\" argument must be followed by a library.";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ cmSystemTools::SetFatalErrorOccured();
+ }
+
+ const cmPolicies::PolicyStatus policy22Status =
+ this->Target->GetPolicyStatusCMP0022();
+
+ // If any of the LINK_ options were given, make sure the
+ // LINK_INTERFACE_LIBRARIES target property exists.
+ // Use of any of the new keywords implies awareness of
+ // this property. And if no libraries are named, it should
+ // result in an empty link interface.
+ if ((policy22Status == cmPolicies::OLD ||
+ policy22Status == cmPolicies::WARN) &&
+ this->CurrentProcessingState != ProcessingLinkLibraries &&
+ !this->Target->GetProperty("LINK_INTERFACE_LIBRARIES")) {
+ this->Target->SetProperty("LINK_INTERFACE_LIBRARIES", "");
+ }
+
+ return true;
+}
+
+void cmTargetLinkLibrariesCommand::LinkLibraryTypeSpecifierWarning(int left,
+ int right)
+{
+ std::ostringstream w;
+ w << "Link library type specifier \""
+ << cmTargetLinkLibrariesCommand::LinkLibraryTypeNames[left]
+ << "\" is followed by specifier \""
+ << cmTargetLinkLibrariesCommand::LinkLibraryTypeNames[right]
+ << "\" instead of a library name. "
+ << "The first specifier will be ignored.";
+ this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, w.str());
+}
+
+bool cmTargetLinkLibrariesCommand::HandleLibrary(const std::string& lib,
+ cmTargetLinkLibraryType llt)
+{
+ if (this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY &&
+ this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "INTERFACE library can only be used with the INTERFACE keyword of "
+ "target_link_libraries");
+ return false;
+ }
+ if (this->Target->IsImported() &&
+ this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "IMPORTED library can only be used with the INTERFACE keyword of "
+ "target_link_libraries");
+ return false;
+ }
+
+ 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, this->Makefile->GetExecutionContext())) {
+ std::ostringstream e;
+ const char* modal = nullptr;
+ MessageType messageType = MessageType::AUTHOR_WARNING;
+ switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0023)) {
+ case cmPolicies::WARN:
+ e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0023) << "\n";
+ modal = "should";
+ case cmPolicies::OLD:
+ break;
+ case cmPolicies::REQUIRED_ALWAYS:
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::NEW:
+ modal = "must";
+ messageType = MessageType::FATAL_ERROR;
+ }
+
+ if (modal) {
+ // 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 << "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());
+ if (messageType == MessageType::FATAL_ERROR) {
+ return false;
+ }
+ }
+ }
+
+ bool warnRemoteInterface = false;
+ bool rejectRemoteLinking = false;
+ bool encodeRemoteReference = false;
+ if (this->Makefile != this->Target->GetMakefile()) {
+ // The LHS target was created in another directory.
+ switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0079)) {
+ case cmPolicies::WARN:
+ warnRemoteInterface = true;
+ CM_FALLTHROUGH;
+ case cmPolicies::OLD:
+ rejectRemoteLinking = true;
+ break;
+ case cmPolicies::REQUIRED_ALWAYS:
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::NEW:
+ encodeRemoteReference = true;
+ break;
+ }
+ }
+
+ std::string libRef;
+ if (encodeRemoteReference && !cmSystemTools::FileIsFullPath(lib)) {
+ // This is a library name added by a caller that is not in the
+ // same directory as the target was created. Add a suffix to
+ // the name to tell ResolveLinkItem to look up the name in the
+ // caller's directory.
+ cmDirectoryId const dirId = this->Makefile->GetDirectoryId();
+ libRef = lib + CMAKE_DIRECTORY_ID_SEP + dirId.String;
+ } else {
+ // This is an absolute path or a library name added by a caller
+ // in the same directory as the target was created. We can use
+ // the original name directly.
+ libRef = lib;
+ }
+
+ // Handle normal case where the command was called with another keyword than
+ // INTERFACE / LINK_INTERFACE_LIBRARIES or none at all. (The "LINK_LIBRARIES"
+ // property of the target on the LHS shall be populated.)
+ if (this->CurrentProcessingState != ProcessingKeywordLinkInterface &&
+ this->CurrentProcessingState != ProcessingPlainLinkInterface) {
+
+ if (rejectRemoteLinking) {
+ std::ostringstream e;
+ e << "Attempt to add link library \"" << lib << "\" to target \""
+ << this->Target->GetName()
+ << "\" which is not built in this directory.\n"
+ << "This is allowed only when policy CMP0079 is set to NEW.";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ return false;
+ }
+
+ cmTarget* tgt = this->Makefile->GetGlobalGenerator()->FindTarget(lib);
+
+ if (tgt && (tgt->GetType() != cmStateEnums::STATIC_LIBRARY) &&
+ (tgt->GetType() != cmStateEnums::SHARED_LIBRARY) &&
+ (tgt->GetType() != cmStateEnums::UNKNOWN_LIBRARY) &&
+ (tgt->GetType() != cmStateEnums::OBJECT_LIBRARY) &&
+ (tgt->GetType() != cmStateEnums::INTERFACE_LIBRARY) &&
+ !tgt->IsExecutableWithExports()) {
+ std::ostringstream e;
+ e << "Target \"" << lib << "\" of type "
+ << cmState::GetTargetTypeName(tgt->GetType())
+ << " may not be linked into another target. One may link only to "
+ "INTERFACE, OBJECT, STATIC or SHARED libraries, or to executables "
+ "with the ENABLE_EXPORTS property set.";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ }
+
+ this->Target->AddLinkLibrary(*this->Makefile, lib, libRef, llt);
+ }
+
+ if (warnRemoteInterface) {
+ std::ostringstream w;
+ /* clang-format off */
+ w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0079) << "\n"
+ "Target\n " << this->Target->GetName() << "\nis not created in this "
+ "directory. For compatibility with older versions of CMake, link "
+ "library\n " << lib << "\nwill be looked up in the directory in "
+ "which the target was created rather than in this calling "
+ "directory.";
+ /* clang-format on */
+ this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, w.str());
+ }
+
+ // Handle (additional) case where the command was called with PRIVATE /
+ // LINK_PRIVATE and stop its processing. (The "INTERFACE_LINK_LIBRARIES"
+ // property of the target on the LHS shall only be populated if it is a
+ // STATIC library.)
+ if (this->CurrentProcessingState == ProcessingKeywordPrivateInterface ||
+ this->CurrentProcessingState == ProcessingPlainPrivateInterface) {
+ if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
+ this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
+ std::string configLib =
+ this->Target->GetDebugGeneratorExpressions(libRef, llt);
+ if (cmGeneratorExpression::IsValidTargetName(lib) ||
+ cmGeneratorExpression::Find(lib) != std::string::npos) {
+ configLib = "$<LINK_ONLY:" + configLib + ">";
+ }
+ this->Target->AppendProperty("INTERFACE_LINK_LIBRARIES",
+ configLib.c_str());
+ }
+ return true;
+ }
+
+ // Handle general case where the command was called with another keyword than
+ // PRIVATE / LINK_PRIVATE or none at all. (The "INTERFACE_LINK_LIBRARIES"
+ // property of the target on the LHS shall be populated.)
+ this->Target->AppendProperty(
+ "INTERFACE_LINK_LIBRARIES",
+ this->Target->GetDebugGeneratorExpressions(libRef, llt).c_str());
+
+ // Stop processing if called without any keyword.
+ if (this->CurrentProcessingState == ProcessingLinkLibraries) {
+ return true;
+ }
+ // Stop processing if policy CMP0022 is set to NEW.
+ const cmPolicies::PolicyStatus policy22Status =
+ this->Target->GetPolicyStatusCMP0022();
+ if (policy22Status != cmPolicies::OLD &&
+ policy22Status != cmPolicies::WARN) {
+ return true;
+ }
+ // Stop processing if called with an INTERFACE library on the LHS.
+ if (this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+ return true;
+ }
+
+ // Handle (additional) backward-compatibility case where the command was
+ // called with PUBLIC / INTERFACE / LINK_PUBLIC / LINK_INTERFACE_LIBRARIES.
+ // (The policy CMP0022 is not set to NEW.)
+ {
+ // Get the list of configurations considered to be DEBUG.
+ std::vector<std::string> debugConfigs =
+ this->Makefile->GetCMakeInstance()->GetDebugConfigs();
+ std::string prop;
+
+ // Include this library in the link interface for the target.
+ if (llt == DEBUG_LibraryType || llt == GENERAL_LibraryType) {
+ // Put in the DEBUG configuration interfaces.
+ for (std::string const& dc : debugConfigs) {
+ prop = "LINK_INTERFACE_LIBRARIES_";
+ prop += dc;
+ this->Target->AppendProperty(prop, libRef.c_str());
+ }
+ }
+ if (llt == OPTIMIZED_LibraryType || llt == GENERAL_LibraryType) {
+ // Put in the non-DEBUG configuration interfaces.
+ this->Target->AppendProperty("LINK_INTERFACE_LIBRARIES", libRef.c_str());
+
+ // Make sure the DEBUG configuration interfaces exist so that the
+ // general one will not be used as a fall-back.
+ for (std::string const& dc : debugConfigs) {
+ prop = "LINK_INTERFACE_LIBRARIES_";
+ prop += dc;
+ if (!this->Target->GetProperty(prop)) {
+ this->Target->SetProperty(prop, "");
+ }
+ }
+ }
+ }
+ return true;
+}