/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2012 Stephen Kelly Distributed under the OSI-approved BSD License (the "License"); see accompanying file Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ #include "cmMakefile.h" #include "cmGeneratorExpressionEvaluator.h" #include "cmGeneratorExpressionParser.h" #include "cmGeneratorExpressionDAGChecker.h" #include "cmGeneratorExpression.h" #include #include //---------------------------------------------------------------------------- #if !defined(__SUNPRO_CC) || __SUNPRO_CC > 0x510 static #endif void reportError(cmGeneratorExpressionContext *context, const std::string &expr, const std::string &result) { context->HadError = true; if (context->Quiet) { return; } cmOStringStream e; e << "Error evaluating generator expression:\n" << " " << expr << "\n" << result; context->Makefile->GetCMakeInstance() ->IssueMessage(cmake::FATAL_ERROR, e.str().c_str(), context->Backtrace); } //---------------------------------------------------------------------------- struct cmGeneratorExpressionNode { virtual ~cmGeneratorExpressionNode() {} virtual bool GeneratesContent() const { return true; } virtual bool RequiresLiteralInput() const { return false; } virtual bool AcceptsSingleArbitraryContentParameter() const { return false; } virtual int NumExpectedParameters() const { return 1; } virtual std::string Evaluate(const std::vector ¶meters, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *content, cmGeneratorExpressionDAGChecker *dagChecker ) const = 0; }; //---------------------------------------------------------------------------- static const struct ZeroNode : public cmGeneratorExpressionNode { ZeroNode() {} virtual bool GeneratesContent() const { return false; } virtual bool AcceptsSingleArbitraryContentParameter() const { return true; } std::string Evaluate(const std::vector &, cmGeneratorExpressionContext *, const GeneratorExpressionContent *, cmGeneratorExpressionDAGChecker *) const { // Unreachable return std::string(); } } zeroNode; //---------------------------------------------------------------------------- static const struct OneNode : public cmGeneratorExpressionNode { OneNode() {} virtual bool AcceptsSingleArbitraryContentParameter() const { return true; } std::string Evaluate(const std::vector &, cmGeneratorExpressionContext *, const GeneratorExpressionContent *, cmGeneratorExpressionDAGChecker *) const { // Unreachable return std::string(); } } oneNode; //---------------------------------------------------------------------------- static const struct OneNode buildInterfaceNode; //---------------------------------------------------------------------------- static const struct ZeroNode installInterfaceNode; //---------------------------------------------------------------------------- #define BOOLEAN_OP_NODE(OPNAME, OP, SUCCESS_VALUE, FAILURE_VALUE) \ static const struct OP ## Node : public cmGeneratorExpressionNode \ { \ OP ## Node () {} \ /* We let -1 carry the meaning 'at least one' */ \ virtual int NumExpectedParameters() const { return -1; } \ \ std::string Evaluate(const std::vector ¶meters, \ cmGeneratorExpressionContext *context, \ const GeneratorExpressionContent *content, \ cmGeneratorExpressionDAGChecker *) const \ { \ std::vector::const_iterator it = parameters.begin(); \ const std::vector::const_iterator end = parameters.end(); \ for ( ; it != end; ++it) \ { \ if (*it == #FAILURE_VALUE) \ { \ return #FAILURE_VALUE; \ } \ else if (*it != #SUCCESS_VALUE) \ { \ reportError(context, content->GetOriginalExpression(), \ "Parameters to $<" #OP "> must resolve to either '0' or '1'."); \ return std::string(); \ } \ } \ return #SUCCESS_VALUE; \ } \ } OPNAME; BOOLEAN_OP_NODE(andNode, AND, 1, 0) BOOLEAN_OP_NODE(orNode, OR, 0, 1) #undef BOOLEAN_OP_NODE //---------------------------------------------------------------------------- static const struct NotNode : public cmGeneratorExpressionNode { NotNode() {} std::string Evaluate(const std::vector ¶meters, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *content, cmGeneratorExpressionDAGChecker *) const { if (*parameters.begin() != "0" && *parameters.begin() != "1") { reportError(context, content->GetOriginalExpression(), "$ parameter must resolve to exactly one '0' or '1' value."); return std::string(); } return *parameters.begin() == "0" ? "1" : "0"; } } notNode; //---------------------------------------------------------------------------- static const struct BoolNode : public cmGeneratorExpressionNode { BoolNode() {} virtual int NumExpectedParameters() const { return 1; } std::string Evaluate(const std::vector ¶meters, cmGeneratorExpressionContext *, const GeneratorExpressionContent *, cmGeneratorExpressionDAGChecker *) const { return !cmSystemTools::IsOff(parameters.begin()->c_str()) ? "1" : "0"; } } boolNode; //---------------------------------------------------------------------------- static const struct StrEqualNode : public cmGeneratorExpressionNode { StrEqualNode() {} virtual int NumExpectedParameters() const { return 2; } std::string Evaluate(const std::vector ¶meters, cmGeneratorExpressionContext *, const GeneratorExpressionContent *, cmGeneratorExpressionDAGChecker *) const { return *parameters.begin() == parameters[1] ? "1" : "0"; } } strEqualNode; //---------------------------------------------------------------------------- static const struct Angle_RNode : public cmGeneratorExpressionNode { Angle_RNode() {} virtual int NumExpectedParameters() const { return 0; } std::string Evaluate(const std::vector &, cmGeneratorExpressionContext *, const GeneratorExpressionContent *, cmGeneratorExpressionDAGChecker *) const { return ">"; } } angle_rNode; //---------------------------------------------------------------------------- static const struct CommaNode : public cmGeneratorExpressionNode { CommaNode() {} virtual int NumExpectedParameters() const { return 0; } std::string Evaluate(const std::vector &, cmGeneratorExpressionContext *, const GeneratorExpressionContent *, cmGeneratorExpressionDAGChecker *) const { return ","; } } commaNode; //---------------------------------------------------------------------------- static const struct ConfigurationNode : public cmGeneratorExpressionNode { ConfigurationNode() {} virtual int NumExpectedParameters() const { return 0; } std::string Evaluate(const std::vector &, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *, cmGeneratorExpressionDAGChecker *) const { return context->Config ? context->Config : ""; } } configurationNode; //---------------------------------------------------------------------------- static const struct ConfigurationTestNode : public cmGeneratorExpressionNode { ConfigurationTestNode() {} virtual int NumExpectedParameters() const { return 1; } std::string Evaluate(const std::vector ¶meters, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *content, cmGeneratorExpressionDAGChecker *) const { cmsys::RegularExpression configValidator; configValidator.compile("^[A-Za-z0-9_]*$"); if (!configValidator.find(parameters.begin()->c_str())) { reportError(context, content->GetOriginalExpression(), "Expression syntax not recognized."); return std::string(); } if (!context->Config) { return parameters.front().empty() ? "1" : "0"; } if (cmsysString_strcasecmp(parameters.begin()->c_str(), context->Config) == 0) { return "1"; } if (context->CurrentTarget && context->CurrentTarget->IsImported()) { const char* loc = 0; const char* imp = 0; std::string suffix; return context->CurrentTarget->GetMappedConfig(context->Config, &loc, &imp, suffix) ? "1" : "0"; } return "0"; } } configurationTestNode; //---------------------------------------------------------------------------- static const char* targetPropertyTransitiveWhitelist[] = { "INTERFACE_INCLUDE_DIRECTORIES" , "INTERFACE_COMPILE_DEFINITIONS" }; //---------------------------------------------------------------------------- static const struct TargetPropertyNode : public cmGeneratorExpressionNode { TargetPropertyNode() {} // This node handles errors on parameter count itself. virtual int NumExpectedParameters() const { return -1; } std::string Evaluate(const std::vector ¶meters, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *content, cmGeneratorExpressionDAGChecker *dagCheckerParent ) const { if (parameters.size() != 1 && parameters.size() != 2) { reportError(context, content->GetOriginalExpression(), "$ expression requires one or two parameters"); return std::string(); } cmsys::RegularExpression targetNameValidator; // The ':' is supported to allow use with IMPORTED targets. At least // Qt 4 and 5 IMPORTED targets use ':' as the namespace delimiter. targetNameValidator.compile("^[A-Za-z0-9_.:-]+$"); cmsys::RegularExpression propertyNameValidator; propertyNameValidator.compile("^[A-Za-z0-9_]+$"); cmTarget* target = context->HeadTarget; std::string propertyName = *parameters.begin(); if (!target && parameters.size() == 1) { reportError(context, content->GetOriginalExpression(), "$ may only be used with targets. It may not " "be used with add_custom_command. Specify the target to read a " "property from using the $ signature " "instead."); return std::string(); } if (parameters.size() == 2) { if (parameters.begin()->empty() && parameters[1].empty()) { reportError(context, content->GetOriginalExpression(), "$ expression requires a non-empty " "target name and property name."); return std::string(); } if (parameters.begin()->empty()) { reportError(context, content->GetOriginalExpression(), "$ expression requires a non-empty " "target name."); return std::string(); } std::string targetName = parameters.front(); propertyName = parameters[1]; if (!targetNameValidator.find(targetName.c_str())) { if (!propertyNameValidator.find(propertyName.c_str())) { ::reportError(context, content->GetOriginalExpression(), "Target name and property name not supported."); return std::string(); } ::reportError(context, content->GetOriginalExpression(), "Target name not supported."); return std::string(); } target = context->Makefile->FindTargetToUse( targetName.c_str()); if (!target) { cmOStringStream e; e << "Target \"" << targetName << "\" not found."; reportError(context, content->GetOriginalExpression(), e.str()); return std::string(); } } if (propertyName.empty()) { reportError(context, content->GetOriginalExpression(), "$ expression requires a non-empty property " "name."); return std::string(); } if (!propertyNameValidator.find(propertyName.c_str())) { ::reportError(context, content->GetOriginalExpression(), "Property name not supported."); return std::string(); } assert(target); cmGeneratorExpressionDAGChecker dagChecker(context->Backtrace, target->GetName(), propertyName, content, dagCheckerParent); switch (dagChecker.check()) { case cmGeneratorExpressionDAGChecker::SELF_REFERENCE: dagChecker.reportError(context, content->GetOriginalExpression()); return std::string(); case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE: // No error. We just skip cyclic references. return std::string(); case cmGeneratorExpressionDAGChecker::DAG: break; } const char *prop = target->GetProperty(propertyName.c_str()); if (!prop) { return std::string(); } for (size_t i = 0; i < (sizeof(targetPropertyTransitiveWhitelist) / sizeof(*targetPropertyTransitiveWhitelist)); ++i) { if (targetPropertyTransitiveWhitelist[i] == propertyName) { cmGeneratorExpression ge(context->Backtrace); return ge.Parse(prop)->Evaluate(context->Makefile, context->Config, context->Quiet, context->HeadTarget, target, &dagChecker); } } return prop; } } targetPropertyNode; //---------------------------------------------------------------------------- static const struct TargetNameNode : public cmGeneratorExpressionNode { TargetNameNode() {} virtual bool GeneratesContent() const { return true; } virtual bool AcceptsSingleArbitraryContentParameter() const { return true; } virtual bool RequiresLiteralInput() const { return true; } std::string Evaluate(const std::vector ¶meters, cmGeneratorExpressionContext *, const GeneratorExpressionContent *, cmGeneratorExpressionDAGChecker *) const { return parameters.front(); } virtual int NumExpectedParameters() const { return 1; } } targetNameNode; //---------------------------------------------------------------------------- template struct TargetFilesystemArtifactResultCreator { static std::string Create(cmTarget* target, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *content); }; //---------------------------------------------------------------------------- template<> struct TargetFilesystemArtifactResultCreator { static std::string Create(cmTarget* target, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *content) { // The target soname file (.so.1). if(target->IsDLLPlatform()) { ::reportError(context, content->GetOriginalExpression(), "TARGET_SONAME_FILE is not allowed " "for DLL target platforms."); return std::string(); } if(target->GetType() != cmTarget::SHARED_LIBRARY) { ::reportError(context, content->GetOriginalExpression(), "TARGET_SONAME_FILE is allowed only for " "SHARED libraries."); return std::string(); } std::string result = target->GetDirectory(context->Config); result += "/"; result += target->GetSOName(context->Config); return result; } }; //---------------------------------------------------------------------------- template<> struct TargetFilesystemArtifactResultCreator { static std::string Create(cmTarget* target, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *content) { // The file used to link to the target (.so, .lib, .a). if(!target->IsLinkable()) { ::reportError(context, content->GetOriginalExpression(), "TARGET_LINKER_FILE is allowed only for libraries and " "executables with ENABLE_EXPORTS."); return std::string(); } return target->GetFullPath(context->Config, target->HasImportLibrary()); } }; //---------------------------------------------------------------------------- template<> struct TargetFilesystemArtifactResultCreator { static std::string Create(cmTarget* target, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *) { return target->GetFullPath(context->Config, false, true); } }; //---------------------------------------------------------------------------- template struct TargetFilesystemArtifactResultGetter { static std::string Get(const std::string &result); }; //---------------------------------------------------------------------------- template<> struct TargetFilesystemArtifactResultGetter { static std::string Get(const std::string &result) { return cmSystemTools::GetFilenameName(result); } }; //---------------------------------------------------------------------------- template<> struct TargetFilesystemArtifactResultGetter { static std::string Get(const std::string &result) { return cmSystemTools::GetFilenamePath(result); } }; //---------------------------------------------------------------------------- template<> struct TargetFilesystemArtifactResultGetter { static std::string Get(const std::string &result) { return result; } }; //---------------------------------------------------------------------------- template struct TargetFilesystemArtifact : public cmGeneratorExpressionNode { TargetFilesystemArtifact() {} virtual int NumExpectedParameters() const { return 1; } std::string Evaluate(const std::vector ¶meters, cmGeneratorExpressionContext *context, const GeneratorExpressionContent *content, cmGeneratorExpressionDAGChecker *) const { // Lookup the referenced target. std::string name = *parameters.begin(); cmsys::RegularExpression targetValidator; // The ':' is supported to allow use with IMPORTED targets. targetValidator.compile("^[A-Za-z0-9_.:-]+$"); if (!targetValidator.find(name.c_str())) { ::reportError(context, content->GetOriginalExpression(), "Expression syntax not recognized."); return std::string(); } cmTarget* target = context->Makefile->FindTargetToUse(name.c_str()); if(!target) { ::reportError(context, content->GetOriginalExpression(), "No target \"" + name + "\""); return std::string(); } if(target->GetType() >= cmTarget::UTILITY && target->GetType() != cmTarget::UNKNOWN_LIBRARY) { ::reportError(context, content->GetOriginalExpression(), "Target \"" + name + "\" is not an executable or library."); return std::string(); } context->Targets.insert(target); std::string result = TargetFilesystemArtifactResultCreator::Create( target, context, content); if (context->HadError) { return std::string(); } return TargetFilesystemArtifactResultGetter::Get(result); } }; //---------------------------------------------------------------------------- static const TargetFilesystemArtifact targetFileNode; static const TargetFilesystemArtifact targetLinkerFileNode; static const TargetFilesystemArtifact targetSoNameFileNode; static const TargetFilesystemArtifact targetFileNameNode; static const TargetFilesystemArtifact targetLinkerFileNameNode; static const TargetFilesystemArtifact targetSoNameFileNameNode; static const TargetFilesystemArtifact targetFileDirNode; static const TargetFilesystemArtifact targetLinkerFileDirNode; static const TargetFilesystemArtifact targetSoNameFileDirNode; //---------------------------------------------------------------------------- static const cmGeneratorExpressionNode* GetNode(const std::string &identifier) { if (identifier == "0") return &zeroNode; else if (identifier == "1") return &oneNode; else if (identifier == "AND") return &andNode; else if (identifier == "OR") return &orNode; else if (identifier == "NOT") return ¬Node; else if (identifier == "CONFIGURATION") return &configurationNode; else if (identifier == "CONFIG") return &configurationTestNode; else if (identifier == "TARGET_FILE") return &targetFileNode; else if (identifier == "TARGET_LINKER_FILE") return &targetLinkerFileNode; else if (identifier == "TARGET_SONAME_FILE") return &targetSoNameFileNode; else if (identifier == "TARGET_FILE_NAME") return &targetFileNameNode; else if (identifier == "TARGET_LINKER_FILE_NAME") return &targetLinkerFileNameNode; else if (identifier == "TARGET_SONAME_FILE_NAME") return &targetSoNameFileNameNode; else if (identifier == "TARGET_FILE_DIR") return &targetFileDirNode; else if (identifier == "TARGET_LINKER_FILE_DIR") return &targetLinkerFileDirNode; else if (identifier == "TARGET_SONAME_FILE_DIR") return &targetSoNameFileDirNode; else if (identifier == "STREQUAL") return &strEqualNode; else if (identifier == "BOOL") return &boolNode; else if (identifier == "ANGLE-R") return &angle_rNode; else if (identifier == "COMMA") return &commaNode; else if (identifier == "TARGET_PROPERTY") return &targetPropertyNode; else if (identifier == "TARGET_NAME") return &targetNameNode; else if (identifier == "BUILD_INTERFACE") return &buildInterfaceNode; else if (identifier == "INSTALL_INTERFACE") return &installInterfaceNode; return 0; } //---------------------------------------------------------------------------- GeneratorExpressionContent::GeneratorExpressionContent( const char *startContent, unsigned int length) : StartContent(startContent), ContentLength(length) { } //---------------------------------------------------------------------------- std::string GeneratorExpressionContent::GetOriginalExpression() const { return std::string(this->StartContent, this->ContentLength); } //---------------------------------------------------------------------------- std::string GeneratorExpressionContent::Evaluate( cmGeneratorExpressionContext *context, cmGeneratorExpressionDAGChecker *dagChecker) const { std::string identifier; { std::vector::const_iterator it = this->IdentifierChildren.begin(); const std::vector::const_iterator end = this->IdentifierChildren.end(); for ( ; it != end; ++it) { identifier += (*it)->Evaluate(context, dagChecker); if (context->HadError) { return std::string(); } } } const cmGeneratorExpressionNode *node = GetNode(identifier); if (!node) { reportError(context, this->GetOriginalExpression(), "Expression did not evaluate to a known generator expression"); return std::string(); } if (!node->GeneratesContent()) { if (node->AcceptsSingleArbitraryContentParameter()) { if (this->ParamChildren.empty()) { reportError(context, this->GetOriginalExpression(), "$<" + identifier + "> expression requires a parameter."); } } else { std::vector parameters; this->EvaluateParameters(node, identifier, context, dagChecker, parameters); } return std::string(); } if (node->AcceptsSingleArbitraryContentParameter()) { std::string result; std::vector >::const_iterator pit = this->ParamChildren.begin(); const std::vector >::const_iterator pend = this->ParamChildren.end(); for ( ; pit != pend; ++pit) { if (!result.empty()) { result += ","; } std::vector::const_iterator it = pit->begin(); const std::vector::const_iterator end = pit->end(); for ( ; it != end; ++it) { if (node->RequiresLiteralInput()) { if ((*it)->GetType() != cmGeneratorExpressionEvaluator::Text) { reportError(context, this->GetOriginalExpression(), "$<" + identifier + "> expression requires literal input."); return std::string(); } } result += (*it)->Evaluate(context, dagChecker); if (context->HadError) { return std::string(); } } } if (node->RequiresLiteralInput()) { std::vector parameters; parameters.push_back(result); return node->Evaluate(parameters, context, this, dagChecker); } return result; } std::vector parameters; this->EvaluateParameters(node, identifier, context, dagChecker, parameters); if (context->HadError) { return std::string(); } return node->Evaluate(parameters, context, this, dagChecker); } //---------------------------------------------------------------------------- std::string GeneratorExpressionContent::EvaluateParameters( const cmGeneratorExpressionNode *node, const std::string &identifier, cmGeneratorExpressionContext *context, cmGeneratorExpressionDAGChecker *dagChecker, std::vector ¶meters) const { { std::vector >::const_iterator pit = this->ParamChildren.begin(); const std::vector >::const_iterator pend = this->ParamChildren.end(); for ( ; pit != pend; ++pit) { std::string parameter; std::vector::const_iterator it = pit->begin(); const std::vector::const_iterator end = pit->end(); for ( ; it != end; ++it) { parameter += (*it)->Evaluate(context, dagChecker); if (context->HadError) { return std::string(); } } parameters.push_back(parameter); } } int numExpected = node->NumExpectedParameters(); if ((numExpected != -1 && (unsigned int)numExpected != parameters.size())) { if (numExpected == 0) { reportError(context, this->GetOriginalExpression(), "$<" + identifier + "> expression requires no parameters."); } else if (numExpected == 1) { reportError(context, this->GetOriginalExpression(), "$<" + identifier + "> expression requires " "exactly one parameter."); } else { cmOStringStream e; e << "$<" + identifier + "> expression requires " << numExpected << " comma separated parameters, but got " << parameters.size() << " instead."; reportError(context, this->GetOriginalExpression(), e.str()); } return std::string(); } if (numExpected == -1 && parameters.empty()) { reportError(context, this->GetOriginalExpression(), "$<" + identifier + "> expression requires at least one parameter."); } return std::string(); } //---------------------------------------------------------------------------- static void deleteAll(const std::vector &c) { std::vector::const_iterator it = c.begin(); const std::vector::const_iterator end = c.end(); for ( ; it != end; ++it) { delete *it; } } //---------------------------------------------------------------------------- GeneratorExpressionContent::~GeneratorExpressionContent() { deleteAll(this->IdentifierChildren); typedef std::vector EvaluatorVector; typedef std::vector TokenVector; std::vector::const_iterator pit = this->ParamChildren.begin(); const std::vector::const_iterator pend = this->ParamChildren.end(); for ( ; pit != pend; ++pit) { deleteAll(*pit); } }