/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmGeneratorExpressionDAGChecker.h" #include "cmAlgorithms.h" #include "cmGeneratorExpressionContext.h" #include "cmGeneratorExpressionEvaluator.h" #include "cmGeneratorTarget.h" #include "cmLocalGenerator.h" #include "cmake.h" #include #include #include cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker( const cmListFileBacktrace& backtrace, cmGeneratorTarget const* target, const std::string& property, const GeneratorExpressionContent* content, cmGeneratorExpressionDAGChecker* parent) : Parent(parent) , Target(target) , Property(property) , Content(content) , Backtrace(backtrace) , TransitivePropertiesOnly(false) { Initialize(); } cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker( cmGeneratorTarget const* target, const std::string& property, const GeneratorExpressionContent* content, cmGeneratorExpressionDAGChecker* parent) : Parent(parent) , Target(target) , Property(property) , Content(content) , Backtrace() , TransitivePropertiesOnly(false) { Initialize(); } void cmGeneratorExpressionDAGChecker::Initialize() { const cmGeneratorExpressionDAGChecker* top = this; const cmGeneratorExpressionDAGChecker* p = this->Parent; while (p) { top = p; p = p->Parent; } this->CheckResult = this->CheckGraph(); #define TEST_TRANSITIVE_PROPERTY_METHOD(METHOD) top->METHOD() || if (CheckResult == DAG && (CM_FOR_EACH_TRANSITIVE_PROPERTY_METHOD( TEST_TRANSITIVE_PROPERTY_METHOD) false)) // NOLINT(clang-tidy) #undef TEST_TRANSITIVE_PROPERTY_METHOD { std::map>::const_iterator it = top->Seen.find(this->Target); if (it != top->Seen.end()) { const std::set& propSet = it->second; if (propSet.find(this->Property) != propSet.end()) { this->CheckResult = ALREADY_SEEN; return; } } const_cast(top) ->Seen[this->Target] .insert(this->Property); } } cmGeneratorExpressionDAGChecker::Result cmGeneratorExpressionDAGChecker::Check() const { return this->CheckResult; } void cmGeneratorExpressionDAGChecker::ReportError( cmGeneratorExpressionContext* context, const std::string& expr) { if (this->CheckResult == DAG) { return; } context->HadError = true; if (context->Quiet) { return; } const cmGeneratorExpressionDAGChecker* parent = this->Parent; if (parent && !parent->Parent) { std::ostringstream e; e << "Error evaluating generator expression:\n" << " " << expr << "\n" << "Self reference on target \"" << context->HeadTarget->GetName() << "\".\n"; context->LG->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, e.str(), parent->Backtrace); return; } { std::ostringstream e; /* clang-format off */ e << "Error evaluating generator expression:\n" << " " << expr << "\n" << "Dependency loop found."; /* clang-format on */ context->LG->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, e.str(), context->Backtrace); } int loopStep = 1; while (parent) { std::ostringstream e; e << "Loop step " << loopStep << "\n" << " " << (parent->Content ? parent->Content->GetOriginalExpression() : expr) << "\n"; context->LG->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, e.str(), parent->Backtrace); parent = parent->Parent; ++loopStep; } } cmGeneratorExpressionDAGChecker::Result cmGeneratorExpressionDAGChecker::CheckGraph() const { const cmGeneratorExpressionDAGChecker* parent = this->Parent; while (parent) { if (this->Target == parent->Target && this->Property == parent->Property) { return (parent == this->Parent) ? SELF_REFERENCE : CYCLIC_REFERENCE; } parent = parent->Parent; } return DAG; } bool cmGeneratorExpressionDAGChecker::GetTransitivePropertiesOnly() { const cmGeneratorExpressionDAGChecker* top = this; const cmGeneratorExpressionDAGChecker* parent = this->Parent; while (parent) { top = parent; parent = parent->Parent; } return top->TransitivePropertiesOnly; } bool cmGeneratorExpressionDAGChecker::EvaluatingGenexExpression() { const cmGeneratorExpressionDAGChecker* top = this; const cmGeneratorExpressionDAGChecker* parent = this->Parent; while (parent) { top = parent; parent = parent->Parent; } return top->Property == "TARGET_GENEX_EVAL" || top->Property == "GENEX_EVAL"; } bool cmGeneratorExpressionDAGChecker::EvaluatingLinkLibraries( cmGeneratorTarget const* tgt) { const cmGeneratorExpressionDAGChecker* top = this; const cmGeneratorExpressionDAGChecker* parent = this->Parent; while (parent) { top = parent; parent = parent->Parent; } const char* prop = top->Property.c_str(); if (tgt) { return top->Target == tgt && strcmp(prop, "LINK_LIBRARIES") == 0; } return (strcmp(prop, "LINK_LIBRARIES") == 0 || strcmp(prop, "LINK_INTERFACE_LIBRARIES") == 0 || strcmp(prop, "IMPORTED_LINK_INTERFACE_LIBRARIES") == 0 || cmHasLiteralPrefix(prop, "LINK_INTERFACE_LIBRARIES_") || cmHasLiteralPrefix(prop, "IMPORTED_LINK_INTERFACE_LIBRARIES_")) || strcmp(prop, "INTERFACE_LINK_LIBRARIES") == 0; } cmGeneratorTarget const* cmGeneratorExpressionDAGChecker::TopTarget() const { const cmGeneratorExpressionDAGChecker* top = this; const cmGeneratorExpressionDAGChecker* parent = this->Parent; while (parent) { top = parent; parent = parent->Parent; } return top->Target; } enum TransitiveProperty { #define DEFINE_ENUM_ENTRY(NAME) NAME, CM_FOR_EACH_TRANSITIVE_PROPERTY_NAME(DEFINE_ENUM_ENTRY) #undef DEFINE_ENUM_ENTRY TransitivePropertyTerminal }; template bool additionalTest(const char* const /*unused*/) { return false; } template <> bool additionalTest(const char* const prop) { return cmHasLiteralPrefix(prop, "COMPILE_DEFINITIONS_"); } #define DEFINE_TRANSITIVE_PROPERTY_METHOD(METHOD, PROPERTY) \ bool cmGeneratorExpressionDAGChecker::METHOD() const \ { \ const char* const prop = this->Property.c_str(); \ if (strcmp(prop, #PROPERTY) == 0 || \ strcmp(prop, "INTERFACE_" #PROPERTY) == 0) { \ return true; \ } \ return additionalTest(prop); \ } CM_FOR_EACH_TRANSITIVE_PROPERTY(DEFINE_TRANSITIVE_PROPERTY_METHOD) #undef DEFINE_TRANSITIVE_PROPERTY_METHOD