/* This file is part of mhmake.
*
* Copyright (C) 2001-2010 marha@sourceforge.net
*
* Mhmake is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Mhmake is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Mhmake. If not, see .
*/
/* $Rev$ */
#include "stdafx.h"
#include "util.h"
#include "md5.h"
#include "mhmakefileparser.h"
#include "rule.h"
#include "flexlexer.h"
commandqueue mhmakefileparser::sm_CommandQueue;
///////////////////////////////////////////////////////////////////////////////
int mhmakefileparser::yylex(void)
{
m_yyloc=m_ptheLexer->lineno();
return m_ptheLexer->yylex(m_theTokenValue);
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::yyerror(const char *m)
{
cerr << this->m_ptheLexer->m_InputFileName<< " ("<GetQuotedFullFileName();
}
ifstream yyin(pFileInfo->GetFullFileName().c_str(),ios_base::in);
if (yyin.fail())
{
cerr << "Error opening makefile: "<GetQuotedFullFileName()<GetFullFileName();
theLexer.m_pParser=(mhmakeparser*)this;
int Ret=yyparse();
return Ret;
}
///////////////////////////////////////////////////////////////////////////////
bool mhmakefileparser::IsDefined(const string &Var) const
{
bool Ret = m_Variables.find(Var)!=m_Variables.end();
if (!Ret)
{
string Env=GetFromEnv(Var);
if (!Env.empty())
{
Ret=true;
}
}
return Ret;
}
///////////////////////////////////////////////////////////////////////////////
static inline size_t SkipUntilQuote(const string &Expr,size_t i,char Char)
{
while (Expr[i++]!=Char) ;
return i;
}
///////////////////////////////////////////////////////////////////////////////
static inline size_t SkipMakeExpr(const string &Expr,size_t i)
{
char Char=Expr[i++];
if (Char!='(')
return i;
Char=Expr[i++];
while (Char!=')')
{
if (Char=='$')
{
i=SkipMakeExpr(Expr,i);
}
#ifdef _DEBUG
if (i>=Expr.length())
throw(string(") not found in ")+Expr);
#endif
Char=Expr[i++];
}
return i;
}
///////////////////////////////////////////////////////////////////////////////
// Splits expression on the Item, but the item may not occur within
// a macro or quoted string
static pair SplitExpr(const string &Expr,char Item)
{
size_t i=0;
char Char=Expr[i++];
while (Char!=Item)
{
if (Char=='"' || Char=='\'')
{
i=SkipUntilQuote(Expr,i,Char);
}
else if (Char=='$')
{
i=SkipMakeExpr(Expr,i);
}
Char=Expr[i++];
}
return pair(Expr.substr(0,i-1),Expr.substr(i));
}
///////////////////////////////////////////////////////////////////////////////
bool mhmakefileparser::IsEqual(const string &EqualExpr) const
{
string Expr=ExpandExpression(EqualExpr);
const char *pStr=Expr.c_str();
const char *pTmp=pStr;
while (*pTmp && *pTmp!=',') pTmp++;
ptrdiff_t Pos=pTmp-pStr;
size_t Size=Expr.size();
pTmp=pStr+Size-1;
while (pTmp>pStr && strchr(" \t",*pTmp))
{
pTmp--;
}
if (2*Pos != pTmp-pStr)
{
return false;
}
pTmp=pStr;
const char *pTmp2=pTmp+Pos+1;
if (*pTmp=='(')
{
pTmp++;
Pos--;
}
for (int i=0; im_InExpandExpression++;
size_t i=0;
size_t Length=Expr.size();
string Ret;
string ToAdd;
while (ii)
{
ToAdd=ExpandMacro(Expr.substr(i,inew-i-1),Recurse);
i=inew;
}
else
{
// This is a single character expression
ToAdd=ExpandMacro(string(1,Expr[i-1]),Recurse);
}
}
Ret+=ToAdd;
}
else
{
Ret+=Char;
}
}
return Ret;
}
///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::ExpandMacro(const string &Expr, bool &Recurse) const
{
string ExpandedExpr=ExpandExpressionRecurse(Expr,Recurse);
const char *pTmp=ExpandedExpr.c_str();
/* First remove leading spaces */
while (*pTmp==' ' || *pTmp=='\t') pTmp++;
const char *pVar=pTmp;
while (*pTmp && *pTmp!=' ' && *pTmp!='\t' && *pTmp!=':') pTmp++;
const char *pVarEnd=pTmp;
char Type=*pTmp++;
while (*pTmp && (*pTmp==' ' || *pTmp=='\t')) pTmp++;
if (Type&&*pTmp)
{ // We have a match for the regular expression ^([^ \\t:]+)([: \\t])[ \\t]*(.+)
if (Type==':')
{
#ifdef WIN32
bool IsFileName=false;
if (pVarEnd-pVar == 1 && (*pVar=='<' || *pVar =='@'))
IsFileName=true;
#endif
string ToSubst=ExpandExpression(ExpandVar(string(pVar,pVarEnd)));
const char *pSrc=pTmp;
const char *pStop=pSrc;
while (*pStop!='=') pStop++;
const char *pTo=pStop+1;
string SrcStr(pSrc,pStop);
string ToStr(pTo);
#ifdef WIN32
if (IsFileName)
{
matchres Res;
string FileName(UnquoteFileName(ToSubst));
if (PercentMatch(FileName,UnquoteFileName(SrcStr),&Res))
{
FileName=ReplaceWithStem(UnquoteFileName(ToStr),Res.m_Stem);
}
return QuoteFileName(FileName);
}
#endif
return Substitute(ToSubst,SrcStr,ToStr);
}
else if (Type==' ' || Type == '\t')
{
string Func(pVar,pVarEnd);
string Arg(pTmp);
if (Recurse)
{
#ifdef _DEBUG
if (!(Arg.find('%')!=string::npos && Arg.find('$')!=string::npos))
throw(string("Bug in mhmake: expected a % and $ sign: ")+Arg);
#endif
return string("$(")+ExpandedExpr+")"; // we cannot call the function yet since there is still a $(*%*) macro to resolve. so
// return it a a macro again so it can be resolved when the % is resolved
// remark that the current test is not completely safe because the % could be out of
// the $ macro
}
function_f pFunc=m_Functions[Func];
#ifdef _DEBUG
if (pFunc)
{
return (this->*pFunc)(Arg, &Expr);
}
else
{
throw string("Unknown function specified in macro: ")+Func;
}
#else
return (this->*pFunc)(Arg);
#endif
}
else
{
#ifdef _DEBUG
throw string("Fatal error in ExpandMacro (bug in mhmake ? ? ?)");
#else
return g_EmptyString;
#endif
}
}
else
{
if (ExpandedExpr.find('%')!=string::npos)
{
// we have encountered a *%* macro. This means a previous subst is not yet finished. so return
// it back as a macro so it can be expanded again later when the % is replaced
Recurse=true;
return string("$(")+ExpandedExpr+")";
}
#ifdef _DEBUG
else if (ExpandedExpr.find('$')!=string::npos)
throw(string("Bug in mhmake: wasn't expecting a $ sign in: ")+ExpandedExpr);
#endif
else
{
return ExpandExpression(ExpandVar(ExpandedExpr));
}
}
}
///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::ExpandVar(const string &Var) const
{
map::const_iterator pIt=m_Variables.find(Var);
if (pIt==m_Variables.end())
{
if (Var.size()==1)
{
char Char=Var[0];
if (m_RuleThatIsBuild)
{
switch (Char)
{
case '<': // return first prerequisit
#ifdef _DEBUG
if (!m_RuleThatIsBuild->GetDeps().size())
{
return "";
}
#endif
return m_RuleThatIsBuild->GetDeps()[0]->GetQuotedFullFileName();
case '@': // return full target file name
return m_RuleThatIsBuild->GetQuotedFullFileName();
case '*': // return stem
return m_RuleThatIsBuild->GetRule()->GetStem();
case '^': // return all prerequisits
return m_RuleThatIsBuild->GetPrerequisits();
case '/':
return OSPATHSEPSTR;
default:
break;
}
}
else
{
switch (Char)
{
case '<': // return first prerequisit
case '@': // return full target file name
case '*': // return stem
case '^': // return all prerequisits
return Var; // To make comparing of rules more accurate
case '/':
return OSPATHSEPSTR;
default:
break;
}
}
}
string Env=GetFromEnv(Var);
if (Env.empty())
{
#ifdef _DEBUG
if (g_PrintAdditionalInfo)
{
cout<<"Warning: Variable "<second;
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::SplitToItems(const string &String,vector &Items) const
{
const char *pTmp=String.c_str();
while (*pTmp)
{
string Item;
pTmp=NextItem(pTmp,Item);
if (!Item.empty())
{
Items.push_back(GetFileInfo(Item,m_MakeDir));
}
}
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::ParseBuildedIncludeFiles()
{
vector::iterator It=m_ToBeIncludeAfterBuild.begin();
while (It!=m_ToBeIncludeAfterBuild.end())
{
int result=ParseFile(GetFileInfo(*It,m_MakeDir));
if (result)
{
throw string("Error parsing ")+*It;
}
It++;
}
}
#ifdef _DEBUG
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::PrintVariables(bool Expand) const
{
map::const_iterator It=m_Variables.begin();
while (It!=m_Variables.end())
{
if (Expand)
{
try
{
cout<first<<" : "<second)<first<<" : "<second<begin();
while (pIt!=m_pCurrentItems->end())
{
if ((*pIt)->GetFullFileName().find('%')!=string::npos)
{
IMPLICITRULE::AddImplicitRule(*pIt,*m_pCurrentDeps,m_pCurrentRule);
}
else
{
// If we had a double colon we must make sure that the target is always build
if (m_DoubleColonRule)
{
(*pIt)->SetNotExist();
}
(*pIt)->SetRuleIfNotExist(m_pCurrentRule);
if (!m_pCurrentRule)
(*pIt)->AddDeps(*m_pCurrentDeps);
else
(*pIt)->InsertDeps(*m_pCurrentDeps);
if (!m_FirstTarget)
{ // Only check this if the rule is not an implicit rule
m_FirstTarget=(*m_pCurrentItems)[0];
}
}
pIt++;
}
m_pCurrentItems=NULL;
m_pCurrentRule=NullRule;
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::GetAutoDeps(const fileinfo *pFirstDep, deps_t &Autodeps)
{
/* Here we have to scan only c/c++ headers so skip certain extensions */
const char *pFullName=pFirstDep->GetFullFileName().c_str();
const char *pExt=strrchr(pFullName,'.');
bool bPython=false;
if (pExt)
{
if (!_stricmp(pExt+1,"py"))
bPython=true;
}
FILE *pIn=fopen(pFullName,"r");
if (!pIn)
return;
char IncludeList[255];
int PrevRet=0;
int Ret=fgetc(pIn);
while(Ret!=EOF)
{
char Type[2];
bool bFound=false;
if (bPython)
{
Type[0]='"';
if (Ret=='i')
{
Ret=fscanf(pIn,"mport %254[^\"\n]",IncludeList);
if (Ret==1)
{
if (IncludeList[0]!='*')
bFound=true;
}
}
}
else
{
if (PrevRet=='/')
{
if (Ret=='/')
{
/* This is a C++ command so read until the next line-feed */
do
{
Ret=fgetc(pIn);
} while (Ret!='\n' && Ret!=EOF);
}
else if (Ret=='*')
{
/* This is a standard C comment, so read until then end of the command */
do
{
PrevRet=Ret;
Ret=fgetc(pIn);
} while ((PrevRet!='*' || Ret!='/') && Ret!=EOF);
}
}
else if (Ret=='#' || Ret=='.')
{
if (Ret=='#')
{
fscanf(pIn,"%*[ \t]");
Ret=fscanf(pIn,"include %1[\"<]%254[^>\"]%*[\">]",&Type,IncludeList);
}
else
Ret=fscanf(pIn,"import %1[\"<]%254[^>\"]%*[\">]",&Type,IncludeList);
if (Ret==2)
{
bFound=true;
}
}
}
if (bFound)
{
const char *pTmp=IncludeList;
while (*pTmp)
{
string IncludeFile;
pTmp=NextItem(pTmp,IncludeFile," \t,");
if (bPython)
IncludeFile+=".py";
if (SkipHeaderFile(IncludeFile))
continue;
#ifdef _DEBUG
m_ImplicitSearch++; // This is to avoid warnings of targets that does not exist
#endif
if (Type[0]=='"')
{
fileinfo *pInclude=GetFileInfo(IncludeFile,pFirstDep->GetDir());
/* Add the dependency when the file alrady exist or there is a rule available to be build */
mh_time_t Date=BuildTarget(pInclude);
if (Date.DoesExist()) // Try to build the target, and add it if it exists after building
{
deps_t::const_iterator pFind=Autodeps.find(pInclude);
if (pFind==Autodeps.end())
{
Autodeps.insert(pInclude);
GetAutoDepsIfNeeded(pInclude,pInclude);
}
}
}
const refptr IncludeDirs=GetIncludeDirs();
fileinfoarray::const_iterator It=IncludeDirs->begin();
while (Itend())
{
fileinfo *pInclude=GetFileInfo(IncludeFile,*It);
mh_time_t Date=BuildTarget(pInclude);
if (Date.DoesExist()) // Try to build the target, and add it if it exists after building
{
deps_t::const_iterator pFind=Autodeps.find(pInclude);
if (pFind==Autodeps.end())
{
Autodeps.insert(pInclude);
GetAutoDepsIfNeeded(pInclude,pInclude);
}
break;
}
It++;
}
#ifdef _DEBUG
m_ImplicitSearch--;
#endif
}
}
PrevRet=Ret;
Ret=fgetc(pIn);
}
fclose(pIn);
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::GetAutoDepsIfNeeded(fileinfo *pTarget, const fileinfo *pFirstDep)
{
autodeps_entry_t &Autodeps=m_AutoDeps[pTarget];
if (!Autodeps.first)
{
Autodeps.first=true;
/* We are going to rescan, so throw away the old. */
Autodeps.second.clear();
GetAutoDeps(pFirstDep,Autodeps.second);
// Now add these dependencies also to the rules
deps_t::iterator It=Autodeps.second.begin();
while (It!=Autodeps.second.end())
{
pTarget->AddDep(*It);
It++;
}
}
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::UpdateAutomaticDependencies(fileinfo *pTarget)
{
if (pTarget->IsAutoDepExtention())
{
// we have to search for the include files in the first dependency of Target
vector &Deps=pTarget->GetDeps();
if (!Deps.size())
return; // There is no first dep
fileinfo *pFirstDep=Deps[0];
GetAutoDepsIfNeeded(pTarget,pFirstDep);
SetAutoDepsDirty();
}
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::UpdateNoRuleAutomaticDependencies(fileinfo *pTarget)
{
// we have to search for the include files in the Target
deps_t Autodeps;
GetAutoDeps(pTarget,Autodeps);
// Now add these dependencies also to the rules
deps_t::iterator It=Autodeps.begin();
while (It!=Autodeps.end())
{
pTarget->AddDep(*It);
It++;
}
}
///////////////////////////////////////////////////////////////////////////////
const refptr mhmakefileparser::GetIncludeDirs() const
{
string Includes=ExpandExpression("$(INCLUDES)");
if (!m_pIncludeDirs || Includes != m_IncludeDirs)
{
((mhmakefileparser*)this)->m_IncludeDirs=Includes;
((mhmakefileparser*)this)->m_pIncludeDirs=refptr(new fileinfoarray);
if (Includes.empty()) // If not defined try a default path
Includes="include inc .."OSPATHSEPSTR"include .."OSPATHSEPSTR"inc";
const char *pTmp=Includes.c_str();
while (*pTmp)
{
string Item;
pTmp=NextItem(pTmp,Item);
fileinfo *pIncDir=GetFileInfo(Item,m_MakeDir);
if (pIncDir->Exists() || pIncDir->GetRule())
((mhmakefileparser*)this)->m_pIncludeDirs->push_back(pIncDir);
}
}
return m_pIncludeDirs;
}
static void ReadStr(FILE *pFile,char *Str)
{
int i=0;
while (1)
{
Str[i]=fgetc(pFile);
#ifdef _DEBUG
if (Str[i]==EOF)
{
cout<<"Premature end of depency file.\n";
Str[i]=0;
return;
}
#endif
if (Str[i]=='\n')
break;
i++;
}
Str[i]='\0';
}
void mhmakefileparser::LoadAutoDepsFile(fileinfo *pDepFile)
{
if (m_AutoDepFileLoaded && m_AutoDepFileLoaded==pDepFile)
return; /* This autodep file is already loaded. */
m_AutoDepFileLoaded=pDepFile;
FILE *pIn=fopen(pDepFile->GetFullFileName().c_str(),"rb");
#ifdef _DEBUG
if (!pIn)
{
cerr << "Error opening autodep file "<GetQuotedFullFileName()<GetQuotedFullFileName()<<": "<AddDep(pDep);
}
ReadStr(pIn,FileName);
}
ReadStr(pIn,FileName);
}
uint32 Md5_32;
bool MakeNotDirty=true;
while (fread(&Md5_32,sizeof(Md5_32),1,pIn))
{
ReadStr(pIn,FileName);
fileinfo *pTarget=GetAbsFileInfo(FileName);
if (!pTarget->CompareMd5_32(0) && !pTarget->CompareMd5_32(Md5_32))
{
MakeNotDirty=false; /* BuildTarget had set the dirty flag, but since the md5 did not change it was a false dirty. This is for BuildTargets called before this routine */
#ifdef _DEBUG
cout << "Warning: trying to set to different md5's for Target "<GetQuotedFullFileName()<<" Old: "<GetCommandsMd5_32()<<" New: "<GetQuotedFullFileName()<<" to "<SetCommandsMd5_32(Md5_32); // If it was already there, just update the md5 value
AddTarget(pTarget);
}
if (MakeNotDirty)
ClearAutoDepsDirty();
fclose(pIn);
}
static void MakeDirs(const fileinfo *pDir)
{
fileinfo *pParentDir=pDir->GetDir();
if (!pParentDir->GetDate().DoesExist())
{ /* First make parent dirs */
MakeDirs(pParentDir);
}
if (!pDir->GetDate().DoesExist())
{ /* Create directory */
mkdir(pDir->GetFullFileName().c_str(),S_IRWXU);
}
}
void mhmakefileparser::SaveAutoDepsFile()
{
if (!IsAutoDepsDirty())
return;
if (g_Clean
#ifdef _DEBUG
|| g_DoNotExecute || g_GenProjectTree
#endif
)
return; // do not save on clean or if no commands are executed
string DepFile=ExpandVar(AUTODEPFILE);
if (!DepFile.size())
{
return;
}
fileinfo *pDepFile=GetFileInfo(DepFile,m_MakeDir);
#ifdef _DEBUG
if (g_PrintAdditionalInfo)
cout<<"Saving automatic dependency file "<GetFullFileName().c_str(),"wb");
if (!pOut)
{
/* Maybe it is because the directory does not exist, so try to create this first */
MakeDirs(pDepFile->GetDir());
pOut=fopen(pDepFile->GetFullFileName().c_str(),"wb");
if (!pOut)
{
#ifdef _DEBUG
if (!g_DoNotExecute)
#endif
cerr << "Error creating file "<second.second.empty())
{
fprintf(pOut,"%s\n",It->first->GetFullFileName().c_str());
deps_t::const_iterator DepIt=It->second.second.begin();
while (DepIt!=It->second.second.end())
{
fprintf(pOut,"%s\n",(*DepIt)->GetFullFileName().c_str());
DepIt++;
}
fprintf(pOut,"\n");
}
It++;
}
/* Now save the Md5 strings */
fprintf(pOut,"\n");
set::iterator pIt=m_Targets.begin();
while (pIt!=m_Targets.end())
{
if (!(*pIt)->CompareMd5_32(0))
{
(*pIt)->WriteMd5_32(pOut);
string FileName=(*pIt)->GetFullFileName();
fwrite(FileName.c_str(),FileName.size(),1,pOut);
fputc('\n',pOut);
}
pIt++;
}
fclose(pOut);
}
///////////////////////////////////////////////////////////////////////////////
// Uses the SKIPHEADERS variable to check if we have to skip the header in
// the automatic dependency scanning
bool mhmakefileparser::SkipHeaderFile(const string &FileName)
{
if (!m_SkipHeadersInitialized)
{
m_SkipHeadersInitialized=true;
string HeadersToSkip=ExpandVar(SKIPHEADERS);
const char *pTmp=HeadersToSkip.c_str();
while (*pTmp)
{
string Item;
pTmp=NextItem(pTmp,Item);
if (Item.find('%')==string::npos)
{
m_SkipHeaders.insert(Item);
}
else
{
m_PercentHeaders.push_back(Item);
}
}
}
if (m_SkipHeaders.find(FileName)!=m_SkipHeaders.end())
return true;
vector::const_iterator It=m_PercentHeaders.begin();
vector::const_iterator ItEnd=m_PercentHeaders.end();
while (It!=ItEnd)
{
if (PercentMatch(FileName,*It++))
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::SetExport(const string &Var, const string &Val)
{
m_Exports.insert(Var);
#ifdef WIN32
if (!m_pEnv)
{
/* Environment not created yet, so create one */
char *pEnv=GetEnvironmentStrings();
char *pEnd=pEnv;
while (*pEnd)
{
while (*pEnd++);
}
size_t Len=pEnd-pEnv+1;
m_pEnv=(char*)malloc(Len);
memcpy(m_pEnv,pEnv,Len);
m_EnvLen=Len;
FreeEnvironmentStrings(pEnv);
}
/* First check if the variable is in the environment, if so remove it first */
char *pEnv=m_pEnv;
while (*pEnv)
{
const char *pVar=Var.c_str();
char *pStart=pEnv;
while (*pEnv!='=' && *pEnv==*pVar)
{
pEnv++; pVar++;
}
if (*pEnv=='=' && !*pVar)
{
/* Variable found, remove it */
while (*pEnv++);
m_EnvLen-=pEnv-pStart;
while (*pEnv)
{
while (*pEnv)
{
*pStart=*pEnv++;
pStart++;
}
*pStart=*pEnv++;
pStart++;
}
*pStart=*pEnv;
break;
}
while (*pEnv++);
}
size_t VarLen=Var.length();
size_t ValLen=Val.length();
size_t Extra=VarLen+ValLen+2;
/* Add the variable at the end */
m_pEnv=(char*)realloc(m_pEnv,m_EnvLen+Extra);
pEnv=m_pEnv+m_EnvLen-1;
memcpy(pEnv,Var.c_str(),VarLen);
pEnv+=VarLen;
*pEnv++='=';
memcpy(pEnv,Val.c_str(),ValLen);
pEnv+=ValLen;
*pEnv++='\0';
*pEnv++='\0';
m_EnvLen+=Extra;
#else
if (!m_pEnv)
{
/* Environment not created yet, so create one */
char **pEnv=environ;
char **pEnd=pEnv;
int Len=1;
while (*pEnd)
{
Len++;
pEnd++;
}
m_EnvLen=Len;
m_pEnv=(char**)malloc(Len*sizeof(pEnv));
int i=0;
while (*pEnv)
{
m_pEnv[i]=strdup(*pEnv);
i++;
pEnv++;
}
m_pEnv[i]=NULL;
}
/* First check if the variable is in the environment, if so replace it */
char **pEnv=m_pEnv;
while (*pEnv)
{
const char *pVar=Var.c_str();
char *pStart=*pEnv;
char *pTmp=pStart;
while (*pTmp!='=' && *pTmp==*pVar)
{
pTmp++; pVar++;
}
if (*pTmp=='=' && !*pVar)
{
free(*pEnv);
*pEnv=strdup((Var+"="+Val).c_str());
break;
}
pEnv++;
}
if (!*pEnv)
{
// Add it at the end of the list
m_pEnv=(char**)realloc(m_pEnv,(m_EnvLen+1)*sizeof(*pEnv));
m_pEnv[m_EnvLen-1]=strdup((Var+"="+Val).c_str());
m_pEnv[m_EnvLen++]=NULL;
}
#endif
}
///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::SetvPath(const string &Pattern, const string &Path)
{
// First create the array of directory from the Path parameter. For now all entries need to
// be valid directories (extra checking). This could be improved by checking if there
// is a rule for creating the directory entry, but like first said, not for now.
refptr pDirArray=new fileinfoarray;
const char *pTmp=Path.c_str();
while (*pTmp)
{
string Item;
pTmp=NextItem(pTmp,Item," \t:;");
if (!Item.empty())
{
fileinfo *pDir=GetFileInfo(Item,m_MakeDir);
if (!pDir->IsDir())
{
#ifdef WIN32
// On windows this could happen if a full pathname was specified, so we try together
// with the next item to check if this is not a valid directories
// but only if the the length of the item was 1 namely the driver letter
if (Item.length()==1)
{
string Rest;
pTmp=NextItem(pTmp,Rest," \t:;");
pDir=GetFileInfo(Item+":"+Rest,m_MakeDir);
#ifdef _DEBUG
if (g_PrintAdditionalInfo && !pDir->IsDir())
throw(pDir->GetFullFileName()+" is not a valid directory.");
#endif
}
#ifdef _DEBUG
else if (g_PrintAdditionalInfo)
cout << pDir->GetFullFileName() << " is not a valid directory.\n";
#endif
#else
#ifdef _DEBUG
if (g_PrintAdditionalInfo)
cout << pDir->GetFullFileName() << " is not a valid directory.\n";
#endif
#endif
}
pDirArray->push_back(pDir);
}
}
m_vPath.push_back(pair >(Pattern,pDirArray));
}
///////////////////////////////////////////////////////////////////////////////
//
// Search for the target using the vPath
fileinfo* mhmakefileparser::SearchvPath(const fileinfo* pTarget)
{
string TargetName=pTarget->GetName();
vector< pair< string, refptr > >::iterator vPathIt=m_vPath.begin();
while (vPathIt!=m_vPath.end())
{
matchres Res;
if (PercentMatch(TargetName,vPathIt->first,&Res))
{
fileinfoarray::iterator pIt=vPathIt->second->begin();
while (pIt!=vPathIt->second->end())
{
fileinfo* pNewTarget=GetFileInfo(TargetName,*pIt);
mh_time_t TargetDate=StartBuildTarget(pNewTarget,false);
if (!TargetDate.IsDateValid())
TargetDate=WaitBuildTarget(pNewTarget);
if (pNewTarget->GetDate().DoesExist())
return pNewTarget;
pIt++;
}
}
vPathIt++;
}
return NULL;
}
///////////////////////////////////////////////////////////////////////////////
//
//Checks if the variables retreived from the environment or command-line have been
//changed. Do this at late as possible because they can also be changed in theLexer
//makefiles.
//
void mhmakefileparser::CheckEnv(void)
{
if (CompareEnv())
{
#ifdef _DEBUG
if (!g_GenProjectTree)
cout << "Rebuilding everything of "<< m_MakeDir->GetQuotedFullFileName() <<" because environment and/or command-line variables have been changed.\n";
#endif
SetRebuildAll();
}
}
///////////////////////////////////////////////////////////////////////////////
//
//Create a Md5 string from m_GlobalCommandLineVars and USED_ENVVARS
//
static bool SkipVar(const string &Var)
{
if (Var==WC_REVISION)
return true;
if (Var==WC_URL)
return true;
if (Var==MAKE)
return true;
return false;
}
#define DBGOUT(stuff)
uint32 mhmakefileparser::CreateEnvMd5_32() const
{
md5_context ctx;
string Md5;
string EnvVars=ExpandVar(USED_ENVVARS);
const char *pTmp=EnvVars.c_str();
// Now create the md5 string
md5_starts( &ctx );
DBGOUT(cout << "MD5 of " << m_MakeDir->GetQuotedFullFileName() << endl);
while (*pTmp)
{
string Var;
pTmp=NextItem(pTmp,Var,";");
if (!SkipVar(Var))
{
string Val=ExpandVar(Var);
transform(Val.begin(),Val.end(),Val.begin(),(int(__CDECL *)(int))toupper);
DBGOUT(cout << GetMakeDir()->GetQuotedFullFileName() << " -> Setting GetFromEnv var " << Var << " to " << Val << endl);
md5_update( &ctx, (uint8 *) Var.c_str(), (unsigned long)Var.size());
md5_update( &ctx, (uint8 *) "=", 1);
md5_update( &ctx, (uint8 *) Val.c_str(), (unsigned long)Val.size());
}
}
return md5_finish32( &ctx);
}
///////////////////////////////////////////////////////////////////////////////
// Makes sure that the makefile exports are set in the environment
bool mhmakefileparser::CompareEnv() const
{
uint32 Md5_32=CreateEnvMd5_32();
#ifdef _DEBUG
if (!g_GenProjectTree && Md5_32!=m_EnvMd5_32)
cout << "Environment has been changed: "<::const_iterator pLineFind=m_CommandLineVars.find(Var);
if (pLineFind!=m_CommandLineVars.end())
{
((mhmakefileparser*)this)->m_UsedEnvVars.insert(Var);
if (Cache)
((mhmakefileparser*)this)->m_Variables[Var]=pLineFind->second;
return pLineFind->second;
}
const char *pEnv=getenv(Var.c_str());
if (!pEnv)
{
return g_EmptyString;
}
((mhmakefileparser*)this)->m_UsedEnvVars.insert(Var);
if (Cache)
((mhmakefileparser*)this)->m_Variables[Var]=pEnv;
return pEnv;
}
///////////////////////////////////////////////////////////////////////////////
// Creates the variable USER_ENVVARS to be saved in the autodeps file
//
void mhmakefileparser::CreateUSED_ENVVARS()
{
set Variables;
set::const_iterator It=m_UsedEnvVars.begin();
set::const_iterator ItEnd=m_UsedEnvVars.end();
while (It!=ItEnd)
{
string Var=*It;
if (!SkipVar(Var))
{
transform(Var.begin(),Var.end(),Var.begin(),(int(__CDECL *)(int))toupper);
Variables.insert(Var);
}
It++;
}
It=m_Exports.begin();
ItEnd=m_Exports.end();
while (It!=ItEnd)
{
string Var=*It;
if (!SkipVar(Var))
{
transform(Var.begin(),Var.end(),Var.begin(),(int(__CDECL *)(int))toupper);
Variables.insert(Var);
}
It++;
}
map::const_iterator CLItEnd=loadedmakefile::sm_Statics.m_GlobalCommandLineVars.end();
map::const_iterator CLIt=loadedmakefile::sm_Statics.m_GlobalCommandLineVars.begin();
while (CLIt!=CLItEnd)
{
string Var=CLIt->first;
if (!SkipVar(Var))
{
transform(Var.begin(),Var.end(),Var.begin(),(int(__CDECL *)(int))toupper);
Variables.insert(Var);
}
CLIt++;
}
It=Variables.begin();
ItEnd=Variables.end();
string Val;
while (It!=ItEnd)
{
Val+=*It;
Val+=";";
It++;
}
m_Variables[USED_ENVVARS]=Val;
}
static string s_TrueString("1");
static string s_FalseString("0");
static string AndExpression(const string &First, const string &Second)
{
if (First.empty() || First==s_FalseString)
{
return g_EmptyString;
}
else
{
return Second.empty() || Second==s_FalseString ? g_EmptyString : s_TrueString;
}
}
static string OrExpression(const string &First, const string &Second)
{
if (First.empty() || First==s_FalseString)
{
return Second.empty() || Second==s_FalseString ? g_EmptyString : s_TrueString;
}
else
{
return s_TrueString;
}
}
string mhmakefileparser::ResolveExpression(const string &InExpr,string &Rest) const
{
unsigned i=0;
string Ret;
string Expr=InExpr;
Rest=g_EmptyString;
while (i