CMake/Source/cmIfCommand.cxx
Stephen Kelly 48bb48e114 De-duplicate version comparison code.
Extend the VersionCompare in cmSystemTools to handle 8 components,
and port the if command to use that.
2013-06-12 14:09:36 +02:00

920 lines
27 KiB
C++

/*============================================================================
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 <stdlib.h> // required for atof
#include <list>
#include <cmsys/RegularExpression.hxx>
static std::string cmIfCommandError(
cmMakefile* mf, std::vector<std::string> const& args)
{
cmLocalGenerator* lg = mf->GetLocalGenerator();
std::string err = "given arguments:\n ";
for(std::vector<std::string>::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<cmFunctionBlocker>
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<void>(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<std::string> 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<cmListFileArgument>& args,
cmExecutionStatus &)
{
std::string errorString;
std::vector<std::string> 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<std::string> &newArgs,
std::list<std::string>::iterator &argP1,
std::list<std::string>::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<std::string>::iterator &arg,
std::list<std::string> &newArgs,
std::list<std::string>::iterator &argP1,
std::list<std::string>::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<std::string>::iterator &arg,
std::list<std::string> &newArgs,
std::list<std::string>::iterator &argP1,
std::list<std::string>::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<std::string> &newArgs,
cmMakefile *makefile,
std::string &errorString,
cmake::MessageType &status)
{
int reducible;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
while (arg != newArgs.end())
{
if (*arg == "(")
{
// search for the closing paren for this opening one
std::list<std::string>::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<std::string> newArgs2;
// copy to the list structure
std::list<std::string>::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<std::string> &newArgs,
cmMakefile *makefile,
std::string &, cmake::MessageType &)
{
int reducible;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
std::list<std::string>::iterator argP1;
std::list<std::string>::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<std::string> &newArgs,
cmMakefile *makefile,
std::string &errorString,
cmake::MessageType &status)
{
int reducible;
const char *def;
const char *def2;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
std::list<std::string>::iterator argP1;
std::list<std::string>::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<std::string> &newArgs,
cmMakefile *makefile,
std::string &errorString,
cmPolicies::PolicyStatus Policy12Status,
cmake::MessageType &status)
{
int reducible;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
std::list<std::string>::iterator argP1;
std::list<std::string>::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<std::string> &newArgs,
cmMakefile *makefile,
std::string &errorString,
cmPolicies::PolicyStatus Policy12Status,
cmake::MessageType &status)
{
int reducible;
bool lhs;
bool rhs;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
std::list<std::string>::iterator argP1;
std::list<std::string>::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<std::string> &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<std::string> 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;
}