/*========================================================================= Program: CMake - Cross-Platform Makefile Generator Module: $RCSfile$ Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "cmIfCommand.h" #include "cmStringCommand.h" #include // required for atof #include #include //========================================================================= bool cmIfFunctionBlocker:: IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile &mf, cmExecutionStatus &inStatus) { // Prevent recusion and don't let this blocker block its own // commands. if (this->Executing) { return false; } // we start by recording all the functions if (!cmSystemTools::Strucmp(lff.Name.c_str(),"if")) { this->ScopeDepth++; } if (!cmSystemTools::Strucmp(lff.Name.c_str(),"endif")) { this->ScopeDepth--; // if this is the endif for this if statement, then start executing if (!this->ScopeDepth) { // execute the functions for the true parts of the if statement this->Executing = true; cmExecutionStatus status; int scopeDepth = 0; for(unsigned int c = 0; c < this->Functions.size(); ++c) { // keep track of scope depth if (!cmSystemTools::Strucmp(this->Functions[c].Name.c_str(),"if")) { scopeDepth++; } if (!cmSystemTools::Strucmp(this->Functions[c].Name.c_str(),"endif")) { scopeDepth--; } // watch for our state change if (scopeDepth == 0 && !cmSystemTools::Strucmp(this->Functions[c].Name.c_str(),"else")) { this->IsBlocking = this->HasRun; this->HasRun = true; } else if (scopeDepth == 0 && !cmSystemTools::Strucmp (this->Functions[c].Name.c_str(),"elseif")) { if (this->HasRun) { this->IsBlocking = true; } else { std::string errorString; std::vector expandedArguments; mf.ExpandArguments(this->Functions[c].Arguments, expandedArguments); bool isTrue = cmIfCommand::IsTrue(expandedArguments,errorString,&mf); if (errorString.size()) { std::string err = "had incorrect arguments: "; unsigned int i; for(i =0; i < this->Functions[c].Arguments.size(); ++i) { err += (this->Functions[c].Arguments[i].Quoted?"\"":""); err += this->Functions[c].Arguments[i].Value; err += (this->Functions[c].Arguments[i].Quoted?"\"":""); err += " "; } err += "("; err += errorString; err += ")."; cmSystemTools::Error(err.c_str()); return false; } if (isTrue) { this->IsBlocking = false; this->HasRun = true; } } } // should we execute? else if (!this->IsBlocking) { status.Clear(); mf.ExecuteCommand(this->Functions[c],status); if (status.GetReturnInvoked()) { inStatus.SetReturnInvoked(true); mf.RemoveFunctionBlocker(lff); return true; } if (status.GetBreakInvoked()) { inStatus.SetBreakInvoked(true); mf.RemoveFunctionBlocker(lff); return true; } } } mf.RemoveFunctionBlocker(lff); return true; } } // record the command this->Functions.push_back(lff); // always return true return true; } //========================================================================= bool cmIfFunctionBlocker::ShouldRemove(const cmListFileFunction& lff, cmMakefile&) { if (!cmSystemTools::Strucmp(lff.Name.c_str(),"endif")) { // if the endif has arguments, then make sure // they match the arguments of the matching if if (lff.Arguments.size() == 0 || lff.Arguments == this->Args) { return true; } } return false; } //========================================================================= void cmIfFunctionBlocker::ScopeEnded(cmMakefile &mf) { std::string errmsg = "The end of a CMakeLists file was reached with an " "IF statement that was not closed properly.\nWithin the directory: "; errmsg += mf.GetCurrentDirectory(); errmsg += "\nThe arguments are: "; for(std::vector::const_iterator j = this->Args.begin(); j != this->Args.end(); ++j) { errmsg += (j->Quoted?"\"":""); errmsg += j->Value; errmsg += (j->Quoted?"\"":""); errmsg += " "; } cmSystemTools::Message(errmsg.c_str(), "Warning"); } //========================================================================= bool cmIfCommand ::InvokeInitialPass(const std::vector& args, cmExecutionStatus &) { std::string errorString; std::vector expandedArguments; this->Makefile->ExpandArguments(args, expandedArguments); bool isTrue = cmIfCommand::IsTrue(expandedArguments,errorString,this->Makefile); if (errorString.size()) { std::string err = "had incorrect arguments: "; unsigned int i; for(i =0; i < args.size(); ++i) { err += (args[i].Quoted?"\"":""); err += args[i].Value; err += (args[i].Quoted?"\"":""); err += " "; } err += "("; err += errorString; err += ")."; this->SetError(err.c_str()); return false; } cmIfFunctionBlocker *f = new cmIfFunctionBlocker(); // if is isn't true block the commands f->ScopeDepth = 1; f->IsBlocking = !isTrue; if (isTrue) { f->HasRun = true; } f->Args = args; this->Makefile->AddFunctionBlocker(f); return true; } namespace { //========================================================================= void IncrementArguments(std::list &newArgs, std::list::iterator &argP1, std::list::iterator &argP2) { if (argP1 != newArgs.end()) { argP1++; argP2 = argP1; if (argP1 != newArgs.end()) { argP2++; } } } //========================================================================= // helper function to reduce code duplication void HandlePredicate(bool value, int &reducible, std::list::iterator &arg, std::list &newArgs, std::list::iterator &argP1, std::list::iterator &argP2) { if(value) { *arg = "1"; } else { *arg = "0"; } newArgs.erase(argP1); argP1 = arg; IncrementArguments(newArgs,argP1,argP2); reducible = 1; } //========================================================================= // helper function to reduce code duplication void HandleBinaryOp(bool value, int &reducible, std::list::iterator &arg, std::list &newArgs, std::list::iterator &argP1, std::list::iterator &argP2) { if(value) { *arg = "1"; } else { *arg = "0"; } newArgs.erase(argP2); newArgs.erase(argP1); argP1 = arg; IncrementArguments(newArgs,argP1,argP2); reducible = 1; } //========================================================================= enum Op { OpLess, OpEqual, OpGreater }; bool HandleVersionCompare(Op op, const char* lhs_str, const char* rhs_str) { // Parse out up to 4 components. unsigned int lhs[4] = {0,0,0,0}; unsigned int rhs[4] = {0,0,0,0}; sscanf(lhs_str, "%u.%u.%u.%u", &lhs[0], &lhs[1], &lhs[2], &lhs[3]); sscanf(rhs_str, "%u.%u.%u.%u", &rhs[0], &rhs[1], &rhs[2], &rhs[3]); // Do component-wise comparison. for(unsigned int i=0; i < 4; ++i) { if(lhs[i] < rhs[i]) { // lhs < rhs, so true if operation is LESS return op == OpLess; } else if(lhs[i] > rhs[i]) { // lhs > rhs, so true if operation is GREATER return op == OpGreater; } } // lhs == rhs, so true if operation is EQUAL return op == OpEqual; } //========================================================================= // level 0 processes parenthetical expressions bool HandleLevel0(std::list &newArgs, cmMakefile *makefile, std::string &errorString) { int reducible; do { reducible = 0; std::list::iterator arg = newArgs.begin(); while (arg != newArgs.end()) { if (*arg == "(") { // search for the closing paren for this opening one std::list::iterator argClose; argClose = arg; argClose++; unsigned int depth = 1; while (argClose != newArgs.end() && depth) { if (*argClose == "(") { depth++; } if (*argClose == ")") { depth--; } argClose++; } if (depth) { errorString = "mismatched parenthesis in condition"; return false; } // store the reduced args in this vector std::vector newArgs2; // copy to the list structure std::list::iterator argP1 = arg; argP1++; for(; argP1 != argClose; argP1++) { newArgs2.push_back(*argP1); } newArgs2.pop_back(); // now recursively invoke IsTrue to handle the values inside the parenthetical expression bool value = cmIfCommand::IsTrue(newArgs2, errorString, makefile); if(value) { *arg = "1"; } else { *arg = "0"; } argP1 = arg; argP1++; // remove the now evaluated parenthetical expression newArgs.erase(argP1,argClose); } ++arg; } } while (reducible); return true; } //========================================================================= // level one handles most predicates except for NOT bool HandleLevel1(std::list &newArgs, cmMakefile *makefile, std::string &) { int reducible; do { reducible = 0; std::list::iterator arg = newArgs.begin(); std::list::iterator argP1; std::list::iterator argP2; while (arg != newArgs.end()) { argP1 = arg; IncrementArguments(newArgs,argP1,argP2); // does a file exist if (*arg == "EXISTS" && argP1 != newArgs.end()) { HandlePredicate( cmSystemTools::FileExists((argP1)->c_str()), reducible, arg, newArgs, argP1, argP2); } // does a directory with this name exist if (*arg == "IS_DIRECTORY" && argP1 != newArgs.end()) { HandlePredicate( cmSystemTools::FileIsDirectory((argP1)->c_str()), reducible, arg, newArgs, argP1, argP2); } // is the given path an absolute path ? if (*arg == "IS_ABSOLUTE" && argP1 != newArgs.end()) { HandlePredicate( cmSystemTools::FileIsFullPath((argP1)->c_str()), reducible, arg, newArgs, argP1, argP2); } // does a command exist if (*arg == "COMMAND" && argP1 != newArgs.end()) { HandlePredicate( makefile->CommandExists((argP1)->c_str()), reducible, arg, newArgs, argP1, argP2); } // does a policy exist if (*arg == "POLICY" && argP1 != newArgs.end()) { cmPolicies::PolicyID pid; HandlePredicate( makefile->GetPolicies()->GetPolicyID((argP1)->c_str(), pid), reducible, arg, newArgs, argP1, argP2); } // does a target exist if (*arg == "TARGET" && argP1 != newArgs.end()) { HandlePredicate( makefile->FindTargetToUse((argP1)->c_str())? true:false, reducible, arg, newArgs, argP1, argP2); } // is a variable defined if (*arg == "DEFINED" && argP1 != newArgs.end()) { size_t argP1len = argP1->size(); bool bdef = false; if(argP1len > 4 && argP1->substr(0, 4) == "ENV{" && argP1->operator[](argP1len-1) == '}') { std::string env = argP1->substr(4, argP1len-5); bdef = cmSystemTools::GetEnv(env.c_str())?true:false; } else { bdef = makefile->IsDefinitionSet((argP1)->c_str()); } HandlePredicate(bdef, reducible, arg, newArgs, argP1, argP2); } ++arg; } } while (reducible); return true; } //========================================================================= // level two handles most binary operations except for AND OR bool HandleLevel2(std::list &newArgs, cmMakefile *makefile, std::string &errorString) { int reducible; const char *def; const char *def2; do { reducible = 0; std::list::iterator arg = newArgs.begin(); std::list::iterator argP1; std::list::iterator argP2; while (arg != newArgs.end()) { argP1 = arg; IncrementArguments(newArgs,argP1,argP2); if (argP1 != newArgs.end() && argP2 != newArgs.end() && *(argP1) == "MATCHES") { def = cmIfCommand::GetVariableOrString(arg->c_str(), makefile); const char* rex = (argP2)->c_str(); cmStringCommand::ClearMatches(makefile); cmsys::RegularExpression regEntry; if ( !regEntry.compile(rex) ) { cmOStringStream error; error << "Regular expression \"" << rex << "\" cannot compile"; errorString = error.str(); return false; } if (regEntry.find(def)) { cmStringCommand::StoreMatches(makefile, regEntry); *arg = "1"; } else { *arg = "0"; } newArgs.erase(argP2); newArgs.erase(argP1); argP1 = arg; IncrementArguments(newArgs,argP1,argP2); reducible = 1; } if (argP1 != newArgs.end() && *arg == "MATCHES") { *arg = "0"; newArgs.erase(argP1); argP1 = arg; IncrementArguments(newArgs,argP1,argP2); reducible = 1; } if (argP1 != newArgs.end() && argP2 != newArgs.end() && (*(argP1) == "LESS" || *(argP1) == "GREATER" || *(argP1) == "EQUAL")) { def = cmIfCommand::GetVariableOrString(arg->c_str(), makefile); def2 = cmIfCommand::GetVariableOrString((argP2)->c_str(), makefile); double lhs; double rhs; bool result; if(sscanf(def, "%lg", &lhs) != 1 || sscanf(def2, "%lg", &rhs) != 1) { result = false; } else if (*(argP1) == "LESS") { result = (lhs < rhs); } else if (*(argP1) == "GREATER") { result = (lhs > rhs); } else { result = (lhs == rhs); } HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2); } if (argP1 != newArgs.end() && argP2 != newArgs.end() && (*(argP1) == "STRLESS" || *(argP1) == "STREQUAL" || *(argP1) == "STRGREATER")) { def = cmIfCommand::GetVariableOrString(arg->c_str(), makefile); def2 = cmIfCommand::GetVariableOrString((argP2)->c_str(), makefile); int val = strcmp(def,def2); bool result; if (*(argP1) == "STRLESS") { result = (val < 0); } else if (*(argP1) == "STRGREATER") { result = (val > 0); } else // strequal { result = (val == 0); } HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2); } if (argP1 != newArgs.end() && argP2 != newArgs.end() && (*(argP1) == "VERSION_LESS" || *(argP1) == "VERSION_GREATER" || *(argP1) == "VERSION_EQUAL")) { def = cmIfCommand::GetVariableOrString(arg->c_str(), makefile); def2 = cmIfCommand::GetVariableOrString((argP2)->c_str(), makefile); Op op = OpEqual; if(*argP1 == "VERSION_LESS") { op = OpLess; } else if(*argP1 == "VERSION_GREATER") { op = OpGreater; } bool result = HandleVersionCompare(op, def, def2); HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2); } // is file A newer than file B if (argP1 != newArgs.end() && argP2 != newArgs.end() && *(argP1) == "IS_NEWER_THAN") { int fileIsNewer=0; bool success=cmSystemTools::FileTimeCompare(arg->c_str(), (argP2)->c_str(), &fileIsNewer); HandleBinaryOp( (success==false || fileIsNewer==1 || fileIsNewer==0), reducible, arg, newArgs, argP1, argP2); } ++arg; } } while (reducible); return true; } //========================================================================= // level 3 handles NOT bool HandleLevel3(std::list &newArgs, cmMakefile *makefile, std::string &) { int reducible; const char *def; do { reducible = 0; std::list::iterator arg = newArgs.begin(); std::list::iterator argP1; std::list::iterator argP2; while (arg != newArgs.end()) { argP1 = arg; IncrementArguments(newArgs,argP1,argP2); if (argP1 != newArgs.end() && *arg == "NOT") { def = cmIfCommand::GetVariableOrNumber((argP1)->c_str(), makefile); HandlePredicate(cmSystemTools::IsOff(def), reducible, arg, newArgs, argP1, argP2); } ++arg; } } while (reducible); return true; } //========================================================================= // level 4 handles AND OR bool HandleLevel4(std::list &newArgs, cmMakefile *makefile, std::string &) { int reducible; const char *def; const char *def2; do { reducible = 0; std::list::iterator arg = newArgs.begin(); std::list::iterator argP1; std::list::iterator argP2; while (arg != newArgs.end()) { argP1 = arg; IncrementArguments(newArgs,argP1,argP2); if (argP1 != newArgs.end() && *(argP1) == "AND" && argP2 != newArgs.end()) { def = cmIfCommand::GetVariableOrNumber(arg->c_str(), makefile); def2 = cmIfCommand::GetVariableOrNumber((argP2)->c_str(), makefile); HandleBinaryOp( !(cmSystemTools::IsOff(def) || cmSystemTools::IsOff(def2)), reducible, arg, newArgs, argP1, argP2); } if (argP1 != newArgs.end() && *(argP1) == "OR" && argP2 != newArgs.end()) { def = cmIfCommand::GetVariableOrNumber(arg->c_str(), makefile); def2 = cmIfCommand::GetVariableOrNumber((argP2)->c_str(), makefile); HandleBinaryOp( !(cmSystemTools::IsOff(def) && cmSystemTools::IsOff(def2)), reducible, arg, newArgs, argP1, argP2); } ++arg; } } while (reducible); return true; } } //========================================================================= // order of operations, // 1. ( ) -- parenthetical groups // 2. IS_DIRECTORY EXISTS COMMAND DEFINED etc predicates // 3. MATCHES LESS GREATER EQUAL STRLESS STRGREATER STREQUAL etc binary ops // 4. NOT // 5. AND OR // // There is an issue on whether the arguments should be values of references, // for example IF (FOO AND BAR) should that compare the strings FOO and BAR // or should it really do IF (${FOO} AND ${BAR}) Currently IS_DIRECTORY // EXISTS COMMAND and DEFINED all take values. EQUAL, LESS and GREATER can // take numeric values or variable names. STRLESS and STRGREATER take // variable names but if the variable name is not found it will use the name // directly. AND OR take variables or the values 0 or 1. bool cmIfCommand::IsTrue(const std::vector &args, std::string &errorString, cmMakefile *makefile) { const char *def; errorString = ""; // handle empty invocation if (args.size() < 1) { return false; } // store the reduced args in this vector std::list newArgs; // copy to the list structure for(unsigned int i = 0; i < args.size(); ++i) { newArgs.push_back(args[i]); } // now loop through the arguments and see if we can reduce any of them // we do this multiple times. Once for each level of precedence if (!HandleLevel0(newArgs, makefile, errorString)) // parens { return false; } if (!HandleLevel1(newArgs, makefile, errorString)) //predicates { return false; } if (!HandleLevel2(newArgs, makefile, errorString)) // binary ops { return false; } if (!HandleLevel3(newArgs, makefile, errorString)) // NOT { return false; } if (!HandleLevel4(newArgs, makefile, errorString)) // AND OR { return false; } // now at the end there should only be one argument left if (newArgs.size() == 1) { if (*newArgs.begin() == "0") { return false; } if (*newArgs.begin() == "1") { return true; } def = makefile->GetDefinition(args[0].c_str()); if(cmSystemTools::IsOff(def)) { return false; } } else { errorString = "Unknown arguments specified"; return false; } return true; } //========================================================================= const char* cmIfCommand::GetVariableOrString(const char* str, const cmMakefile* mf) { const char* def = mf->GetDefinition(str); if(!def) { def = str; } return def; } //========================================================================= const char* cmIfCommand::GetVariableOrNumber(const char* str, const cmMakefile* mf) { const char* def = mf->GetDefinition(str); if(!def) { if (atoi(str)) { def = str; } } return def; }