/* 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 "commandqueue.h"
#include "mhmakefileparser.h"
#ifndef WIN32
#define INFINITE 0
#define FALSE 0
static int Status;
unsigned WaitForMultipleObjects(int Nbr, mh_pid_t *phProcesses, int, int)
{
mh_pid_t ID=waitpid(0,&Status,0);
for (int i=0; i[m_MaxNrCommandsInParallel];
}
commandqueue::~commandqueue()
{
delete [] m_pActiveProcesses;
delete [] m_pActiveEntries;
#ifdef WIN32
CloseHandle(m_DummyWaitHandle);
#endif
}
void commandqueue::SetNrParallelBuilds(unsigned NrParallelBuilds)
{
if (m_NrActiveEntries)
throw("Changing of number of parallel builds is only allowed when no commands are executing");
NrParallelBuilds=max(1u,NrParallelBuilds);
m_MaxNrCommandsInParallel=NrParallelBuilds;
delete [] m_pActiveProcesses;
delete [] m_pActiveEntries;
m_pActiveProcesses=new mh_pid_t[NrParallelBuilds];
m_pActiveEntries= new refptr[NrParallelBuilds];
}
void commandqueue::ThrowCommandExecutionError(refptr pActiveEntry)
{
fileinfo* pTarget=pActiveEntry->pTarget;
const string &Command=pActiveEntry->Command;
mhmakefileparser *pMakefile=pTarget->GetRule()->GetMakefile();
string ErrorMessage = string("Error running command: ")+ Command +"\n";
ErrorMessage += "Command defined in makefile: " + pMakefile->GetMakeDir()->GetQuotedFullFileName();
pTarget->SetCommandsMd5_32(0); /* Clear the md5 to make sure that the target is rebuild the next time mhmake is ran */
pMakefile->SetAutoDepsDirty(); /* We need to update the autodeps file if the md5 has been changed */
throw ErrorMessage;
}
refptr commandqueue::CreateActiveEntry(void)
{
refptr pRet=new activeentry;
m_pActiveEntries[m_NrActiveEntries]=pRet;
m_pActiveProcesses[m_NrActiveEntries]=this->m_DummyWaitHandle;
m_NrActiveEntries++;
return pRet;
}
unsigned commandqueue::GetActiveEntryId(const refptr pActiveEntry) const
{
unsigned i=0;
for (i=0; ipTarget->GetFullFileName());
}
void commandqueue::RemoveActiveEntry(unsigned Entry)
{
//cout << "Remove entry "<GetQuotedFullFileName()<<<<" ("< pActiveEntry, mh_pid_t *pActiveProcess)
{
fileinfo* pTarget=pActiveEntry->pTarget;
mhmakefileparser *pMakefile=pTarget->GetRule()->GetMakefile();
pMakefile->SetRuleThatIsBuild(pTarget); // Make sure that the command expension is correct
string Command=pMakefile->ExpandExpression(*pActiveEntry->CurrentCommandIt);
pMakefile->ClearRuleThatIsBuild(); /* Make sure that further expansion is not taking this rule into account.*/
md5_update( &pActiveEntry->md5ctx, (uint8 *)Command.c_str(), (unsigned long)Command.size());
pActiveEntry->Command=Command;
#ifdef _DEBUG
if (g_pPrintDependencyCheck)
{
cout<<"-> "<ExecuteCommand(Command,pActiveEntry->IgnoreError);
if (hProcess==(mh_pid_t)-1)
{
ThrowCommandExecutionError(pActiveEntry);
}
else if (!hProcess)
{
// Command already finished, so execute next command
return true;
}
else
{
// Command still executing, so add it to the list of handles to wait for
*pActiveProcess=hProcess;
return false;
}
#ifdef _DEBUG
}
#endif
return true;
}
void commandqueue::TargetBuildFinished(refptr pActiveEntry)
{
fileinfo* pTarget=pActiveEntry->pTarget;
// Building of this target finished
uint32 Md5_32=md5_finish32( &pActiveEntry->md5ctx);
#ifdef _DEBUG
if (g_DoNotExecute||g_GenProjectTree)
pTarget->SetDateToNow();
else
#endif
pTarget->InvalidateDate();
pTarget->SetCommandsMd5_32(Md5_32); /* If the rule of the target was added with an implicit rule the targets in the rule is empty */
refptr pRule=pTarget->GetRule();
mhmakefileparser *pMakefile=pRule->GetMakefile();
pMakefile->AddTarget(pTarget);
pMakefile->SetAutoDepsDirty();
pRule->SetTargetsIsBuild(Md5_32);
#ifdef _DEBUG
if (g_pPrintDependencyCheck)
{
cout<<"Building "<GetQuotedFullFileName()<<" finished : "<< pTarget->GetDate() << endl;
}
if (!pMakefile->ImplicitSearch() && !pTarget->Exists() && !pTarget->IsPhony() && !g_DoNotExecute && !g_GenProjectTree)
{
// This is only a warning for phony messages
cout<<"Warning: don't know how to make "<GetQuotedFullFileName()<<"\nMake the rule a phony rule to avoid this warning (but only do it when it is really phony).\n";;
}
#endif
pTarget->ClearBuilding();
}
/* Start executing the commands of a target
*/
bool commandqueue::StartExecuteCommands(fileinfo* pTarget)
{
cout << "Building " << pTarget->GetQuotedFullFileName()< pRule=pTarget->GetRule();
mhmakefileparser *pMakefile=pRule->GetMakefile();
vector::iterator CommandIt=pRule->GetCommands().begin();
refptr pActiveEntry=CreateActiveEntry();
mh_pid_t ActiveProcess;
md5_starts( &pActiveEntry->md5ctx );
pActiveEntry->pTarget=pTarget;
pActiveEntry->CurrentCommandIt=CommandIt;
while (1)
{
if (StartExecuteNextCommand(pActiveEntry, &ActiveProcess))
{
pActiveEntry->CurrentCommandIt++;
if (pActiveEntry->CurrentCommandIt==pRule->GetCommands().end())
{
// All commands executed
break;
}
}
else
{
m_pActiveProcesses[GetActiveEntryId(pActiveEntry)]=ActiveProcess; // We use GetActiveEntryId to avoid reentrancy problems
return false;
}
}
TargetBuildFinished(pActiveEntry);
RemoveActiveEntry(pActiveEntry);
return true;
}
/* put the target in the execution queue or start executing immediately
*/
bool commandqueue::QueueTarget(fileinfo* pTarget)
{
pTarget->SetBuilding();
// First check if there is place in the active entries
if (m_NrActiveEntries==m_MaxNrCommandsInParallel)
{
// commands cannot be started yet
m_Queue.push(pTarget);
return true;
}
else
{
return !StartExecuteCommands(pTarget);
}
}
/* Wait for all the commands being executed of a target. In the mean time also continue
executing all other commands in the queue
*/
mh_time_t commandqueue::WaitForTarget(fileinfo *pTarget)
{
if (!pTarget->IsBuilding())
return pTarget->GetDate();
while (1)
{
// First wait until one of the processes that are running is finished
if (m_NrActiveEntries==1 && m_pActiveProcesses[0]==this->m_DummyWaitHandle)
{
#ifdef _DEBUG
if (pTarget!=m_pActiveEntries[0]->pTarget)
throw("Wrong assumption: waiting for target " + pTarget->GetFullFileName() + " but in wait list is " + m_pActiveEntries[0]->pTarget->GetFullFileName());
#endif
pTarget->SetDateToNow();
return pTarget->GetDate(); // This is a reentrancy problem, assume that the target is just build
}
unsigned Ret=WaitForMultipleObjects(m_NrActiveEntries,m_pActiveProcesses,FALSE,INFINITE);
if (Ret>=m_NrActiveEntries)
#ifdef WIN32
throw("fatal error: unexpected return value of WaitForMultipleObjects " + stringify(Ret) + " " + stringify(GetLastError()));
#else
throw("fatal error: unexpected return value of WaitForMultipleObjects " + stringify(Ret));
#endif
refptr pActiveEntry=m_pActiveEntries[Ret];
fileinfo* pCurrentTarget=pActiveEntry->pTarget;
refptr pRule=pCurrentTarget->GetRule();
// First check the error code of the command
DWORD ExitCode=0;
mh_pid_t hProcess=m_pActiveProcesses[Ret];
if (!GetExitCodeProcess(hProcess,&ExitCode) || ExitCode)
{
if (pActiveEntry->IgnoreError)
{
cerr << "Error running command: "<Command<<", but ignoring error\n";
}
else
ThrowCommandExecutionError(pActiveEntry);
}
CloseHandle(hProcess);
// Now check if we have to execute more commands for this target
pActiveEntry->CurrentCommandIt++;
while (pActiveEntry->CurrentCommandIt!=pRule->GetCommands().end())
{
if (!StartExecuteNextCommand(pActiveEntry, &m_pActiveProcesses[Ret]))
break; // We have to wait for end of command execution
pActiveEntry->CurrentCommandIt++;
}
if (pActiveEntry->CurrentCommandIt==pRule->GetCommands().end())
{
TargetBuildFinished(pActiveEntry);
// Target building finished, so remove it from the list of active entries
RemoveActiveEntry(Ret);
bool Return=false;
if (pTarget==pCurrentTarget)
{
Return = true;
}
// Check if we have other entries in the queue to start
while (1)
{
if (m_Queue.empty())
{
// There should still be active entries, otherwise this is a serious bug
if (!m_NrActiveEntries && !Return)
{
// This may happen when having multiple target rules and pTarget was build when on of the other targets
// was build. so first check if the pTarget was not build in the mean time. If so, this is not an error
if (pTarget->IsBuild())
{
Return=true;
break;
}
else
throw("Fatal error: WaitForTarget "+pTarget->GetQuotedFullFileName()+": no active targets anymore.");
}
else
break;
}
else
{
fileinfo* pNewTarget=m_Queue.front();
m_Queue.pop();
if (StartExecuteCommands(pNewTarget))
{
if (pNewTarget==pTarget)
Return=true;
}
else
break;
}
}
if (Return)
return pTarget->GetDate();
}
}
}