From 567edf0e9a8f4dcaa82ceb0ae6a586867e3687f5 Mon Sep 17 00:00:00 2001 From: Bill Hoffman Date: Thu, 14 Mar 2002 11:11:39 -0500 Subject: ENH: overhaul of RunCommand on windows, if only win32 had popen... --- Source/cmConfigureGccXmlCommand.cxx | 2 +- Source/cmExecProgramCommand.cxx | 8 +- Source/cmSystemTools.cxx | 208 ++++++++++++++++++++++++++++++++++-- Source/cmSystemTools.h | 4 +- Source/cmaketest.cxx | 2 +- Source/ctest.cxx | 2 +- 6 files changed, 207 insertions(+), 19 deletions(-) diff --git a/Source/cmConfigureGccXmlCommand.cxx b/Source/cmConfigureGccXmlCommand.cxx index 4e9eea0..d8a8efe 100644 --- a/Source/cmConfigureGccXmlCommand.cxx +++ b/Source/cmConfigureGccXmlCommand.cxx @@ -253,7 +253,7 @@ bool cmConfigureGccXmlCommand::CompilerIsMipsPro() const std::string command = compiler; command += " -version 2>&1"; std::string output; - if(!cmSystemTools::RunCommand(command.c_str(), output, false)) + if(!cmSystemTools::RunCommand(command.c_str(), output, 0, false)) { return false; } if(output.find("MIPSpro") != std::string::npos) { diff --git a/Source/cmExecProgramCommand.cxx b/Source/cmExecProgramCommand.cxx index bd40613..aea6950 100644 --- a/Source/cmExecProgramCommand.cxx +++ b/Source/cmExecProgramCommand.cxx @@ -29,12 +29,8 @@ bool cmExecProgramCommand::InitialPass(std::vector const& args) if(args.size() == 2) { cmSystemTools::MakeDirectory(args[1].c_str()); - std::string command; - command = "cd "; - command += cmSystemTools::ConvertToOutputPath(args[1].c_str()); - command += " && "; - command += args[0].c_str(); - cmSystemTools::RunCommand(command.c_str(), output); + cmSystemTools::RunCommand(args[0].c_str(), output, + cmSystemTools::ConvertToOutputPath(args[1].c_str()).c_str()); } else { diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index e2d184b..0f93d5b 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -31,6 +31,7 @@ #endif #if defined(_MSC_VER) || defined(__BORLANDC__) +#include #include #include #define _unlink unlink @@ -966,24 +967,162 @@ bool cmSystemTools::IsOff(const char* val) bool cmSystemTools::RunCommand(const char* command, std::string& output, + const char* dir, bool verbose) { int foo; - return cmSystemTools::RunCommand(command, output, foo, verbose); + return cmSystemTools::RunCommand(command, output, foo, dir, verbose); } -bool cmSystemTools::RunCommand(const char* command, - std::string& output, - int &retVal, bool verbose) + +#if defined(WIN32) && !defined(__CYGWIN__) +// Code from a Borland web site with the following explaination : +/* In this article, I will explain how to spawn a console application and redirect its standard input/output using anonymous pipes. An anonymous pipe is a pipe that goes only in one direction (read pipe, write pipe, etc.). Maybe you are asking, "why would I ever need to do this sort of thing?" One example would be a Windows telnet server, where you spawn a shell and listen on a port and send and receive data between the shell and the socket client. (Windows does not really have a built-in remote shell). +First, we should talk about pipes. A pipe in Windows is simply a method of communication, often between process. The SDK defines a pipe as "a communication conduit with two ends; a process with a handle to one end can communicate with a process having a handle to the other end." In our case, we are using "anonymous" pipes, one-way pipes that "transfer data between a parent process and a child process or between two child processes of the same parent process." It's easiest to imagine a pipe as its namesake. An actual pipe running between processes that can carry data. + +We are using anonymous pipes because the console app we are spawning is a child process. We use the CreatePipe function which will create an anonymous pipe and return a read handle and a write handle. We will create two pipes, on for stdin and one for stdout. We will then monitor the read end of the stdout pipe to check for display on our child process. Every time there is something availabe for reading, we will display it in our app. Consequently, we check for input in our app and send it off to the write end of the stdin pipe. +*/ + +inline bool IsWinNT() //check if we're running NT +{ + OSVERSIONINFO osv; + osv.dwOSVersionInfoSize = sizeof(osv); + GetVersionEx(&osv); + return (osv.dwPlatformId == VER_PLATFORM_WIN32_NT); +} + +//--------------------------------------------------------------------------- +bool WindowsRunCommand(const char* command, + const char* dir, + std::string& output, + int& retVal, + bool verbose) { const int BUFFER_SIZE = 4096; + char buf[BUFFER_SIZE]; //i/o buffer + + STARTUPINFO si; + SECURITY_ATTRIBUTES sa; + SECURITY_DESCRIPTOR sd; //security information for pipes + PROCESS_INFORMATION pi; + HANDLE newstdin,newstdout,read_stdout,write_stdin; //pipe handles + + if (IsWinNT()) //initialize security descriptor (Windows NT) + { + InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(&sd, true, NULL, false); + sa.lpSecurityDescriptor = &sd; + } + else sa.lpSecurityDescriptor = NULL; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; //allow inheritable handles + + if (!CreatePipe(&newstdin,&write_stdin,&sa,0)) //create stdin pipe + { + cmSystemTools::Error("CreatePipe"); + return false; + } + if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) //create stdout pipe + { + cmSystemTools::Error("CreatePipe"); + CloseHandle(newstdin); + CloseHandle(write_stdin); + return false; + } + + GetStartupInfo(&si); //set startupinfo for the spawned process + /* + The dwFlags member tells CreateProcess how to make the process. + STARTF_USESTDHANDLES validates the hStd* members. STARTF_USESHOWWINDOW + validates the wShowWindow member. + */ + si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdOutput = newstdout; + si.hStdError = newstdout; //set the new handles for the child process + si.hStdInput = newstdin; + char* commandAndArgs = strcpy(new char[strlen(command)+1], command); + if (!CreateProcess(NULL,commandAndArgs,NULL,NULL,TRUE,CREATE_NEW_CONSOLE, + NULL,dir,&si,&pi)) + { + cmSystemTools::Error("CreateProcess failed", commandAndArgs); + CloseHandle(newstdin); + CloseHandle(newstdout); + CloseHandle(read_stdout); + CloseHandle(write_stdin); + delete [] commandAndArgs; + return false; + } + delete [] commandAndArgs; + + unsigned long exit=0; //process exit code + unsigned long bread; //bytes read + unsigned long avail; //bytes available + + memset(buf, 0, sizeof(buf)); + for(;;) //main program loop + { + GetExitCodeProcess(pi.hProcess,&exit); //while the process is running + if (exit != STILL_ACTIVE) + break; + //check to see if there is any data to read from stdout + PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL); + if (bread != 0) + { + memset(buf, 0, sizeof(buf)); + if (avail > 1023) + { + while (bread >= 1023) + { + ReadFile(read_stdout,buf,1023,&bread,NULL); //read the stdout pipe + printf("%s",buf); + memset(buf, 0, sizeof(buf)); + } + } + else + { + ReadFile(read_stdout,buf,1023,&bread,NULL); + output += buf; + output += "\n"; + if(verbose) + { + std::cout << buf << "\n" << std::flush; + } + } + } + } + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + CloseHandle(newstdin); //clean stuff up + CloseHandle(newstdout); + CloseHandle(read_stdout); + CloseHandle(write_stdin); + retVal = exit; + return true; +} + +// use this for shell commands like echo and dir +bool RunCommandViaSystem(const char* command, + const char* dir, + std::string& output, + int& retVal, + bool verbose) +{ + const int BUFFER_SIZE = 4096; char buffer[BUFFER_SIZE]; - if(s_DisableRunCommandOutput) + std::string commandInDir; + if(dir) { - verbose = false; + commandInDir = "cd "; + commandInDir += dir; + commandInDir += " && "; + commandInDir += command; } - -#if defined(WIN32) && !defined(__CYGWIN__) + else + { + commandInDir = command; + } + command = commandInDir.c_str(); std::string commandToFile = command; commandToFile += " > "; std::string tempFile; @@ -1015,7 +1154,60 @@ bool cmSystemTools::RunCommand(const char* command, fin.close(); cmSystemTools::RemoveFile(tempFile.c_str()); return true; +} + + +#endif // endif WIN32 not CYGWIN + + +// run a command unix uses popen (easy) +// windows uses CreateProcess (difficult) or system for built in commands +bool cmSystemTools::RunCommand(const char* command, + std::string& output, + int &retVal, + const char* dir, + bool verbose) +{ + if(s_DisableRunCommandOutput) + { + verbose = false; + } + +#if defined(WIN32) && !defined(__CYGWIN__) + // if the command does not start with a quote, then + // try to find the program, and if the program can not be + // found use system to run the command as it must be a built in + // shell command like echo or dir + if(command[0] != '\"') + { + cmRegularExpression nonQuoted("^([^ \t]*)[ \t](.*)"); + if(nonQuoted.find(command)) + { + std::string cmd = nonQuoted.match(1); + if(cmSystemTools::FindProgram(cmd.c_str()) == "") + { + return RunCommandViaSystem(command, dir, output, retVal, verbose); + } + } + } + // best case is the command is an executable and we run it this way + return WindowsRunCommand(command, dir, output, retVal, verbose); #else + std::string commandInDir; + if(dir) + { + commandInDir = "cd "; + commandInDir += dir; + commandInDir += " && "; + commandInDir += command; + } + else + { + commandInDir = command; + } + command = commandInDir.c_str(); + const int BUFFER_SIZE = 4096; + char buffer[BUFFER_SIZE]; if(verbose) { std::cout << "running " << command << std::endl; diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h index d9e4796..9cbd62a 100644 --- a/Source/cmSystemTools.h +++ b/Source/cmSystemTools.h @@ -247,10 +247,10 @@ public: * If verbose is false, no user-viewable output from the program * being run will be generated. */ - static bool RunCommand(const char* command, std::string& output, + static bool RunCommand(const char* command, std::string& output, const char* directory = 0, bool verbose = true); static bool RunCommand(const char* command, std::string& output, - int &retVal, bool verbose = true); + int &retVal, const char* directory = 0, bool verbose = true); ///! change directory the the directory specified static int ChangeDirectory(const char* dir); diff --git a/Source/cmaketest.cxx b/Source/cmaketest.cxx index 1645b7c..521c965 100644 --- a/Source/cmaketest.cxx +++ b/Source/cmaketest.cxx @@ -256,7 +256,7 @@ int main (int argc, char *argv[]) fullPath = cmSystemTools::ConvertToOutputPath(fullPath.c_str()); std::cout << "Running test executable: " << fullPath.c_str() << "\n"; int ret = 0; - if (!cmSystemTools::RunCommand(fullPath.c_str(), output, ret, true)) + if (!cmSystemTools::RunCommand(fullPath.c_str(), output, ret, 0, true)) { std::cerr << "Error: " << fullPath.c_str() << " execution failed\n"; // return to the original directory diff --git a/Source/ctest.cxx b/Source/ctest.cxx index 563d28a..7ff80ba 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -211,7 +211,7 @@ void ctest::ProcessDirectory(int &passed, std::vector &failed) std::string output; int retVal; if (!cmSystemTools::RunCommand(testCommand.c_str(), output, - retVal, false) || retVal != 0) + retVal, 0, false) || retVal != 0) { fprintf(stderr,"***Failed\n"); if (output != "") -- cgit v0.12