// (c) 2001-2007 Electronic Arts Inc.
///////////////////////////////////////////////////////////////////////////////
// Filename        : cl_dual.cpp
// Content         : Dual-processor compilation support for VC++ 6.0.
// Date Created    : June 16, 2001
// Authors         : Michael Antonov, Brendan Iribe, Paul Pedriana.
// Current Version : 1.1, 12/8/2001. Built by Paul Pedriana.
// History         : Paul Pedriana -- Created v1.1.
//                      - Converted code from C to C++.
//                      - Added lots of comments to the code.
//                      - Added support for > 2 CPUs.
//                      - Added support for precompiled headers.
//                      - Added support for drop-in compilers such as Intel C++.
//                      - Added ability to call start debugger from this app.
//                      - Added other miscellaneous features.
//
// This program is copyrighted and should not be redistributed without
// the proper written permission.  Please contact Brendan Iribe if you
// are interested in distributing either the binary or source of this
// program.  Furthermore, if any modifications are made to this file
// please send a copy of the new source to Brendan Iribe.
//
// Brendan Iribe - biribe@sonicfusion.com
///////////////////////////////////////////////////////////////////////////////



///////////////////////////////////////////////////////////////////////////////
// Some Documentation
//
// When the VC++ IDE builds a library (defined by a .dsp file), it does it by
// calling the standard Microsoft command line compiler (cl.exe) one or more
// times to compile the files specified by the library. To be efficient, it
// will attempt to have the compiler compile multiple source files in a single
// execution of cl.exe. Instead of passing a super long command line to the
// cl.exe program, it passes the path of a file that holds the command line
// to use. It does it by like this: "cl.exe @C:\TempDir\SomeTempFile.tmp".
// The IDE intercepts printf statements made by cl.exe and echos them to
// its own output display. The IDE waits for the cl.exe program to finish
// and if cl.exe reports no errors and returns 0, the IDE tries to compile
// more files or tries to link.
//
// What we do is take the list of files to compile and split up the compilation
// task between multiple CPUs via multiple processes (one for each CPU).
// If there are two CPUs, then we create two processes and split the files
// to compile evenly between the two processes.
//
// There are some issues we have to watch out for. First of all, when compiling
// with debugging information, the compiler writes debug data to a pair of
// files: vc60.idb and vc60.pdb. The problem is that you can only write to
// these files from one process at a time and there are no coordination
// provisions. To deal with this, we create a subdirectory for each extra
// process (underneath wherever the idb/pdb files are specified) and have the
// compiler write to idb/pdb files there. The .obj files go where they normally
// would. The .obj files have the path to these idb/pdb files written into them
// so at debug time these extra idb/pdb files will get used as desired.
//
// Precompiled headers present an additional issue to deal with. If you have a
// library with 20 .cpp files and they use some .pch file, the IDE will call
// cl.exe with the 'create PCH file' directive. We need to be concerned because
// the compilation of future files will expect that the PCH file data be present
// in both the .pch file and the idb/pdb files. As a result, when we get a
// request to compile a single file to a .pch file, we need to compile that
// file once for each processor. This will result in us making a .pch file
// in the subdirectory for each processor in addition to the idb/pdb pair.
//
// As of this writing (8/2001), I (Paul Pedriana) have found that for some
// unknown reason, occasionally the created PCH files will somehow be made
// or seen as corrupt by the normal cl.exe compiler program. I am not sure
// why, as the different .pch/idb/pdb files are written by different processes.
// But there is no doubt some cause for this. Since the problem happens only
// occasionally, it suggests some kind of race condition is at hand. Perhaps
// Microsoft's cl.exe doesn't really expect to be called on a project more
// than once at a time and it has some collision between processes. On the
// other hand, perhaps there is a bug in this program or an assumtion that
// it is making that is incorrect.
//
// Debugging Notes:
//   - Turn on the keyboard scroll lock key to have the debug build of this
//     program generate an exception that will allow you to debug it.
//   - For ease of debugging C++ strings, don't forget to modify your VC++
//     'Program Files/Microsoft Visual Studio/Common/MSDev98/Bin/Autoexp.dat'
//     file to add this line: 'std::basic_string<*>=<_Ptr>'. This will allow
//     you to hold the cursor over string variables and view their contents.
//
///////////////////////////////////////////////////////////////////////////////
// Example compilation directive file text (e.g. C:\TempDir\SomeTempFile.tmp)
//
// Example compilation directive file without precompiled header usage:
//    /nologo /MTd /W3 /Gm /GX /ZI /Od /I "." /I "..\..\GZFrame\Rizzo\author" /I "..\..\GZFrame\GZInterface" /I "..\..\GZFrame\GZInterface\Win32" /I "..\..\GZFrame\Rizzo\h" /I "..\..\GZFrame\Rizzo\stl" /I "..\..\GZFrame\Rizzo\RizzoWin32\h" /D "_DEBUG" /D "MASTER_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_CONSOLE" /D "NOMINMAX" /D "W95DX" /D "_GZNOFRAME" /D "_GZ_NO_SYSTEM_SERVICES" /D _WIN32_WINNT=0x0400 /D "USE_SGISTLV3" /Fo"Debug/" /Fd"Debug/" /FD /c
//    "C:\Development\SC4000Projects\Utils\ConsoleApp\Main.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\source\RZCmdLine.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\source\RZFile.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\source\RZRandom.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\source\RZStream.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\source\RZString.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\RizzoWin32\source\RZTimerW95.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\source\RZXError.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\source\RZXGlobal.cpp"
//    "C:\Development\SC4000Projects\GZFrame\Rizzo\source\RZXSTL.cpp"
//
// Example compilation directive file with precompiled header usage:
//    /nologo /MTd /W3 /Gi /GX /Zi /Od /Gy /I ".\author" /I ".\h" /I "..\gzinterface" /I ".\stl" /D "MASTER_DEBUG" /D "_DEBUG" /D "W95DX" /D "NOMINMAX" /D "WIN32" /D "_WINDOWS" /D "_WIN32" /Fp"..\..\obj\DebugSRT\Rizzo/Rizzo.pch" /Yc"rizzo.h" /Fo"..\..\obj\DebugSRT\Rizzo/" /Fd"..\..\obj\DebugSRT\Rizzo/" /FD /Zm1000 /GZ /c
//    "H:\SC4000Projects\GZFrame\Rizzo\source\Rizzo.cpp"
//
///////////////////////////////////////////////////////////////////////////////



