diff options
Diffstat (limited to 'Source/cmTargetLinkLibrariesCommand.cxx')
-rw-r--r-- | Source/cmTargetLinkLibrariesCommand.cxx | 526 |
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; +} |