/* 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 "rule.h"
#include "util.h"
#include "mhmakeparser.hpp"
#ifdef WIN32
#include
#define REPARSE_MOUNTPOINT_HEADER_SIZE 8
typedef struct {
DWORD ReparseTag;
DWORD ReparseDataLength;
WORD Reserved;
WORD ReparseTargetLength;
WORD ReparseTargetMaximumLength;
WORD Reserved1;
WCHAR ReparseTarget[1];
} REPARSE_MOUNTPOINT_DATA_BUFFER, *PREPARSE_MOUNTPOINT_DATA_BUFFER;
#endif
static char s_UsageString[]=
"\
Usage: mhmake [-f ] [-[c|C] ] [=]\n\
[-a] [-q] [-s] [-v] [-P ] [targets]+\n"
#ifdef _DEBUG
"\
[-p] [-n] [-e] [-l] [-w] [-d] [-CD] [-m] [-b]\n"
#endif
"\n\
: Makefile to load (if not specified 'makefile' is used\n\
: Make is setting the current directory to this directory at the\n\
start.\n\
: Defines a variable\n\
: Value of the variable\n\
-a : Rebuild all targets\n\
-s : Rescan automatic dependencies\n\
-v : Print version information\n\
-q : Quiet. Disable all output \n\
-P :\n\
Number of parallel build commands executed at the \n\
same time. Default is this the number of processor \n\
cores. 1 disables parallel builds.\n"
#ifdef _DEBUG
"\n\
The following options are additional options in mhmake_dbg which are not\n\
available in mhmake. These are mainly options for debugging purposes.\n\
-e : Dump Vars and Rules on error\n\
-w : Print additional information\n\
-p : Prints the variables and the rules before building\n\
-n : Only prints the commands, but does not execute them\n\
-l : Print parser debug information\n\
-d : Print the dependency checking\n\
-CD : Do circular dependency checking (targets depending on itself)\n\
-m : Create md5 database in md5.database in start directory. \n\
-b : Print build tree. \n\
-D : Print all double defined rules (even if commands are the same) \n\
"
#else
"\
\n\
It is adviced during creation of makefiles to use mhmake_dbg. It has additional\n\
debugging options and does some extra error checking.\n\
For a description of the additional options: run mhmake_dbg -h\n\
"
#endif
;
#ifdef _DEBUG
bool g_PrintVarsAndRules=false;
bool g_DoNotExecute=false;
bool g_BuildMd5Db=false;
bool g_GenProjectTree=false;
bool g_DumpOnError=false;
bool g_PrintAdditionalInfo=false;
bool g_pPrintDependencyCheck=false;
bool g_CheckCircularDeps=false;
bool g_PrintLexYacc=false;
bool g_PrintMultipleDefinedRules=false;
#endif
bool g_Quiet=false;
bool g_RebuildAll=false;
bool g_ForceAutoDepRescan=false;
const string g_EmptyString;
const string g_SpaceString(" ");
///////////////////////////////////////////////////////////////////////////////
void PrintVersionInfo(void)
{
static const char VersionStr[]="\
mhmake : GNU compatible make tool with extensions\n\
version: "MHMAKEVER"\n\
Remarks and bug reports -> marha@sourceforge.net\n\
";
cerr << VersionStr;
exit(1);
}
///////////////////////////////////////////////////////////////////////////////
makecommand::makecommand()
{
char ExeName[MAX_PATH];
#ifdef WIN32
GetModuleFileName(NULL,ExeName,sizeof(ExeName));
m_BuildCommand=ExeName;
transform(m_BuildCommand.begin(),m_BuildCommand.end(),m_BuildCommand.begin(),(int(__CDECL *)(int))tolower);
#else
int NrChars=readlink ("/proc/self/exe", ExeName, sizeof(ExeName));
ExeName[NrChars]=0;
m_BuildCommand=ExeName;
#endif
}
///////////////////////////////////////////////////////////////////////////////
string Substitute(const string &ToSubst,const string &iSrcStr,const string &iToStr)
{
string Ret=g_EmptyString;
matchres Res;
string SrcStr=iSrcStr;
string ToStr=iToStr;
if (string::npos==SrcStr.find('%'))
{
string PerStr("%");
SrcStr=PerStr+SrcStr;
ToStr=PerStr+ToStr;
}
const char *pTmp=ToSubst.c_str();
bool first=true;
while (*pTmp)
{
if (!first)
{
Ret+=g_SpaceString;
}
else
{
first=false;
}
string Item;
pTmp=NextItem(pTmp,Item);
if (PercentMatch(Item,SrcStr,&Res))
{
Ret+=ReplaceWithStem(ToStr,Res.m_Stem);
}
else
{
Ret+=Item;
}
}
return Ret;
}
///////////////////////////////////////////////////////////////////////////////
bool PercentMatch(const string &String,const string &Expr,matchres *pRes,const char Char)
{
const char *pFirst=String.c_str();
const char *pFirstExpr=Expr.c_str();
while (*pFirstExpr && *pFirstExpr!=Char)
{
if (*pFirst!=*pFirstExpr)
return false;
pFirst++;
pFirstExpr++;
}
if (!*pFirstExpr)
{
if (!*pFirst)
{
if (pRes)
{
pRes->m_First=String;
pRes->m_Stem=pRes->m_Last=g_EmptyString;
}
return true;
} else
return false;
}
else if (!*pFirst)
return false;
const char *pEnd=pFirst+strlen(pFirst);
const char *pLast=pEnd;
const char *pLastExpr=pFirstExpr+strlen(pFirstExpr)-1;
if (pLastExpr!=pFirstExpr)
{
pLast--;
while (pLastExpr>pFirstExpr)
{
if (*pLastExpr!=*pLast)
return false;
pLastExpr--;
pLast--;
}
pLast++;
}
string Stem=string(pFirst,pLast-pFirst);
if (pRes)
{
pRes->m_First=string(String.c_str(),pFirst-String.c_str());
pRes->m_Stem=Stem;
pRes->m_Last=string(pLast,pEnd-pLast);
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
bool PercentMatchNoCase(const string &String,const string &Expr,matchres *pRes,const char Char)
{
const char *pFirst=String.c_str();
const char *pFirstExpr=Expr.c_str();
while (*pFirstExpr && *pFirstExpr!=Char)
{
if (tolower(*pFirst)!=tolower(*pFirstExpr))
return false;
pFirst++;
pFirstExpr++;
}
if (!*pFirstExpr)
{
if (!*pFirst)
{
if (pRes)
{
pRes->m_First=String;
pRes->m_Stem=pRes->m_Last=g_EmptyString;
}
return true;
} else
return false;
}
else if (!*pFirst)
return false;
const char *pEnd=pFirst+strlen(pFirst);
const char *pLast=pEnd;
const char *pLastExpr=pFirstExpr+strlen(pFirstExpr)-1;
if (pLastExpr!=pFirstExpr)
{
pLast--;
while (pLastExpr>pFirstExpr)
{
if (tolower(*pLastExpr)!=tolower(*pLast))
return false;
pLastExpr--;
pLast--;
}
pLast++;
}
string Stem=string(pFirst,pLast-pFirst);
if (pRes)
{
pRes->m_First=string(String.c_str(),pFirst-String.c_str());
pRes->m_Stem=Stem;
pRes->m_Last=string(pLast,pEnd-pLast);
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
bool PercentMatchList(const string &String,const string &ExprList,matchres *pRes)
{
const char *pTmp=ExprList.c_str();
while (*pTmp)
{
string Expr;
pTmp=NextItem(pTmp,Expr);
if (PercentMatch(String,Expr,pRes))
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
string ReplaceWithStem(const string &String,const string &Stem)
{
string Ret=String;
size_t Pos=Ret.find('%');
while (Pos!=string::npos)
{
Ret=Ret.substr(0,Pos)+Stem+Ret.substr(Pos+1);
Pos=Ret.find('%');
}
return Ret;
}
///////////////////////////////////////////////////////////////////////////////
refptr LOADEDMAKEFILES::find(const loadedmakefile &ToSearch)
{
vector >::const_iterator It=begin();
while (It!=end())
{
if (*(*It)==ToSearch)
return *It;
It++;
}
return refptr();
}
LOADEDMAKEFILES g_LoadedMakefiles;
///////////////////////////////////////////////////////////////////////////////
loadedmakefile::loadedmakefile_statics::loadedmakefile_statics()
{
m_GlobalCommandLineVars[MAKE]=g_MakeCommand;
const char *pEnv=getenv(MHMAKECONF);
if (pEnv)
{
string Env(QuoteFileName(pEnv));
m_GlobalCommandLineVars[MHMAKECONF]=Env;
m_MhMakeConf=GetAbsFileInfo(Env);
// Get the revision of the working copy
// We do it with the svn info command
string Output;
try
{
mhmakefileparser Dummy(curdir::GetCurDir());
string SvnCommand=Dummy.SearchCommand("svn",EXEEXT);
#ifdef WIN32
if (GetFileAttributes(m_MhMakeConf->GetFullFileName().c_str())&FILE_ATTRIBUTE_REPARSE_POINT)
{
WIN32_FIND_DATA FindData;
HANDLE hFind=FindFirstFile(m_MhMakeConf->GetFullFileName().c_str(),&FindData);
if (hFind!=INVALID_HANDLE_VALUE)
{
if (FindData.dwReserved0==IO_REPARSE_TAG_MOUNT_POINT)
{
HANDLE hDir = ::CreateFile(m_MhMakeConf->GetFullFileName().c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; // We need a large buffer
REPARSE_MOUNTPOINT_DATA_BUFFER& ReparseBuffer = (REPARSE_MOUNTPOINT_DATA_BUFFER&)buf;
DWORD dwRet;
if (::DeviceIoControl(hDir, FSCTL_GET_REPARSE_POINT, NULL, 0, &ReparseBuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwRet, NULL))
{
// Success
::CloseHandle(hDir);
LPCWSTR pPath = ReparseBuffer.ReparseTarget;
if (wcsncmp(pPath, L"\\??\\", 4) == 0) pPath += 4; // Skip 'non-parsed' prefix
char szPath[MAX_PATH];
::WideCharToMultiByte(CP_ACP, 0, pPath, -1, szPath, MAX_PATH, NULL, NULL);
Dummy.OsExeCommand(SvnCommand,string(" info ")+GetFileInfo(szPath,m_MhMakeConf->GetDir())->GetQuotedFullFileName(),false,&Output);
}
else
{ // Error
::CloseHandle(hDir);
}
}
FindClose(hFind);
}
}
#else
struct stat Stat;
lstat(m_MhMakeConf->GetFullFileName().c_str(),&Stat);
if (S_ISLNK(Stat.st_mode))
{
char FileName[1024];
int len=readlink(m_MhMakeConf->GetFullFileName().c_str(),FileName,sizeof(FileName));
FileName[len]=0;
Dummy.OsExeCommand(SvnCommand,string(" info ")+GetFileInfo(FileName,m_MhMakeConf->GetDir())->GetQuotedFullFileName(),false,&Output);
}
#endif
else
Dummy.OsExeCommand(SvnCommand,string(" info ")+m_MhMakeConf->GetQuotedFullFileName(),false,&Output);
}
catch (string Message)
{
}
char *pTok=strtok((char*)Output.c_str(),"\n"); // doing this is changing string, so this is very dangerous !!!
while (pTok)
{
if (!strncmp(pTok,"URL: ",5))
{
m_GlobalCommandLineVars[WC_URL]=pTok+5+7;
}
else if (!strncmp(pTok,"Revision: ",10))
{
m_GlobalCommandLineVars[WC_REVISION]=pTok+10;
break;
}
pTok=strtok(NULL,"\n");
}
}
}
///////////////////////////////////////////////////////////////////////////////
loadedmakefile::loadedmakefile(const fileinfo *pDir, vector &Args,const string&Makefile)
{
m_Makefile=NULL;
m_CommandLineVars=sm_Statics.m_GlobalCommandLineVars;
m_MakeDir=NULL;
vector::iterator ArgIt=Args.begin();
while (ArgIt!=Args.end())
{
size_t EqPos=ArgIt->find('=');
if (EqPos!=string::npos)
{
string Var=ArgIt->substr(0,EqPos);
string Val=ArgIt->substr(EqPos+1);
m_CommandLineVars[Var]=Val;
}
else if ((*ArgIt)[0]=='-')
{
switch ((*ArgIt)[1])
{
case 'f':
if (ArgIt->size()>2)
{
if (!m_MakeDir)
{
m_Makefile=GetFileInfo(ArgIt->substr(2),pDir);
}
else
{
m_Makefile=GetFileInfo(ArgIt->substr(2),m_MakeDir);
}
}
else
{
ArgIt++;
if (!m_MakeDir)
{
m_Makefile=GetFileInfo(*ArgIt,pDir);
}
else
{
m_Makefile=GetFileInfo(*ArgIt,m_MakeDir);
}
}
break;
case 'C':
#ifdef _DEBUG
if (ArgIt->size()>2 && (*ArgIt)[2]=='D')
{
g_CheckCircularDeps=true;
break;
}
#endif
/* Fall through */
case 'c':
if (ArgIt->size()>2)
m_MakeDir=GetFileInfo(ArgIt->substr(2),pDir);
else
{
ArgIt++;
m_MakeDir=GetFileInfo(*ArgIt,pDir);
}
break;
case 'a':
g_RebuildAll=true;
break;
case 'q':
g_Quiet=true;
break;
case 's':
g_ForceAutoDepRescan=true;
break;
case 'v':
PrintVersionInfo();
break;
case 'P':
if (ArgIt->size()>2)
mhmakefileparser::SetNrParallelBuilds(atoi(ArgIt->substr(2).c_str()));
else
{
ArgIt++;
mhmakefileparser::SetNrParallelBuilds(atoi((*ArgIt).c_str()));
}
break;
#ifdef _DEBUG
case 'p':
g_PrintVarsAndRules=true;
break;
case 'n':
g_DoNotExecute=true;
break;
case 'w':
g_PrintAdditionalInfo=true;
break;
case 'd':
g_pPrintDependencyCheck=true;
break;
case 'D':
g_PrintMultipleDefinedRules=true;
break;
case 'l':
g_PrintLexYacc=true;
break;
case 'e':
g_DumpOnError=true;
break;
case 'm':
g_BuildMd5Db=true;
break;
case 'b':
g_GenProjectTree=true;
break;
#endif
default:
throw string("\nUnknown option: ")+*ArgIt+"\n\n"+ s_UsageString;
}
}
else
{
m_CommandLineTargets.push_back(*ArgIt);
}
ArgIt++;
}
if (!m_Makefile)
{
if (!Makefile.empty())
{
if (!m_MakeDir)
m_Makefile=GetFileInfo(Makefile,pDir);
else
m_Makefile=GetFileInfo(Makefile,m_MakeDir);
}
}
if (!m_Makefile)
{
if (!m_MakeDir)
m_Makefile=GetFileInfo(m_CommandLineTargets[0],pDir);
else
m_Makefile=GetFileInfo(m_CommandLineTargets[0],m_MakeDir);
m_CommandLineTargets.erase(m_CommandLineTargets.begin());
}
if (!m_MakeDir)
{
if (Makefile==g_EmptyString)
m_MakeDir=m_Makefile->GetDir(); /* This is one from load_makefile, so we take the directory of the makefile itself. */
else
m_MakeDir=curdir::GetCurDir(); /* This means that this is the main makefile given on the command line, so we take the current directory */
}
if (loadedmakefile::sm_Statics.m_MhMakeConf)
{
const string &RootDir=loadedmakefile::sm_Statics.m_MhMakeConf->GetFullFileName();
string MakeDir=m_MakeDir->GetFullFileName();
if (RootDir.length()>MakeDir.length() || _strnicmp(RootDir.c_str(),MakeDir.c_str(),RootDir.length()))
{
cerr<<"mhmake needs to run in a directory that is a subdirectory of the directory specified with %MHMAKECONF : "<GetFullFileName()<GetQuotedFullFileName()<(new mhmakefileparser(m_CommandLineVars));
// Add the MAKECMDGOALS environment variable
string MakeCmdGoals;
bool First=true;
vector::iterator TarIt=m_CommandLineTargets.begin();
while (TarIt!=m_CommandLineTargets.end())
{
if (First)
First=false;
else
MakeCmdGoals+=g_SpaceString;
MakeCmdGoals+=*TarIt;
TarIt++;
}
m_pMakefileParser->SetVariable("MAKECMDGOALS",MakeCmdGoals);
string BaseAutoMak;
// First parse the makefile.before makefile which is in the directory $(MHMAKECONF) environment variable
fileinfo *pDepFile;
if (sm_Statics.m_MhMakeConf)
{
BaseAutoMak=m_pMakefileParser->ExpandVar(BASEAUTOMAK);
if (BaseAutoMak.empty())
{
BaseAutoMak="makefile";
m_pMakefileParser->SetVariable(BASEAUTOMAK,BaseAutoMak);
}
const fileinfo *pBeforeMakefile=GetFileInfo(BaseAutoMak+".before",sm_Statics.m_MhMakeConf);
int result=m_pMakefileParser->ParseFile(pBeforeMakefile,m_MakeDir);
if (result)
{
throw string("Error parsing ")+pBeforeMakefile->GetQuotedFullFileName();
}
m_pMakefileParser->UpdateDate(pBeforeMakefile->GetDate());
// Now parse the automaticly generated dependency file, which needs to be in the objdir directory
string ObjDirName=m_pMakefileParser->ExpandExpression("$(OBJDIR)");
if (!ObjDirName.size())
{
throw string("When making use of MHMAKECONF, you have to define OBJDIR in makefile.before");
}
pDepFile=GetFileInfo(ObjDirName+OSPATHSEPSTR "." + m_Makefile->GetName()+ ".dep",m_MakeDir);
m_pMakefileParser->SetVariable(AUTODEPFILE,pDepFile->GetQuotedFullFileName());
}
else
{
/* Create a file that is depending on the makefile name and the arguments */
md5_context ctx;
md5_starts( &ctx );
map::const_iterator pIt=m_CommandLineVars.begin();
while (pIt!=m_CommandLineVars.end())
{
if (pIt->first!="MAKE")
{
md5_update(&ctx, (uint8*)pIt->first.c_str(), pIt->first.size());
md5_update(&ctx, (uint8*)pIt->second.c_str(), pIt->second.size());
}
pIt++;
}
md5_update(&ctx, (uint8*)m_Makefile->GetFullFileName().c_str(), m_Makefile->GetFullFileName().size());
char ID[10];
sprintf(ID,"_%lx",md5_finish32( &ctx));
pDepFile=GetFileInfo(string(".") + m_Makefile->GetName()+ ".dep"+ID,m_MakeDir);
m_pMakefileParser->SetVariable(AUTODEPFILE,pDepFile->GetQuotedFullFileName());
}
if (pDepFile->Exists())
m_pMakefileParser->LoadAutoDepsFile(pDepFile); /* Already load this autodep file before parsing of the makefile to avoid needless rebuilds. */
//m_pMakefileParser->yydebug=1;
int result=m_pMakefileParser->ParseFile(m_Makefile,m_MakeDir);
if (result)
{
throw string("Error parsing ")+m_Makefile->GetQuotedFullFileName();
}
#ifdef _DEBUG
/* Check if the makefile has changed the AUTODEPFILE variable, if so generate a warning that a
* rebuild could happen for the rules defined for making included makefiles */
if (m_pMakefileParser->ExpandVar(AUTODEPFILE)!=pDepFile->GetQuotedFullFileName())
{
cout << "\n\nWARNING:\n makefile '"<< m_Makefile->GetQuotedFullFileName() <<"' re-defines AUTODEPFILE\n from '"<< pDepFile->GetQuotedFullFileName() <<"'\n to '"<<
m_pMakefileParser->ExpandVar(AUTODEPFILE) << "'\n (may cause needless rebuilds when having rules for included makefiles!!!!!)\n\n\n";
}
if (g_PrintAdditionalInfo)
{
if (m_pMakefileParser->GetFirstTarget())
cout<<"First target of "<GetQuotedFullFileName()<<" is "<GetFirstTarget()->GetQuotedFullFileName()<GetQuotedFullFileName()<UpdateDate(m_Makefile->GetDate());
if (sm_Statics.m_MhMakeConf)
{
fileinfo *pAfterMakefile=GetFileInfo(BaseAutoMak+".after",sm_Statics.m_MhMakeConf);
int result=m_pMakefileParser->ParseFile(pAfterMakefile);
if (result) {
throw string("Error parsing ")+pAfterMakefile->GetQuotedFullFileName();
}
m_pMakefileParser->UpdateDate(pAfterMakefile->GetDate());
}
bool ForceAutoDepRescan=g_ForceAutoDepRescan;
if (pDepFile->Exists())
m_pMakefileParser->LoadAutoDepsFile(pDepFile);
else
ForceAutoDepRescan=true;
if (ForceAutoDepRescan)
m_pMakefileParser->EnableAutoDepRescan();
vector &MakefilesToLoad=m_pMakefileParser->GetMakefilesToLoad();
vector::iterator It=MakefilesToLoad.begin();
while (It!=MakefilesToLoad.end())
{
// First split the command into arguments
const char *pTmp=It->c_str();
vector Args;
while (*pTmp)
{
string Item;
pTmp=NextItem(pTmp,Item);
Args.push_back(Item);
}
refptr pLoadedMakefile(new loadedmakefile(m_MakeDir,Args));
refptr Found=g_LoadedMakefiles.find(*pLoadedMakefile);
if (Found)
{
#ifdef _DEBUG
if (g_PrintAdditionalInfo)
cout << "Makefile already loaded: "<m_Makefile->GetQuotedFullFileName()<BuildTarget(pLoadedMakefile->m_Makefile);
pLoadedMakefile->LoadMakefile();
}
It++;
}
}
/*****************************************************************************/
bool MakeDirs(fileinfo *pDir)
{
fileinfo *pParentDir=pDir->GetDir();
if (!pParentDir->GetDate().DoesExist())
{ /* First make parent dirs */
if (!MakeDirs(pParentDir))
return false;
}
if (!pDir->GetDate().DoesExist())
{ /* Create directory */
if (-1==mkdir(pDir->GetFullFileName().c_str(),S_IRWXU))
{
cerr << "mkdir function failed for directory " << QuoteFileName(pDir->GetFullFileName()) << endl;
return false;
}
pDir->InvalidateDate(); // Directory created successfully, so invalidate the date
}
return true;
}
#ifdef _DEBUG
///////////////////////////////////////////////////////////////////////////////
void DumpVarsAndRules()
{
int i;
LOADEDMAKEFILES::iterator LoadMakIt=g_LoadedMakefiles.begin();
while (LoadMakIt!=g_LoadedMakefiles.end())
{
for (i=0; i<80; i++) cout << "_";
cout << endl;
cout << "Variables of makefile " << (*LoadMakIt)->m_Makefile->GetQuotedFullFileName() << endl;
for (i=0; i<80; i++) cout << "_";
cout << endl;
(*LoadMakIt)->m_pMakefileParser->PrintVariables(true);
cout << endl;
LoadMakIt++;
}
for (i=0; i<80; i++) cout << "_";
cout << endl;
cout << "All Rules\n";
for (i=0; i<80; i++) cout << "_";
cout << endl;
PrintFileInfos();
for (i=0; i<80; i++) cout << "_";
cout << endl;
cout << "All Implicit Rules\n";
for (i=0; i<80; i++) cout << "_";
cout << endl;
IMPLICITRULE::PrintImplicitRules();
}
#endif