///////////////////////////////////////////////////////////////////////////////
// Compiler shutups
//
#if defined(_MSC_VER) // Microsoft compiler
   #pragma warning(disable: 4786) // 4786 identifier was truncated to 255 characters in the debug information
#endif



///////////////////////////////////////////////////////////////////////////////
// Include files
//
#include <windows.h>
#include <conio.h>
#include <stdio.h>
#include <direct.h>
#include <time.h>
#include <string>
#include <vector>
#include <assert.h>



///////////////////////////////////////////////////////////////////////////////
// ProcessData
//
struct ProcessData{
   HANDLE              hReadPipe;
   HANDLE              hWritePipe;
   PROCESS_INFORMATION processInfo;
   BOOL                bStillActive;
   DWORD               nReturnValue;

   ProcessData() : hReadPipe(0), hWritePipe(0), bStillActive(true), nReturnValue(0) {
      ::memset(&processInfo, 0, sizeof(processInfo));
   }
};



///////////////////////////////////////////////////////////////////////////////
// ReadLine
//
// Reads a line from the source and starting offset to the destination.
// Copies the '\n' to the destination. If the source has no '\n', then the
// entire source is copied without the ending '\n' and no '\n' is added
// to the destination.
//
unsigned ReadLine(const std::string& source, int nSourceOffset, std::string& dest){
   dest.erase();
   for(unsigned i(nSourceOffset), iEnd(source.length()); (i < iEnd) && source[i]; i++){
      dest += source[i];
      if(source[i] == '\n')
         break;
   }
   return dest.length();
}


