/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2000-2009 Kitware, Inc., Insight Software Consortium 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 "cmIfCommand.h" #include "cmStringCommand.h" #include // required for atof #include #include //========================================================================= bool cmIfFunctionBlocker:: IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile &mf, cmExecutionStatus &inStatus) { // 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) { // Remove the function blocker for this scope or bail. cmsys::auto_ptr fb(mf.RemoveFunctionBlocker(this, lff)); if(!fb.get()) { return false; } // execute the functions for the true parts of the if statement 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 { // Place this call on the call stack. cmMakefileCall stack_manager(&mf, this->Functions[c], status); static_cast(stack_manager); std::string errorString; std::vector expandedArguments; mf.ExpandArguments(this->Functions[c].Arguments, expandedArguments); cmake::MessageType messType; bool isTrue = cmIfCommand::IsTrue(expandedArguments, errorString, &mf, messType); if (errorString.size()) { std::string err = "given arguments\n "; 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 += "\n"; err += errorString; mf.IssueMessage(messType, err); if (messType == cmake::FATAL_ERROR) { cmSystemTools::SetFatalErrorOccured(); return true; } } 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); return true; } if (status.GetBreakInvoked()) { inStatus.SetBreakInvoked(true); return true; } } } 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; } //========================================================================= bool cmIfCommand ::InvokeInitialPass(const std::vector& args, cmExecutionStatus &) { std::string errorString; std::vector expandedArguments; this->Makefile->ExpandArguments(args, expandedArguments); cmake::MessageType status; bool isTrue = cmIfCommand::IsTrue(expandedArguments,errorString, this->Makefile, status); if (errorString.size()) { std::string err = "given arguments\n "; unsigned int i; for(i =0; i < args.size(); ++i) { err += (args[i].Quoted?"\"":""); err += args[i].Value; err += (args[i].Quoted?"\"":""); err += " "; } err += "\n"; err += errorString; if (status == cmake::FATAL_ERROR) { this->SetError(err.c_str()); cmSystemTools::SetFatalErrorOccured(); return false; } else { this->Makefile->IssueMessage(status, err); } } 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 { //========================================================================= // returns true if succesfull, the resulting bool parsed is stored in result bool GetBooleanValue(std::string &newArg, cmMakefile *makefile, bool &result, std::string &errorString, cmPolicies::PolicyStatus Policy12Status, cmake::MessageType &status) { if (Policy12Status != cmPolicies::OLD && Policy12Status != cmPolicies::WARN) { // please note IsOn(var) does not always equal !IsOff(var) // that is why each is called if (cmSystemTools::IsOn(newArg.c_str())) { result = true; return true; } if (cmSystemTools::IsOff(newArg.c_str())) { result = false; return true; } return false; } // Old policy is more complex... // 0 and 1 are very common, test for them first quickly if (newArg == "0") { result = false; return true; } if (newArg == "1") { result = true; return true; } // old behavior is to dereference the var if (Policy12Status == cmPolicies::OLD) { return false; } // now test for values that may be the name of a variable // warn if used if (cmSystemTools::IsOn(newArg.c_str())) { // only warn if the value would change const char *def = makefile->GetDefinition(newArg.c_str()); if (cmSystemTools::IsOff(def)) { cmPolicies* policies = makefile->GetPolicies(); errorString = "You have used a variable or argument named \"" + newArg + "\" in a conditional statement. Please be aware of issues " + "related to policy CMP0012. " + policies->GetPolicyWarning(cmPolicies::CMP0012); status = cmake::AUTHOR_WARNING; } return false; } if (cmSystemTools::IsOff(newArg.c_str())) { // only warn if the value would change const char *def = makefile->GetDefinition(newArg.c_str()); if (!cmSystemTools::IsOff(def)) { cmPolicies* policies = makefile->GetPolicies(); errorString = "You have used a variable or argument named \"" + newArg + "\" in a conditional statement. Please be aware of issues " + "related to policy CMP0012. " + policies->GetPolicyWarning(cmPolicies::CMP0012); status = cmake::AUTHOR_WARNING; } return false; } return false; } //========================================================================= // returns the resulting boolean value bool GetBooleanValueWithAutoDereference( std::string &newArg, cmMakefile *makefile, std::string &errorString, cmPolicies::PolicyStatus Policy12Status, cmake::MessageType &status) { bool result = false; if (GetBooleanValue(newArg, makefile, result, errorString, Policy12Status, status)) { return result; } const char *def = makefile->GetDefinition(newArg.c_str()); return !cmSystemTools::IsOff(def); } //========================================================================= 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, cmake::MessageType &status) { 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"; status = cmake::FATAL_ERROR; 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, status); 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 &, cmake::MessageType &) { 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, cmake::MessageType &status) { 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(); status = cmake::FATAL_ERROR; 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 &errorString, cmPolicies::PolicyStatus Policy12Status, cmake::MessageType &status) { 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); if (argP1 != newArgs.end() && *arg == "NOT") { bool rhs = GetBooleanValueWithAutoDereference(*argP1, makefile, errorString, Policy12Status, status); HandlePredicate(!rhs, reducible, arg, newArgs, argP1, argP2); } ++arg; } } while (reducible); return true; } //========================================================================= // level 4 handles AND OR bool HandleLevel4(std::list &newArgs, cmMakefile *makefile, std::string &errorString, cmPolicies::PolicyStatus Policy12Status, cmake::MessageType &status) { int reducible; bool lhs; bool rhs; 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()) { lhs = GetBooleanValueWithAutoDereference(*arg, makefile, errorString, Policy12Status, status); rhs = GetBooleanValueWithAutoDereference(*argP2, makefile, errorString, Policy12Status, status); HandleBinaryOp((lhs && rhs), reducible, arg, newArgs, argP1, argP2); } if (argP1 != newArgs.end() && *(argP1) == "OR" && argP2 != newArgs.end()) { lhs = GetBooleanValueWithAutoDereference(*arg, makefile, errorString, Policy12Status, status); rhs = GetBooleanValueWithAutoDereference(*argP2, makefile, errorString, Policy12Status, status); HandleBinaryOp((lhs || rhs), 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, cmake::MessageType &status) { 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 // parens if (!HandleLevel0(newArgs, makefile, errorString, status)) { return false; } //predicates if (!HandleLevel1(newArgs, makefile, errorString, status)) { return false; } // binary ops if (!HandleLevel2(newArgs, makefile, errorString, status)) { return false; } // used to store the value of policy CMP0012 for performance cmPolicies::PolicyStatus Policy12Status = makefile->GetPolicyStatus(cmPolicies::CMP0012); // NOT if (!HandleLevel3(newArgs, makefile, errorString, Policy12Status, status)) { return false; } // AND OR if (!HandleLevel4(newArgs, makefile, errorString, Policy12Status, status)) { return false; } // now at the end there should only be one argument left if (newArgs.size() != 1) { errorString = "Unknown arguments specified"; return false; } return GetBooleanValueWithAutoDereference(*(newArgs.begin()), makefile, errorString, Policy12Status, status); } //========================================================================= const char* cmIfCommand::GetVariableOrString(const char* str, const cmMakefile* mf) { const char* def = mf->GetDefinition(str); if(!def) { def = str; } return def; }