From 1843f550d9b8b6d271cefdfb5fffd150bb8ef069 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 12 Aug 2012 14:52:18 -0700 Subject: add subprocess-spawning to msvc_helper Rather than using subprocess.h, reimplement the subprocess code. This allows: 1) using anonymous (instead of named) pipes 2) not using all the completion port craziness 3) printing the output as it happens 4) further variation, like adjusting the environment (in a forthcoming change) without affecting the main subprocess code --- src/msvc_helper-win32.cc | 98 +++++++++++++++++++++++++++++++++++++++++++++++- src/msvc_helper.h | 9 +++++ src/msvc_helper_test.cc | 10 +++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc index ff18e80..ac957e4 100644 --- a/src/msvc_helper-win32.cc +++ b/src/msvc_helper-win32.cc @@ -15,8 +15,9 @@ #include "msvc_helper.h" #include +#include -#include "string_piece.h" +#include "util.h" // static string CLWrapper::FilterShowIncludes(const string& line) { @@ -33,3 +34,98 @@ string CLWrapper::FilterShowIncludes(const string& line) { } return ""; } + +int CLWrapper::Run(const string& command, string* extra_output) { + SECURITY_ATTRIBUTES security_attributes = {}; + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = TRUE; + + // Must be inheritable so subprocesses can dup to children. + HANDLE nul = CreateFile("NUL", GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | + FILE_SHARE_DELETE, + &security_attributes, OPEN_EXISTING, 0, NULL); + if (nul == INVALID_HANDLE_VALUE) + Fatal("couldn't open nul"); + + HANDLE stdout_read, stdout_write; + if (!CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0)) + Win32Fatal("CreatePipe"); + + if (!SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)) + Win32Fatal("SetHandleInformation"); + + PROCESS_INFORMATION process_info = {}; + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(STARTUPINFO); + startup_info.hStdInput = nul; + startup_info.hStdError = stdout_write; + startup_info.hStdOutput = stdout_write; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + + if (!CreateProcessA(NULL, (char*)command.c_str(), NULL, NULL, + /* inherit handles */ TRUE, 0, + NULL, NULL, + &startup_info, &process_info)) { + Win32Fatal("CreateProcess"); + } + + if (!CloseHandle(nul) || + !CloseHandle(stdout_write)) { + Win32Fatal("CloseHandle"); + } + + // Read output of the subprocess and parse it. + string output; + DWORD read_len = 1; + while (read_len) { + char buf[64 << 10]; + read_len = 0; + if (!::ReadFile(stdout_read, buf, sizeof(buf), &read_len, NULL) && + GetLastError() != ERROR_BROKEN_PIPE) { + Win32Fatal("ReadFile"); + } + output.append(buf, read_len); + + // Loop over all lines in the output and process them. + for (;;) { + size_t ofs = output.find_first_of("\r\n"); + if (ofs == string::npos) + break; + string line = output.substr(0, ofs); + + string include = FilterShowIncludes(line); + if (!include.empty()) { + includes_.push_back(include); + } else { + if (extra_output) { + extra_output->append(line); + extra_output->append("\n"); + } else { + printf("%s\n", line.c_str()); + } + } + + if (ofs < output.size() && output[ofs] == '\r') + ++ofs; + if (ofs < output.size() && output[ofs] == '\n') + ++ofs; + output = output.substr(ofs); + } + } + + if (WaitForSingleObject(process_info.hProcess, INFINITE) == WAIT_FAILED) + Win32Fatal("WaitForSingleObject"); + + DWORD exit_code = 0; + if (!GetExitCodeProcess(process_info.hProcess, &exit_code)) + Win32Fatal("GetExitCodeProcess"); + + if (!CloseHandle(stdout_read) || + !CloseHandle(process_info.hProcess) || + !CloseHandle(process_info.hThread)) { + Win32Fatal("CloseHandle"); + } + + return exit_code; +} diff --git a/src/msvc_helper.h b/src/msvc_helper.h index e7c2b1a..5a657be 100644 --- a/src/msvc_helper.h +++ b/src/msvc_helper.h @@ -23,7 +23,16 @@ struct StringPiece; /// format when building with /showIncludes. This class wraps a CL /// process and parses that output to extract the file list. struct CLWrapper { + /// Start a process and parse its output. Returns its exit code. + /// Any non-parsed output is buffered into \a extra_output if provided, + /// otherwise it is printed to stdout while the process runs. + /// Crashes (calls Fatal()) on error. + int Run(const string& command, string* extra_output=NULL); + /// Parse a line of cl.exe output and extract /showIncludes info. /// If a dependency is extracted, returns a nonempty string. + /// Exposed for testing. static string FilterShowIncludes(const string& line); + + vector includes_; }; diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc index 9eb3c89..8db4c69 100644 --- a/src/msvc_helper_test.cc +++ b/src/msvc_helper_test.cc @@ -30,3 +30,13 @@ TEST(MSVCHelperTest, ShowIncludes) { CLWrapper::FilterShowIncludes("Note: including file: " "c:\\initspaces.h")); } + +TEST(MSVCHelperTest, Run) { + CLWrapper cl; + string output; + cl.Run("cmd /c \"echo foo&& echo Note: including file: foo.h&&echo bar\"", + &output); + ASSERT_EQ("foo\nbar\n", output); + ASSERT_EQ(1u, cl.includes_.size()); + ASSERT_EQ("foo.h", cl.includes_[0]); +} -- cgit v0.12