| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664 |
- /*
- ninja's subprocess.h
- */
- // Copyright 2012 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #ifndef NINJA_SUBPROCESS_H_
- #define NINJA_SUBPROCESS_H_
- #include <string>
- #include <vector>
- #include <queue>
- using namespace std;
- #ifdef _WIN32
- #include <windows.h>
- #else
- #include <signal.h>
- #endif
- //#include "exit_status.h"
- enum ExitStatus {
- ExitSuccess,
- ExitFailure,
- ExitInterrupted
- };
- /// Subprocess wraps a single async subprocess. It is entirely
- /// passive: it expects the caller to notify it when its fds are ready
- /// for reading, as well as call Finish() to reap the child once done()
- /// is true.
- struct Subprocess {
- ~Subprocess();
- /// Returns ExitSuccess on successful process exit, ExitInterrupted if
- /// the process was interrupted, ExitFailure if it otherwise failed.
- ExitStatus Finish();
- bool Done() const;
- const string& GetOutput() const;
- int ExitCode() const { return exit_code_; }
- private:
- Subprocess();
- bool Start(struct SubprocessSet* set, const string& command);
- void OnPipeReady();
- string buf_;
- #ifdef _WIN32
- /// Set up pipe_ as the parent-side pipe of the subprocess; return the
- /// other end of the pipe, usable in the child process.
- HANDLE SetupPipe(HANDLE ioport);
- HANDLE child_;
- HANDLE pipe_;
- OVERLAPPED overlapped_;
- char overlapped_buf_[4 << 10];
- bool is_reading_;
- int exit_code_;
- #else
- int fd_;
- pid_t pid_;
- #endif
- friend struct SubprocessSet;
- };
- /// SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses.
- /// DoWork() waits for any state change in subprocesses; finished_
- /// is a queue of subprocesses as they finish.
- struct SubprocessSet {
- SubprocessSet();
- ~SubprocessSet();
- Subprocess* Add(const string& command);
- bool DoWork();
- Subprocess* NextFinished();
- void Clear();
- vector<Subprocess*> running_;
- queue<Subprocess*> finished_;
- #ifdef _WIN32
- static BOOL WINAPI NotifyInterrupted(DWORD dwCtrlType);
- static HANDLE ioport_;
- #else
- static void SetInterruptedFlag(int signum);
- static bool interrupted_;
- struct sigaction old_act_;
- sigset_t old_mask_;
- #endif
- };
- #endif // NINJA_SUBPROCESS_H_
- /*
- ninja's util functions
- */
- static void Fatal(const char* msg, ...) {
- va_list ap;
- fprintf(stderr, "ninja: FATAL: ");
- va_start(ap, msg);
- vfprintf(stderr, msg, ap);
- va_end(ap);
- fprintf(stderr, "\n");
- #ifdef _WIN32
- // On Windows, some tools may inject extra threads.
- // exit() may block on locks held by those threads, so forcibly exit.
- fflush(stderr);
- fflush(stdout);
- ExitProcess(1);
- #else
- exit(1);
- #endif
- }
- #ifdef _WIN32
- string GetLastErrorString() {
- DWORD err = GetLastError();
- char* msg_buf;
- FormatMessageA(
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL,
- err,
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- (char*)&msg_buf,
- 0,
- NULL);
- string msg = msg_buf;
- LocalFree(msg_buf);
- return msg;
- }
- #endif
- #define snprintf _snprintf
- /*
- ninja's subprocess-win32.cc
- */
- // Copyright 2012 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //#include "subprocess.h"
- #include <stdio.h>
- #include <algorithm>
- //#include "util.h"
- namespace {
- void Win32Fatal(const char* function) {
- Fatal("%s: %s", function, GetLastErrorString().c_str());
- }
- } // anonymous namespace
- Subprocess::Subprocess() : child_(NULL) , overlapped_(), is_reading_(false),
- exit_code_(1) {
- }
- Subprocess::~Subprocess() {
- if (pipe_) {
- if (!CloseHandle(pipe_))
- Win32Fatal("CloseHandle");
- }
- // Reap child if forgotten.
- if (child_)
- Finish();
- }
- HANDLE Subprocess::SetupPipe(HANDLE ioport) {
- char pipe_name[100];
- snprintf(pipe_name, sizeof(pipe_name),
- "\\\\.\\pipe\\ninja_pid%u_sp%p", GetCurrentProcessId(), this);
- pipe_ = ::CreateNamedPipeA(pipe_name,
- PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
- PIPE_TYPE_BYTE,
- PIPE_UNLIMITED_INSTANCES,
- 0, 0, INFINITE, NULL);
- if (pipe_ == INVALID_HANDLE_VALUE)
- Win32Fatal("CreateNamedPipe");
- if (!CreateIoCompletionPort(pipe_, ioport, (ULONG_PTR)this, 0))
- Win32Fatal("CreateIoCompletionPort");
- memset(&overlapped_, 0, sizeof(overlapped_));
- if (!ConnectNamedPipe(pipe_, &overlapped_) &&
- GetLastError() != ERROR_IO_PENDING) {
- Win32Fatal("ConnectNamedPipe");
- }
- // Get the write end of the pipe as a handle inheritable across processes.
- HANDLE output_write_handle = CreateFile(pipe_name, GENERIC_WRITE, 0,
- NULL, OPEN_EXISTING, 0, NULL);
- HANDLE output_write_child;
- if (!DuplicateHandle(GetCurrentProcess(), output_write_handle,
- GetCurrentProcess(), &output_write_child,
- 0, TRUE, DUPLICATE_SAME_ACCESS)) {
- Win32Fatal("DuplicateHandle");
- }
- CloseHandle(output_write_handle);
- return output_write_child;
- }
- bool Subprocess::Start(SubprocessSet* set, const string& command) {
- HANDLE child_pipe = SetupPipe(set->ioport_);
- SECURITY_ATTRIBUTES security_attributes;
- memset(&security_attributes, 0, sizeof(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");
- STARTUPINFOA startup_info;
- memset(&startup_info, 0, sizeof(startup_info));
- startup_info.cb = sizeof(STARTUPINFO);
- startup_info.dwFlags = STARTF_USESTDHANDLES;
- startup_info.hStdInput = nul;
- startup_info.hStdOutput = child_pipe;
- startup_info.hStdError = child_pipe;
- PROCESS_INFORMATION process_info;
- memset(&process_info, 0, sizeof(process_info));
- // Do not prepend 'cmd /c' on Windows, this breaks command
- // lines greater than 8,191 chars.
- if (!CreateProcessA(NULL, (char*)command.c_str(), NULL, NULL,
- /* inherit handles */ TRUE, CREATE_NEW_PROCESS_GROUP,
- NULL, NULL,
- &startup_info, &process_info)) {
- DWORD error = GetLastError();
- if (error == ERROR_FILE_NOT_FOUND) {
- // file (program) not found error is treated
- // as a normal build action failure
- if (child_pipe)
- CloseHandle(child_pipe);
- CloseHandle(pipe_);
- CloseHandle(nul);
- pipe_ = NULL;
- // child_ is already NULL;
- buf_ =
- "CreateProcess failed: The system cannot find the file specified.\n";
- return true;
- } else {
- Win32Fatal("CreateProcess"); // pass all other errors to Win32Fatal
- }
- }
- // Close pipe channel only used by the child.
- if (child_pipe)
- CloseHandle(child_pipe);
- CloseHandle(nul);
- CloseHandle(process_info.hThread);
- child_ = process_info.hProcess;
- return true;
- }
- void Subprocess::OnPipeReady() {
- DWORD bytes;
- if (!GetOverlappedResult(pipe_, &overlapped_, &bytes, TRUE)) {
- if (GetLastError() == ERROR_BROKEN_PIPE) {
- CloseHandle(pipe_);
- pipe_ = NULL;
- return;
- }
- Win32Fatal("GetOverlappedResult");
- }
- if (is_reading_ && bytes)
- buf_.append(overlapped_buf_, bytes);
- memset(&overlapped_, 0, sizeof(overlapped_));
- is_reading_ = true;
- if (!::ReadFile(pipe_, overlapped_buf_, sizeof(overlapped_buf_),
- &bytes, &overlapped_)) {
- if (GetLastError() == ERROR_BROKEN_PIPE) {
- CloseHandle(pipe_);
- pipe_ = NULL;
- return;
- }
- if (GetLastError() != ERROR_IO_PENDING)
- Win32Fatal("ReadFile");
- }
- // Even if we read any bytes in the readfile call, we'll enter this
- // function again later and get them at that point.
- }
- ExitStatus Subprocess::Finish() {
- if (!child_)
- return ExitFailure;
- // TODO: add error handling for all of these.
- WaitForSingleObject(child_, INFINITE);
- DWORD exit_code = 0;
- GetExitCodeProcess(child_, &exit_code);
- CloseHandle(child_);
- child_ = NULL;
- exit_code_ = exit_code;
- return exit_code == 0 ? ExitSuccess :
- exit_code == CONTROL_C_EXIT ? ExitInterrupted :
- ExitFailure;
- }
- bool Subprocess::Done() const {
- return pipe_ == NULL;
- }
- const string& Subprocess::GetOutput() const {
- return buf_;
- }
- HANDLE SubprocessSet::ioport_;
- SubprocessSet::SubprocessSet() {
- ioport_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
- if (!ioport_)
- Win32Fatal("CreateIoCompletionPort");
- if (!SetConsoleCtrlHandler(NotifyInterrupted, TRUE))
- Win32Fatal("SetConsoleCtrlHandler");
- }
- SubprocessSet::~SubprocessSet() {
- Clear();
- SetConsoleCtrlHandler(NotifyInterrupted, FALSE);
- CloseHandle(ioport_);
- }
- BOOL WINAPI SubprocessSet::NotifyInterrupted(DWORD dwCtrlType) {
- if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) {
- if (!PostQueuedCompletionStatus(ioport_, 0, 0, NULL))
- Win32Fatal("PostQueuedCompletionStatus");
- return TRUE;
- }
- return FALSE;
- }
- Subprocess *SubprocessSet::Add(const string& command) {
- Subprocess *subprocess = new Subprocess;
- if (!subprocess->Start(this, command)) {
- delete subprocess;
- return 0;
- }
- if (subprocess->child_)
- running_.push_back(subprocess);
- else
- finished_.push(subprocess);
- return subprocess;
- }
- bool SubprocessSet::DoWork() {
- DWORD bytes_read;
- Subprocess* subproc;
- OVERLAPPED* overlapped;
- if (!GetQueuedCompletionStatus(ioport_, &bytes_read, (PULONG_PTR)&subproc,
- &overlapped, INFINITE)) {
- if (GetLastError() != ERROR_BROKEN_PIPE)
- Win32Fatal("GetQueuedCompletionStatus");
- }
- if (!subproc) // A NULL subproc indicates that we were interrupted and is
- // delivered by NotifyInterrupted above.
- return true;
- subproc->OnPipeReady();
- if (subproc->Done()) {
- vector<Subprocess*>::iterator end =
- std::remove(running_.begin(), running_.end(), subproc);
- if (running_.end() != end) {
- finished_.push(subproc);
- running_.resize(end - running_.begin());
- }
- }
- return false;
- }
- Subprocess* SubprocessSet::NextFinished() {
- if (finished_.empty())
- return NULL;
- Subprocess* subproc = finished_.front();
- finished_.pop();
- return subproc;
- }
- void SubprocessSet::Clear() {
- for (vector<Subprocess*>::iterator i = running_.begin();
- i != running_.end(); ++i) {
- if ((*i)->child_)
- if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
- GetProcessId((*i)->child_)))
- Win32Fatal("GenerateConsoleCtrlEvent");
- }
- for (vector<Subprocess*>::iterator i = running_.begin();
- i != running_.end(); ++i)
- delete *i;
- running_.clear();
- }
- // Copyright 2011 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- // Wrapper around cl that adds /showIncludes to command line, and uses that to
- // generate .d files that match the style from gcc -MD.
- //
- // /showIncludes is equivalent to -MD, not -MMD, that is, system headers are
- // included.
- #include <windows.h>
- #include <sstream>
- //#include "subprocess.h"
- //#include "util.h"
- // We don't want any wildcard expansion.
- // See http://msdn.microsoft.com/en-us/library/zay8tzh6(v=vs.85).aspx
- void _setargv() {}
- static void usage(const char* msg) {
- Fatal("%s\n\nusage:\n"
- " cldeps "
- "<source file> "
- "<output path for *.d file> "
- "<output path for *.obj file> "
- "<prefix of /showIncludes> "
- "<path to cl> "
- "<rest of command ...>\n", msg);
- }
- static string trimLeadingSpace(const string& cmdline) {
- int i = 0;
- for (; cmdline[i] == ' '; ++i)
- ;
- return cmdline.substr(i);
- }
- static void doEscape(string& str, const string& search, const string& repl) {
- string::size_type pos = 0;
- while ((pos = str.find(search, pos)) != string::npos) {
- str.replace(pos, search.size(), repl);
- pos += repl.size();
- }
- }
- // Strips one argument from the cmdline and returns it. "surrounding quotes"
- // are removed from the argument if there were any.
- static string getArg(string& cmdline) {
- string ret;
- bool in_quoted = false;
- unsigned int i = 0;
- cmdline = trimLeadingSpace(cmdline);
- for (;; ++i) {
- if (i >= cmdline.size())
- usage("Couldn't parse arguments.");
- if (!in_quoted && cmdline[i] == ' ')
- break; // "a b" "x y"
- if (cmdline[i] == '"')
- in_quoted = !in_quoted;
- }
- ret = cmdline.substr(0, i);
- if (ret[0] == '"' && ret[i - 1] == '"')
- ret = ret.substr(1, ret.size() - 2);
- cmdline = cmdline.substr(i);
- return ret;
- }
- static void parseCommandLine(LPTSTR wincmdline,
- string& srcfile,
- string& dfile,
- string& objfile,
- string& prefix,
- string& clpath,
- string& rest) {
- string cmdline(wincmdline);
- /* self */ getArg(cmdline);
- srcfile = getArg(cmdline);
- std::string::size_type pos = srcfile.rfind("\\");
- if (pos != string::npos) {
- srcfile = srcfile.substr(pos + 1);
- } else {
- srcfile = "";
- }
- dfile = getArg(cmdline);
- objfile = getArg(cmdline);
- prefix = getArg(cmdline);
- clpath = getArg(cmdline);
- rest = trimLeadingSpace(cmdline);
- }
- static void outputDepFile(const string& dfile, const string& objfile,
- vector<string>& incs) {
- // strip duplicates
- sort(incs.begin(), incs.end());
- incs.erase(unique(incs.begin(), incs.end()), incs.end());
- FILE* out = fopen(dfile.c_str(), "wb");
- // FIXME should this be fatal or not? delete obj? delete d?
- if (!out)
- return;
- string tmp = objfile;
- doEscape(tmp, " ", "\\ ");
- fprintf(out, "%s: \\\n", tmp.c_str());
- for (vector<string>::iterator i(incs.begin()); i != incs.end(); ++i) {
- tmp = *i;
- doEscape(tmp, "\\", "/");
- doEscape(tmp, " ", "\\ ");
- //doEscape(tmp, "(", "\\("); // TODO ninja can't read ( and )
- //doEscape(tmp, ")", "\\)");
- fprintf(out, "%s \\\n", tmp.c_str());
- }
- fprintf(out, "\n");
- fclose(out);
- }
- bool startsWith(const std::string& str, const std::string& what) {
- return str.compare(0, what.size(), what) == 0;
- }
- bool contains(const std::string& str, const std::string& what) {
- return str.find(what) != std::string::npos;
- }
- int main() {
- // Use the Win32 api instead of argc/argv so we can avoid interpreting the
- // rest of command line after the .d and .obj. Custom parsing seemed
- // preferable to the ugliness you get into in trying to re-escape quotes for
- // subprocesses, so by avoiding argc/argv, the subprocess is called with
- // the same command line verbatim.
- string srcfile, dfile, objfile, prefix, clpath, rest;
- parseCommandLine(GetCommandLine(), srcfile, dfile, objfile,
- prefix, clpath, rest);
- #if 0
- fprintf(stderr, "\n\ncmcldebug:\n");
- fprintf(stderr, ".d : %s\n", dfile.c_str());
- fprintf(stderr, "OBJ : %s\n", objfile.c_str());
- fprintf(stderr, "CL : %s\n", clpath.c_str());
- fprintf(stderr, "REST: %s\n", rest.c_str());
- fprintf(stderr, "\n\n");
- #endif
- SubprocessSet subprocs;
- Subprocess* subproc = subprocs.Add(clpath + " /showIncludes " + rest);
- if(!subproc)
- return 2;
- while ((subproc = subprocs.NextFinished()) == NULL) {
- subprocs.DoWork();
- }
- bool success = subproc->Finish() == ExitSuccess;
- int exit_code = subproc->ExitCode();
- string output = subproc->GetOutput();
- delete subproc;
- // process the include directives and output everything else
- stringstream ss(output);
- string line;
- vector<string> includes;
- bool isFirstLine = true; // cl prints always first the source filename
- while (getline(ss, line)) {
- if (startsWith(line, prefix)) {
- if (!contains(line, "(") && !contains(line, ")")) {
- string inc = trimLeadingSpace(line.substr(prefix.size()).c_str());
- if (inc[inc.size() - 1] == '\r') // blech, stupid \r\n
- inc = inc.substr(0, inc.size() - 1);
- includes.push_back(inc);
- }
- } else {
- if (!isFirstLine || !startsWith(line, srcfile)) {
- fprintf(stdout, "%s\n", line.c_str());
- } else {
- isFirstLine = false;
- }
- }
- }
- if (!success)
- return exit_code;
- // don't update .d until/unless we succeed compilation
- outputDepFile(dfile, objfile, includes);
- return 0;
- }
|