| /* 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 "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) { |
| 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; |
| } |
| } |
| } |
| |
| // 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) { |
| |
| // Assure that the target on the LHS was created in the current directory. |
| cmTarget* t = |
| this->Makefile->FindLocalNonAliasTarget(this->Target->GetName()); |
| if (!t) { |
| const std::vector<cmTarget*>& importedTargets = |
| this->Makefile->GetOwnedImportedTargets(); |
| for (cmTarget* importedTarget : importedTargets) { |
| if (importedTarget->GetName() == this->Target->GetName()) { |
| t = importedTarget; |
| break; |
| } |
| } |
| } |
| if (!t) { |
| std::ostringstream e; |
| e << "Attempt to add link library \"" << lib << "\" to target \"" |
| << this->Target->GetName() |
| << "\" which is not built in this directory."; |
| 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, llt); |
| } |
| |
| // 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(lib, 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(lib, 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, lib.c_str()); |
| } |
| } |
| if (llt == OPTIMIZED_LibraryType || llt == GENERAL_LibraryType) { |
| // Put in the non-DEBUG configuration interfaces. |
| this->Target->AppendProperty("LINK_INTERFACE_LIBRARIES", lib.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; |
| } |