summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Kuemmel <syntheticpp@gmx.net>2012-06-08 16:31:54 +0200
committerPeter Kuemmel <syntheticpp@gmx.net>2012-06-08 17:42:11 +0200
commit033a687acd828ad6667d154939ffdbc482ab047f (patch)
tree7e61efbc88da39b57810be42d502baaab0145136
parent1d40729eaa35dd643efdf5e793e6a541e890f33a (diff)
downloadcmake-033a687acd828ad6667d154939ffdbc482ab047f.tar.gz
Ninja: add wrapper for cl to extract dependencies
cmcldeps wraps cl and adds /showInclude before calling cl. It parses the output of cl for used headers, drops system headers and writes them to a GCC like dependency file. cmcldeps uses ATM ninja code for process handling, but could be ported later to SystemTools. TODO: Why needs ninja multiple calls in the BuildDepends test?
-rw-r--r--Modules/Platform/Windows-cl.cmake18
-rw-r--r--Source/CMakeLists.txt4
-rw-r--r--Source/cmGlobalNinjaGenerator.cxx15
-rw-r--r--Source/cmNinjaTargetGenerator.cxx17
-rw-r--r--Source/cmcldeps.cxx644
-rw-r--r--Tests/BuildDepends/CMakeLists.txt8
6 files changed, 697 insertions, 9 deletions
diff --git a/Modules/Platform/Windows-cl.cmake b/Modules/Platform/Windows-cl.cmake
index be6abb690d..eed4e52da3 100644
--- a/Modules/Platform/Windows-cl.cmake
+++ b/Modules/Platform/Windows-cl.cmake
@@ -251,3 +251,21 @@ IF(NOT EXISTS "${CMAKE_PLATFORM_ROOT_BIN}/CMakeCXXPlatform.cmake")
CONFIGURE_FILE(${CMAKE_ROOT}/Modules/Platform/Windows-cl.cmake.in
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeCXXPlatform.cmake IMMEDIATE)
ENDIF(NOT EXISTS "${CMAKE_PLATFORM_ROOT_BIN}/CMakeCXXPlatform.cmake")
+
+
+IF(CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_C_COMPILER)
+ # TODO try_compile doesn't need cmcldeps, find a better solution
+ if(NOT EXISTS ${CMAKE_TRY_COMPILE_SOURCE_DIR}/../ShowIncludes)
+ SET(showdir ${CMAKE_BINARY_DIR}/CMakeFiles/ShowIncludes)
+ FILE(WRITE ${showdir}/foo.h "\n")
+ FILE(WRITE ${showdir}/main.c "#include \"foo.h\" \nint main(){}\n")
+ EXECUTE_PROCESS(COMMAND ${CMAKE_C_COMPILER} /nologo /showIncludes ${showdir}/main.c
+ WORKING_DIRECTORY ${showdir} OUTPUT_VARIABLE showOut)
+ STRING(REPLACE main.c "" showOut1 ${showOut})
+ STRING(REPLACE "/" "\\" header1 ${showdir}/foo.h)
+ STRING(TOLOWER ${header1} header2)
+ STRING(REPLACE ${header2} "" showOut2 ${showOut1})
+ STRING(REPLACE "\n" "" showOut3 ${showOut2})
+ SET(CMAKE_CL_SHOWINCLUDE_PREFIX ${showOut3})
+ ENDIF()
+ENDIF()
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 46bdec6b9a..ebeda5679d 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -383,6 +383,10 @@ IF(CMAKE_ENABLE_NINJA)
cmNinjaUtilityTargetGenerator.h
)
ADD_DEFINITIONS(-DCMAKE_USE_NINJA)
+ IF(MSVC) # TODO WIN32
+ ADD_EXECUTABLE(cmcldeps cmcldeps.cxx)
+ INSTALL_TARGETS(/bin cmcldeps)
+ ENDIF()
ELSE()
MESSAGE(STATUS "Ninja generator disabled, enforce with -DCMAKE_ENABLE_NINJA=ON")
ENDIF()
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index c4b1cc999d..9a2597b299 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -460,17 +460,28 @@ void cmGlobalNinjaGenerator
// check if mingw is used
const char* cc = mf->GetDefinition("CMAKE_C_COMPILER");
if(cc && std::string(cc).find("gcc.exe") != std::string::npos)
- {
+ {
UsingMinGW = true;
std::string rc = cmSystemTools::FindProgram("windres");
if(rc.empty())
rc = "windres.exe";;
mf->AddDefinition("CMAKE_RC_COMPILER", rc.c_str());
}
+ else if (cc && std::string(cc).find("cl.exe") != std::string::npos)
+ {
+ const char* cmake = mf->GetDefinition("CMAKE_COMMAND");
+ std::string bindir = cmake ? cmake : "";
+ cmSystemTools::ReplaceString(bindir, "cmake.exe", "");
+ std::vector<std::string> locations;
+ locations.push_back(bindir);
+ std::string cldeps = cmSystemTools::FindProgram("cmcldeps", locations);
+ if(!cldeps.empty())
+ mf->AddDefinition("CMAKE_CMCLDEPS_EXECUTABLE", cldeps.c_str());
+ }
}
this->cmGlobalGenerator::EnableLanguage(language, mf, optional);
this->ResolveLanguageCompiler(*l, mf, optional);
- }
+ }
}
bool cmGlobalNinjaGenerator::UsingMinGW = false;
diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index 74b5c9213b..6518727aef 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -330,16 +330,20 @@ cmNinjaTargetGenerator
vars.Defines = "$DEFINES";
vars.TargetPDB = "$TARGET_PDB";
- bool cldeps = false;
+ const char* cldeps = 0;
+ const char* showIncludePrefix = 0;
const char* cc = this->GetMakefile()->GetDefinition("CMAKE_C_COMPILER");
if(cc && std::string(cc).find("cl.exe") != std::string::npos)
- cldeps = true;
+ {
+ cldeps = this->GetMakefile()->GetDefinition("CMAKE_CMCLDEPS_EXECUTABLE");
+ showIncludePrefix = this->GetMakefile()->GetDefinition("CMAKE_CL_SHOWINCLUDE_PREFIX");
+ }
std::string depfile;
std::string depfileFlagsName = "CMAKE_DEPFILE_FLAGS_" + language;
const char *depfileFlags =
this->GetMakefile()->GetDefinition(depfileFlagsName.c_str());
- if (depfileFlags || cldeps) {
+ if (depfileFlags || (cldeps && showIncludePrefix)) {
std::string depfileFlagsStr = depfileFlags ? depfileFlags : "";
depfile = "$out.d";
cmSystemTools::ReplaceString(depfileFlagsStr, "<DEPFILE>",
@@ -369,8 +373,11 @@ cmNinjaTargetGenerator
std::string cmdLine =
this->GetLocalGenerator()->BuildCommandLine(compileCmds);
- if(cldeps)
- cmdLine = "cldeps.exe $out.d $out " + cmdLine;
+ if(cldeps && showIncludePrefix)
+ {
+ std::string prefix = showIncludePrefix;
+ cmdLine = std::string(cldeps) + " $in $out.d $out " + "\"" + prefix + "\" " + cmdLine;
+ }
// Write the rule for compiling file of the given language.
std::ostringstream comment;
diff --git a/Source/cmcldeps.cxx b/Source/cmcldeps.cxx
new file mode 100644
index 0000000000..48f2cfd54e
--- /dev/null
+++ b/Source/cmcldeps.cxx
@@ -0,0 +1,644 @@
+/*
+ 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;
+
+ 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_;
+#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) {
+}
+
+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;
+
+ 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;
+ 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;
+
+ fprintf(out, "%s: \\\n", objfile.c_str());
+ for (vector<string>::iterator i(incs.begin()); i != incs.end(); ++i) {
+ string tmp = *i;
+ doEscape(tmp, "\\", "\\\\");
+ doEscape(tmp, " ", "\\ ");
+ //doEscape(tmp, "(", "("); // TODO ninja cant 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);
+
+ //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());
+
+ 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;
+ 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
+ std::string sysHeadersCamel = "Program Files (x86)\\Microsoft ";
+ std::string sysHeadersLower = "program files (x86)\\microsoft ";
+ while (getline(ss, line)) {
+ if (startsWith(line, prefix)) {
+ if (!contains(line, sysHeadersCamel) && !contains(line, sysHeadersLower)) {
+ 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 3;
+
+ // don't update .d until/unless we succeed compilation
+ outputDepFile(dfile, objfile, includes);
+
+ return 0;
+}
diff --git a/Tests/BuildDepends/CMakeLists.txt b/Tests/BuildDepends/CMakeLists.txt
index aa32d67a00..5e36d11b44 100644
--- a/Tests/BuildDepends/CMakeLists.txt
+++ b/Tests/BuildDepends/CMakeLists.txt
@@ -28,6 +28,10 @@ function(help_xcode_depends)
endif(HELP_XCODE)
endfunction(help_xcode_depends)
+if("${CMAKE_GENERATOR}" MATCHES "Ninja")
+ set(HELP_NINJA 1) # TODO Why is this needed?
+endif()
+
# The Intel compiler causes the MSVC linker to crash during
# incremental linking, so avoid the /INCREMENTAL:YES flag.
if(WIN32 AND "${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel")
@@ -154,7 +158,7 @@ try_compile(RESULT
OUTPUT_VARIABLE OUTPUT)
# Xcode is in serious need of help here
-if(HELP_XCODE)
+if(HELP_XCODE OR HELP_NINJA)
try_compile(RESULT
${BuildDepends_BINARY_DIR}/Project
${BuildDepends_SOURCE_DIR}/Project
@@ -165,7 +169,7 @@ if(HELP_XCODE)
${BuildDepends_SOURCE_DIR}/Project
testRebuild
OUTPUT_VARIABLE OUTPUT)
-endif(HELP_XCODE)
+endif()
message("Output from second build:\n${OUTPUT}")
if(NOT RESULT)