///////////////////////////////////////////////////////////////////////////////
// MoveLine
//
// Moves a line (delimited by '\n') from the source to the destination.
// Moves the '\n' to the destination. If the source has no '\n', then the
// entire source is moved without the ending '\n' and no '\n' is added
// to the destination.
//
bool MoveLine(std::string& source, std::string& dest){
   dest.erase();
   for(unsigned i(0), iEnd(source.length()); (i < iEnd) && source[i]; i++){
      dest += source[i];
      if(source[i] == '\n')
         break;
   }
   source.erase(0, dest.length());
   return dest.length() != 0;
}



///////////////////////////////////////////////////////////////////////////////
// main
//
int main(int argc, char* argv[], char *envp[]){
   int  i, iEnd; //We'll be using this as a loop counter in various places.
   bool bBeingDebugged(false);

   //Possibly wait so a debugger can be attached.
   #ifdef _DEBUG
      if(::GetKeyState(VK_SCROLL) & 0x0001){
         bBeingDebugged = true;
         printf("Scroll lock key is down in debug build. Intentionally crashing this program so we can attach a debugger.\n");
         fflush(stdout);

         //Generate a crash; we'll have the debugger take over from here.
         *((int*)0x00000000) = 0;

         //Wait for the user to attach a debugger.
         //printf("Scroll lock key is down. Waiting (at most 30 seconds) for a debugger to be attached...\n");
         //fflush(stdout);
         //const int nTimeToGiveUp(time(NULL)+30);
         //while(time(NULL) < nTimeToGiveUp)
         //   ::SleepEx(200, true);
      }
   #endif

   //Print the input arguments to this program.
   //for(i=0; i<argc; i++){
   //   printf("%s\n", argv[i]);

   //Print the environment strings.
   //This is disabled because normally it spits out 30 or more lines which aren't useful.
   //printf("Environment:\n");
   //for(i=0; envp[i]; i++)
   //   printf("%d: %s\n",i,envp[i]);
   //fflush(stdout);

   //Print the current working direction for this program.
   std::string sCurrentWorkingDirectory(256, 0);
   getcwd((char*)sCurrentWorkingDirectory.data(), 255);
   //printf("Current working directory: %s\n", sCurrentWorkingDirectory.c_str());


   //Possibly print out help. We do this if there is no parameters file passed to this program.
   bool bPrintHelp(true);
   for(i=0; i<argc; i++){
      if(argv[i][0] == '@'){ //VC++ IDE passes a command line arg with this as a prefix.
         bPrintHelp = false;
         break;
      }
   }
   if(bPrintHelp){
      const char* pHelp = "VC++ Dual Compiler Mod, v1.1.\n"
                          "Copyright (c) 2001 SonicFusion. All Rights Reserved.\n"
                          "\n"
                          "This program is not endorsed or supported by Microsoft in any way.\n"
                          "It has not been tested thoroughly and at this point occasionally seems\n"
                          "to mess up with precompiled headers. We're looking into this.\n"
                          "\n"
                          "To use this program, save a copy of the standard Microsoft cl.exe\n"
                          "program as cl_normal.exe and put a copy of this program in its place.\n"
                          "Alternatively, if using another compiler such as Intel's xicl.exe\n"
                          "you save a copy of it as xicl_normal.exe and put a copy of this program\n"
                          "in its place. For flexibility, it's easiest if you write a batch file\n"
                          "to do the work of saving and restoring these settings.\n"
                          "\n"
                          "The debug build of this mod has the feature whereby it will intentionally\n"
                          "cause an exception so a debugger can be started on it. Also, the debug\n"
                          "build will pretend there are two processors even if there is only one.\n\n";

      printf("%s", pHelp);
      fflush(stdout);
      printf("Press the return key to exit...\n");
      getchar();
      return 1;
   }

   //Store the path to this program and its .ini file in a string.
   std::string sCurrentAppPath(argv[0]);                 //The current path to the current application.
   std::string sCurrentAppName;                          //The file name alone of the current application. Will not hold the extension. e.g. "cl" (referring to Microsoft's cl.exe).
   std::string sCurrentAppIniFilePath(sCurrentAppPath);  //The app ini file path.
   std::string sCurrentOutputIniFilePath;                //The ini file we put in the current output directory for data specific to it.
   while(sCurrentAppIniFilePath.length() &&
         sCurrentAppIniFilePath[sCurrentAppIniFilePath.length()-1] != '\\' &&
         sCurrentAppIniFilePath[sCurrentAppIniFilePath.length()-1] != '/')
   {
      sCurrentAppName.insert(0, sCurrentAppIniFilePath[sCurrentAppIniFilePath.length()-1]);
      sCurrentAppIniFilePath.resize(sCurrentAppIniFilePath.length()-1);
   }
   while(sCurrentAppName.length() && sCurrentAppName[sCurrentAppName.length()-1] != '.')
      sCurrentAppName.resize(sCurrentAppName.length()-1);
   sCurrentAppName.resize(sCurrentAppName.length()-1);
   sCurrentAppIniFilePath += sCurrentAppName;
   sCurrentAppIniFilePath += ".ini";
   //<Code to get the current output ini file path here. Look for the '/Fo' compiler argument.>

   //Get the processor count for this machine. While at it, get the
   //processor count for the last time this app was executed. It is possible
   //that a hard drive with a build on it is being moved from a single to a
   //dual processing machine. We detect this and warn the user to do a deletion
   //of obj files if so.
   SYSTEM_INFO systemInfo;
   ::GetSystemInfo(&systemInfo);
   unsigned nProcessorCount(systemInfo.dwNumberOfProcessors);
   unsigned nProcessorCountToUse(nProcessorCount);
   #ifdef _DEBUG
      if(nProcessorCountToUse < 2)
         nProcessorCountToUse = 2;
   #endif

   //Put a trace to notify the user.
   printf("VC++ Dual Compiler Mod active. %u processor(s) present.\n", nProcessorCount);
   fflush(stdout);

   //We read the processor count of the previous run by checking our .ini file.
   //We don't use the Win32 GetPrivateProfileString or GetPrivateProfileInt
   //functions because they are -pig slow- (I have personally verified this).
   unsigned nProcessorCountOfPreviousRun(nProcessorCountToUse);
   //<code to read .ini file data here>

   //Check to see if the processor count has changed from the last run. If so, then
   //possibly warn the user of the operation.
   if(nProcessorCountToUse != nProcessorCountOfPreviousRun){
      //printf("Processor count has changed since last run. Please clear all affected compiled files.\n");
      //<code to write out new .ini file data here>
   }


   //Get the parameters file path. This will be an argument passed to this app that gives
   //a path to a file to use that contains the text of the compilation commands/options.
   //Under VC++, this is normally a temp file created by the IDE.
   std::string sParamatersFilePath;
   for(i=0; i<argc; i++){
      if(argv[i][0] == '@'){ //VC++ IDE passes a command line arg with this as a prefix.
         sParamatersFilePath = argv[i] + 1;
         //printf("Parameters file: \"%s\"\n", sParamatersFilePath.c_str());
         break;
      }
   }


   //Try opening the parameters file.
   FILE* pParametersFile = NULL;
   if(sParamatersFilePath.length())
      pParametersFile = fopen(sParamatersFilePath.c_str(), "r");
   if(!pParametersFile){
      printf("Cannot open temporary parameter file.\n");
      return 1;
   }

   //Read the data from the parameters file into a string.
   //The parameters file should have one (potentially long) line of compile command line
   //options and then a list of one or more files to apply that command line to.
   std::string              sParametersFileData;
   std::string              sCompileCommandLine;
   std::vector<std::string> compileFilesStringArray;

   fseek(pParametersFile, 0, SEEK_END);
   const int nFileLength = ftell(pParametersFile);
   fseek(pParametersFile, 0, SEEK_SET);
   sParametersFileData.resize(nFileLength, 0);
   fread(&sParametersFileData[0], nFileLength, 1, pParametersFile);
   ::MoveLine(sParametersFileData, sCompileCommandLine);
   while(true){
      std::string sTemp;
      if(::MoveLine(sParametersFileData, sTemp))
         compileFilesStringArray.push_back(sTemp);
      else
         break;
   }
   fclose(pParametersFile);


   //Check to see if we are doing precompiled header (PCH) compilation here.
   const bool bPCHCompilation(sCompileCommandLine.find("/Yc") < sCompileCommandLine.length());

   //If there is only one processor, then there is no way to take advantage of multiple
   //CPUs. If there is only one file to compile, then there is no way to take advantage
   //of multiple CPUs. However, if we are being requested to make a precompiled header,
   //then we'll need to do the precompiled header compilation twice, once for each
   //output PDB (VC++ project database) file.
   std::vector<std::string> compilationFilePathArray;

   if(bPCHCompilation && (compileFilesStringArray.size() != 1)){
      assert(false);
      printf("Problem doing compilation. PCH is being compiled from multiple sources.\n");
      return 1;
   }

   if((compileFilesStringArray.size() <= 1) && !bPCHCompilation){
      //In this case we simply compile as normal with the original compile settings.
      nProcessorCountToUse = 1;
      compilationFilePathArray.push_back(sParamatersFilePath);
   }
   else{ //Else we will be doing dual processor compilations.
      //What we want to do here is make multiple independent and different compilation
      //directive files for each processor. We first write the compilation directive line
      //into each of the files. Then we split up the compilation file name lines evenly
      //between the processors.

      //There is an issue here. If the list of files to compile changes from one day to
      //the next, the files will get compiled by different CPUs than previously. This is
      //a problem because each CPU gets its own .PDB file and we can't have a file's obj
      //data in two .PDB files at the same time; it can potentially confuse the debugger
      //or linker. I (Paul Pedriana) am not yet sure of this but believe I saw it.
      if(bPCHCompilation){
         //In this case we've only been requested to compile one file but we have
         //multiple processors at hand. We need to compile the PCH file for as many
         //processors as we have. The reason for this is that the next time we're going
         //to be run we will be compiling multiple files that use this PCH data.
         //The issue is that when we compile the PCH file, its information goes into
         //the .pdb file. There is one .pdb file for each procesor, so we need to
         //replicate this .pdb data for each processor. I turns out that when you
         //build a precompiled header file, you need to rebuild all the other files
         //that use it, so you can simply delete the .pdb files that the extra
         //processors use and copy the main one to the others. But alas this only
         //works if -all- data stored in the .pdb file comes from .cpp files that
         //used precompiled headers. The only solution I can think of in the meantime
         //is to simply multiply compile the precompiled header file, which could be slow.

         //<code to deal with this here>
      }
      else{
         if(compileFilesStringArray.size() < nProcessorCountToUse)   //No need to use three processors
            nProcessorCountToUse = compileFilesStringArray.size();   //to compile two files!
      }

      std::vector<FILE*> compilationFileArray;

      for(i=0; i < nProcessorCountToUse; i++){
         std::string sParametersFilePathTemp(sParamatersFilePath);
         sParametersFilePathTemp += '_';
         sParametersFilePathTemp += (char)('0'+i);
         compilationFilePathArray.push_back(sParametersFilePathTemp);
         compilationFileArray.push_back(NULL);
         compilationFileArray[i] = fopen(sParametersFilePathTemp.c_str(), "w");
         if(compilationFileArray[i]){
            if(i == 0){
               //For the first CPU, we use the original command line just like
               //it was specified. For later CPUs, we modify it.
               fwrite(sCompileCommandLine.data(), sCompileCommandLine.length(), 1, compilationFileArray[i]);
            }
            else{
               //What we do here is take a command string that looks like this:
               //    /MTd /W3 /Gm /GX /Fo"Debug/" /Fd"Debug/" /FD /c
               //and change it to look like this:
               //    /MTd /W3 /Gm /GX /Fo"Debug/CPU 1/" /Fd"Debug/CPU 1/" /FD /c
               //
               std::string sCurrentCPUDirectoryName("CPU ");
               sCurrentCPUDirectoryName += (char)('0'+i);

               //The '/Fo' option specifies where to write the object (.obj) files.
               //We leave the object file destination specification just like it is.

               //The '/Fd' option specifies where to write the project database (.pdb) file.
               //Since we are compiling with multiple processes in parallel and the .pdb file system
               //is apparently not multi-thread and multi-process safe, we specify extra .pdb
               //files to be used by our other processes.
               unsigned nDatabaseDirectoryStartIndex(sCompileCommandLine.find("/Fd\""));
               if(nDatabaseDirectoryStartIndex < sCompileCommandLine.length()){
                  nDatabaseDirectoryStartIndex += 4; //Move it to past the '/Fd"' part.
                  const unsigned nDatabaseDirectoryEndIndex(sCompileCommandLine.find("/\"", nDatabaseDirectoryStartIndex)); //We start with something like this:  /MTd /W3 /Gm /GX /Fd"Debug/" /FD /c
                  std::string sCurrentDatabaseDirectoryPath(sCompileCommandLine.data(), nDatabaseDirectoryStartIndex, nDatabaseDirectoryEndIndex-nDatabaseDirectoryStartIndex);
                  sCompileCommandLine.insert(nDatabaseDirectoryEndIndex+1, "/");                         //Now this string will be something like: /MTd /W3 /Gm /GX /Fd"Debug//" /FD /c
                  sCompileCommandLine.insert(nDatabaseDirectoryEndIndex+1,  sCurrentCPUDirectoryName);   //Now this string will be something like: /MTd /W3 /Gm /GX /Fd"Debug/CPU 1/" /FD /c

                  //Here we make sure the given subdirectory we reference above exists.
                  ::chdir(sCurrentDatabaseDirectoryPath.c_str());
                  ::mkdir(sCurrentCPUDirectoryName.c_str());
                  ::chdir(sCurrentWorkingDirectory.c_str());
               }

               //The '/Fp' option specifies where to write the .pch file. We will not see
               //this option unless the '/Fu' option is also present. We need to direct this
               //.pch file to go to the same place we put the .pdb file, which is specified
               //by the '/Fd' option. The directory for the .pch file specified by the '/Fp'
               //option will generally (always?) be the same the as the directory specified
               //by the '/Fd' option.
               unsigned nPCHPathStartIndex(sCompileCommandLine.find("/Fp\""));
               if(nPCHPathStartIndex < sCompileCommandLine.length()){
                  nPCHPathStartIndex += 4; //Move it to past the '/Fp"' part.
                  const unsigned nPCHPathEndIndex(sCompileCommandLine.find(".pch\"", nPCHPathStartIndex)); //We start with something like this:  /MTd /W3 /Gm /GX /Fp"Debug/File.pch" /FD /c
                  const unsigned nPCHDirectoryEndIndex(sCompileCommandLine.rfind("/", nPCHPathEndIndex));
                  assert(nPCHDirectoryEndIndex > nPCHPathStartIndex);
                  sCompileCommandLine.insert(nPCHDirectoryEndIndex+1, "/");                           //Now this string will be something like:  /MTd /W3 /Gm /GX /Fp"Debug//File.pch" /FD /c
                  sCompileCommandLine.insert(nPCHDirectoryEndIndex+1,  sCurrentCPUDirectoryName);     //Now this string will be something like:  /MTd /W3 /Gm /GX /Fp"Debug/CPU 1/File.pch" /FD /c
               }

               //Finally we write the modfied command line to the compile file.
               fwrite(sCompileCommandLine.data(), sCompileCommandLine.length(), 1, compilationFileArray[i]);
            }
         }
      }

      //Write the file list into the compilation file.
      if(bPCHCompilation){
         //In this case, we compile the same PCH file once for each processor.
         assert(compileFilesStringArray.size() == 1);
         for(i=0, iEnd=compilationFileArray.size(); i < iEnd; i++)
            fwrite(compileFilesStringArray[0].data(), compileFilesStringArray[0].length(), 1, compilationFileArray[i]);
      }
      else{
         //In this case we spread the multiple files between the various processors.
         for(i=0, iEnd=compileFilesStringArray.size(); i < iEnd; i++){
            const unsigned nFileToWriteTo(i % nProcessorCountToUse);
            if(compilationFileArray[nFileToWriteTo])
               fwrite(compileFilesStringArray[i].data(), compileFilesStringArray[i].length(), 1, compilationFileArray[nFileToWriteTo]);
         }
      }

      //Close the compilation directive files.
      for(i=0; i < nProcessorCountToUse; i++){
         if(compilationFileArray[i]){
            fclose(compilationFileArray[i]);
            compilationFileArray[i] = NULL;
         }
      }
   }


   //For each CPU, create pipes and start processes.
   //Create an array of ProcessData for the processes we will be creating.
   std::vector<ProcessData> processDataArray;
   processDataArray.resize(compilationFilePathArray.size());

   for(i=0, iEnd=compilationFilePathArray.size(); i < iEnd; i++){
      HANDLE hSaveStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);

      SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE };
      STARTUPINFO si;
      memset(&si, 0, sizeof(si));
      si.cb = sizeof(si);

      ::CreatePipe(&processDataArray[i].hReadPipe, &processDataArray[i].hWritePipe, &sa, 0);

      std::string sCommandLine(sCurrentAppName);   //So this is now something like: "cl"
      sCommandLine += "_normal.exe @";             //So this is now something like: "cl_normal.exe @"
      sCommandLine += compilationFilePathArray[i]; //So this is now something like: "cl_normal.exe @C:\temp\blah.tmp"
      ::CreateProcess(NULL, (char*)sCommandLine.c_str(), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &processDataArray[i].processInfo);

      ::SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdOut);
      ::CloseHandle(processDataArray[i].hWritePipe);
      processDataArray[i].hWritePipe = 0;
   }


   //We now have one or more processes running. What we do here is wait for the proceses
   //to finish. While doing this, we echo the process output to our own output.
   std::string sProcessOutputText(4000, 0);
   std::vector<HANDLE> processHandleArray(processDataArray.size(), 0);
   for(i=0, iEnd=processHandleArray.size(); i < iEnd; i++)
      processHandleArray[i] = processDataArray[i].processInfo.hProcess;

   while(!processHandleArray.empty()){
      DWORD dwWaitTimeout(100);
      DWORD dwWaitResult(::WaitForMultipleObjects(processHandleArray.size(), &processHandleArray[0], 0, dwWaitTimeout));

      if(dwWaitResult != WAIT_TIMEOUT){ //It will be WAIT_OBJECT_0+n or WAIT_ABANDONED_0+n.
         unsigned nIndex(dwWaitResult-WAIT_OBJECT_0);

         if(nIndex < processHandleArray.size()){ //This should always be true.
            for(i=0, iEnd=processDataArray.size(); i < iEnd; i++){
               if(processDataArray[i].processInfo.hProcess == processHandleArray[nIndex]){
                  processDataArray[i].bStillActive = false;
                  ::GetExitCodeProcess(processDataArray[i].processInfo.hProcess, &processDataArray[i].nReturnValue);
                  ::CloseHandle(processDataArray[i].processInfo.hProcess);
                  ::CloseHandle(processDataArray[i].processInfo.hThread);
               }
            }
            processHandleArray.erase(&processHandleArray[nIndex]);
         }
      }

      for(i=0, iEnd=processDataArray.size(); i < iEnd; i++){
         if(processDataArray[i].bStillActive){
            DWORD dwReadSize(0);
            if(::PeekNamedPipe(processDataArray[i].hReadPipe,0,0,0,&dwReadSize,0) && dwReadSize){
               if(dwReadSize > sProcessOutputText.length())
                  sProcessOutputText.resize(dwReadSize*2);
               ::ReadFile(processDataArray[i].hReadPipe, (char*)&sProcessOutputText[0], dwReadSize, &dwReadSize, NULL);
               sProcessOutputText[dwReadSize] = 0;
               fputs(sProcessOutputText.data(), stdout);
               fflush(stdout);
            }
         }
      }
   }

   //Delete any temporary files we've created. Make sure we don't delete
   //the temp parameters file that was passed to us.
   for(i=0, iEnd=compilationFilePathArray.size(); i < iEnd; i++){
      if(compilationFilePathArray[i] != sParamatersFilePath)
         ::unlink(compilationFilePathArray[i].c_str());
   }

   //Calculate our return value.
   unsigned nReturnValue(0);
   for(i=0, iEnd=processDataArray.size(); i < iEnd; i++)
      nReturnValue += processDataArray[i].nReturnValue;
   return nReturnValue;
}













