/* 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 #include #include "cmGeneratorExpression.h" #include "cmGlobalGenerator.h" #include "cmMakefile.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 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& importedTargets = this->Makefile->GetOwnedImportedTargets(); for (cmTarget* importedTarget : importedTargets) { if (importedTarget->GetName() == args[0]) { this->Target = importedTarget; break; } } } if (!this->Target) { cmake::MessageType t = cmake::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 = cmake::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 = cmake::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 cmake::AUTHOR_WARNING: this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, e.str()); break; case cmake::FATAL_ERROR: this->Makefile->IssueMessage(cmake::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; cmake::MessageType messageType = cmake::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 = cmake::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 == cmake::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( cmake::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( cmake::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( cmake::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( cmake::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( cmake::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( cmake::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 \"" << this->LinkLibraryTypeNames[llt] << "\" argument must be followed by a library."; this->Makefile->IssueMessage(cmake::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 \"" << this->LinkLibraryTypeNames[left] << "\" is followed by specifier \"" << this->LinkLibraryTypeNames[right] << "\" instead of a library name. " << "The first specifier will be ignored."; this->Makefile->IssueMessage(cmake::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( cmake::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( cmake::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; cmake::MessageType messageType = cmake::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 = cmake::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 == cmake::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(cmake::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(cmake::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(cmake::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) { std::string configLib = this->Target->GetDebugGeneratorExpressions(libRef, llt); if (cmGeneratorExpression::IsValidTargetName(lib) || cmGeneratorExpression::Find(lib) != std::string::npos) { 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 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; }