/*============================================================================ 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 static std::string cmIfCommandError( cmMakefile* mf, std::vector const& args) { cmLocalGenerator* lg = mf->GetLocalGenerator(); std::string err = "given arguments:\n "; for(std::vector::const_iterator i = args.begin(); i != args.end(); ++i) { err += " "; err += lg->EscapeForCMake(i->c_str()); } err += "\n"; return err; } //========================================================================= 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; // if trace is enabled, print a (trivially) evaluated "else" // statement if(!this->IsBlocking && mf.GetCMakeInstance()->GetTrace()) { mf.PrintCommandTrace(this->Functions[c]); } } 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); // if trace is enabled, print the evaluated "elseif" statement if(mf.GetCMakeInstance()->GetTrace()) { mf.PrintCommandTrace(this->Functions[c]); } 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 = cmIfCommandError(&mf, expandedArguments); 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 = cmIfCommandError(this->Makefile, expandedArguments); 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 { //========================================================================= bool GetBooleanValue(std::string& arg, cmMakefile* mf) { // Check basic constants. if (arg == "0") { return false; } if (arg == "1") { return true; } // Check named constants. if (cmSystemTools::IsOn(arg.c_str())) { return true; } if (cmSystemTools::IsOff(arg.c_str())) { return false; } // Check for numbers. if(!arg.empty()) { char* end; double d = strtod(arg.c_str(), &end); if(*end == '\0') { // The whole string is a number. Use C conversion to bool. return d? true:false; } } // Check definition. const char* def = mf->GetDefinition(arg.c_str()); return !cmSystemTools::IsOff(def); } //========================================================================= // Boolean value behavior from CMake 2.6.4 and below. bool GetBooleanValueOld(std::string const& arg, cmMakefile* mf, bool one) { if(one) { // Old IsTrue behavior for single argument. if(arg == "0") { return false; } else if(arg == "1") { return true; } else { return !cmSystemTools::IsOff(mf->GetDefinition(arg.c_str())); } } else { // Old GetVariableOrNumber behavior. const char* def = mf->GetDefinition(arg.c_str()); if(!def && atoi(arg.c_str())) { def = arg.c_str(); } return !cmSystemTools::IsOff(def); } } //========================================================================= // returns the resulting boolean value bool GetBooleanValueWithAutoDereference( std::string &newArg, cmMakefile *makefile, std::string &errorString, cmPolicies::PolicyStatus Policy12Status, cmake::MessageType &status, bool oneArg = false) { // Use the policy if it is set. if (Policy12Status == cmPolicies::NEW) { return GetBooleanValue(newArg, makefile); } else if (Policy12Status == cmPolicies::OLD) { return GetBooleanValueOld(newArg, makefile, oneArg); } // Check policy only if old and new results differ. bool newResult = GetBooleanValue(newArg, makefile); bool oldResult = GetBooleanValueOld(newArg, makefile, oneArg); if(newResult != oldResult) { switch(Policy12Status) { case cmPolicies::WARN: { cmPolicies* policies = makefile->GetPolicies(); errorString = "An argument named \"" + newArg + "\" appears in a conditional statement. " + policies->GetPolicyWarning(cmPolicies::CMP0012); status = cmake::AUTHOR_WARNING; } case cmPolicies::OLD: return oldResult; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: { cmPolicies* policies = makefile->GetPolicies(); errorString = "An argument named \"" + newArg + "\" appears in a conditional statement. " + policies->GetRequiredPolicyError(cmPolicies::CMP0012); status = cmake::FATAL_ERROR; } case cmPolicies::NEW: break; } } return newResult; } //========================================================================= 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; } //========================================================================= // 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); } // does a symlink with this name exist if (*arg == "IS_SYMLINK" && argP1 != newArgs.end()) { HandlePredicate( cmSystemTools::FileIsSymlink((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); cmSystemTools::CompareOp op = cmSystemTools::OP_EQUAL; if(*argP1 == "VERSION_LESS") { op = cmSystemTools::OP_LESS; } else if(*argP1 == "VERSION_GREATER") { op = cmSystemTools::OP_GREATER; } bool result = cmSystemTools::VersionCompare(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"; status = cmake::FATAL_ERROR; return false; } return GetBooleanValueWithAutoDereference(*(newArgs.begin()), makefile, errorString, Policy12Status, status, true); } //========================================================================= const char* cmIfCommand::GetVariableOrString(const char* str, const cmMakefile* mf) { const char* def = mf->GetDefinition(str); if(!def) { def = str; } return def; }