/* 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 (pActiveEntry->CurrentCommandIt!=pRule->GetCommands().end()) { if (StartExecuteNextCommand(pActiveEntry, &ActiveProcess)) { pActiveEntry->CurrentCommandIt++; } 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(); } } }