Remove process execution code from cmcldeps and have it use cmake code.

This simplifies the code in cmcldeps and avoids having yet another
set of process execution code.
This commit is contained in:
Bill Hoffman 2012-06-27 12:28:12 -04:00
parent bd67f75e41
commit 5f12424ebc
2 changed files with 39 additions and 486 deletions

View File

@ -386,6 +386,7 @@ IF(CMAKE_ENABLE_NINJA)
IF(WIN32 AND NOT CYGWIN AND NOT BORLAND)
SET_SOURCE_FILES_PROPERTIES(cmcldeps.cxx PROPERTIES COMPILE_DEFINITIONS _WIN32_WINNT=0x0501)
ADD_EXECUTABLE(cmcldeps cmcldeps.cxx)
TARGET_LINK_LIBRARIES(cmcldeps CMakeLib)
INSTALL_TARGETS(/bin cmcldeps)
ENDIF()
ELSE()

View File

@ -1,470 +1,3 @@
/*
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>
#include <cstdio>
#include <algorithm>
#ifdef _WIN32
#include <windows.h>
#else
#include <signal.h>
#endif
#if defined(_WIN64)
typedef unsigned __int64 cmULONG_PTR;
#else
typedef unsigned long cmULONG_PTR;
#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 std::string& GetOutput() const;
int ExitCode() const { return exit_code_; }
private:
Subprocess();
bool Start(struct SubprocessSet* set, const std::string& command,
const std::string& dir);
void OnPipeReady();
std::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);
PROCESS_INFORMATION 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 std::string& command, const std::string& dir);
bool DoWork();
Subprocess* NextFinished();
void Clear();
std::vector<Subprocess*> running_;
std::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
std::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);
std::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() : overlapped_(), is_reading_(false),
exit_code_(1) {
child_.hProcess = NULL;
}
Subprocess::~Subprocess() {
if (pipe_) {
if (!CloseHandle(pipe_))
Win32Fatal("CloseHandle");
}
// Reap child if forgotten.
if (child_.hProcess)
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, (cmULONG_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 std::string& command,
const std::string& dir) {
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, (dir.empty() ? NULL : dir.c_str()),
&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;
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_.hProcess)
return ExitFailure;
// TODO: add error handling for all of these.
WaitForSingleObject(child_.hProcess, INFINITE);
DWORD exit_code = 0;
GetExitCodeProcess(child_.hProcess, &exit_code);
CloseHandle(child_.hProcess);
child_.hProcess = 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 std::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 std::string& command,
const std::string& dir) {
Subprocess *subprocess = new Subprocess;
if (!subprocess->Start(this, command, dir)) {
delete subprocess;
return 0;
}
if (subprocess->child_.hProcess)
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, (cmULONG_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()) {
std::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() {
std::vector<Subprocess*>::iterator it = running_.begin();
for (; it != running_.end(); ++it) {
if ((*it)->child_.hProcess) {
if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
(*it)->child_.dwProcessId))
Win32Fatal("GenerateConsoleCtrlEvent");
}
}
it = running_.begin();
for (; it != running_.end(); ++it)
delete *it;
running_.clear();
}
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -489,13 +22,26 @@ void SubprocessSet::Clear() {
#include <windows.h>
#include <sstream>
//#include "subprocess.h"
//#include "util.h"
#include <cmSystemTools.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 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");
// 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);
}
static void usage(const char* msg) {
Fatal("%s\n\nusage:\n "
"cmcldeps "
@ -629,24 +175,30 @@ static int process( const std::string& srcfilename,
const std::string& prefix,
const std::string& cmd,
const std::string& dir = "",
bool quiet = false) {
SubprocessSet subprocs;
Subprocess* subproc = subprocs.Add(cmd, dir);
if(!subproc)
bool quiet = false)
{
std::string output;
int ret = 0;
// break up command line into a vector
std::vector<std::string> args;
cmSystemTools::ParseWindowsCommandLine(cmd.c_str(),
args);
// convert to correct vector type for RunSingleCommand
std::vector<cmStdString> command;
for(std::vector<std::string>::iterator i = args.begin();
i != args.end(); ++i)
{
command.push_back(i->c_str());
}
// run the command
bool success =
cmSystemTools::RunSingleCommand(command, &output, &ret, dir.c_str(),
cmSystemTools::OUTPUT_NONE);
if(ret!= 0)
{
return 2;
while ((subproc = subprocs.NextFinished()) == NULL) {
subprocs.DoWork();
}
bool success = subproc->Finish() == ExitSuccess;
int exit_code = subproc->ExitCode();
std::string output = subproc->GetOutput();
delete subproc;
}
int exit_code = ret;
// process the include directives and output everything else
std::stringstream ss(output);
std::string line;