diff options
Diffstat (limited to 'Source/cmcldeps.cxx')
-rw-r--r-- | Source/cmcldeps.cxx | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/Source/cmcldeps.cxx b/Source/cmcldeps.cxx new file mode 100644 index 0000000..262d83b --- /dev/null +++ b/Source/cmcldeps.cxx @@ -0,0 +1,291 @@ +// 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 <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 " + "<language C, CXX or RC> " + "<source file path> " + "<output path for *.d file> " + "<output path for *.obj file> " + "<prefix of /showIncludes> " + "<path to cl.exe> " + "<path to tool (cl or rc)> " + "<rest of command ...>\n", msg); +} + +static std::string trimLeadingSpace(const std::string& cmdline) { + int i = 0; + for (; cmdline[i] == ' '; ++i) + ; + return cmdline.substr(i); +} + +static void doEscape(std::string& str, const std::string& search, + const std::string& repl) { + std::string::size_type pos = 0; + while ((pos = str.find(search, pos)) != std::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 std::string getArg(std::string& cmdline) { + std::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, + std::string& lang, + std::string& srcfile, + std::string& dfile, + std::string& objfile, + std::string& prefix, + std::string& clpath, + std::string& binpath, + std::string& rest) { + std::string cmdline(wincmdline); + /* self */ getArg(cmdline); + lang = getArg(cmdline); + srcfile = getArg(cmdline); + dfile = getArg(cmdline); + objfile = getArg(cmdline); + prefix = getArg(cmdline); + clpath = getArg(cmdline); + binpath = getArg(cmdline); + rest = trimLeadingSpace(cmdline); +} + +static void outputDepFile(const std::string& dfile, const std::string& objfile, + std::vector<std::string>& incs) { + + if (dfile.empty()) + return; + + // strip duplicates + std::sort(incs.begin(), incs.end()); + incs.erase(std::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; + + std::string tmp = objfile; + doEscape(tmp, " ", "\\ "); + fprintf(out, "%s: \\\n", tmp.c_str()); + + std::vector<std::string>::iterator it = incs.begin(); + for (; it != incs.end(); ++it) { + tmp = *it; + doEscape(tmp, "\\", "/"); + 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; +} + +std::string replace(const std::string& str, const std::string& what, + const std::string& replacement) { + size_t pos = str.find(what); + if (pos == std::string::npos) + return str; + std::string replaced = str; + return replaced.replace(pos, what.size(), replacement); +} + + + +static int process( const std::string& srcfilename, + const std::string& dfile, + const std::string& objfile, + const std::string& prefix, + const std::string& cmd, + const std::string& dir = "", + bool quiet = false) +{ + std::string output; + // 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 + int exit_code = 0; + bool run = cmSystemTools::RunSingleCommand(command, &output, &exit_code, + dir.c_str(), cmSystemTools::OUTPUT_NONE); + + // process the include directives and output everything else + std::stringstream ss(output); + std::string line; + std::vector<std::string> includes; + bool isFirstLine = true; // cl prints always first the source filename + while (std::getline(ss, line)) { + if (startsWith(line, prefix)) { + std::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, srcfilename)) { + if (!quiet || exit_code != 0) { + fprintf(stdout, "%s\n", line.c_str()); + } + } else { + isFirstLine = false; + } + } + } + + // don't update .d until/unless we succeed compilation + if (run && exit_code == 0) + outputDepFile(dfile, objfile, includes); + + return exit_code; +} + + +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. + + std::string lang, srcfile, dfile, objfile, prefix, cl, binpath, rest; + parseCommandLine(GetCommandLine(), lang, srcfile, dfile, objfile, + prefix, cl, binpath, rest); + + // needed to suppress filename output of msvc tools + std::string srcfilename; + { + std::string::size_type pos = srcfile.rfind("\\"); + if (pos == std::string::npos) { + srcfilename = srcfile; + } else { + srcfilename = srcfile.substr(pos + 1); + } + } + + std::string nol = " /nologo "; + std::string show = " /showIncludes "; + if (lang == "C" || lang == "CXX") { + return process(srcfilename, dfile, objfile, prefix, + binpath + nol + show + rest); + } else if (lang == "RC") { + // "misuse" cl.exe to get headers from .rc files + + std::string clrest = rest; + // rc: /fo x.dir\x.rc.res -> cl: /out:x.dir\x.rc.res.dep.obj + clrest = replace(clrest, "/fo", "/out:"); + clrest = replace(clrest, objfile, objfile + ".dep.obj "); + + // rc: src\x\x.rc -> cl: /Tc src\x\x.rc + if (srcfile.find(" ") != std::string::npos) + srcfile = "\"" + srcfile + "\""; + clrest = replace(clrest, srcfile, "/Tc " + srcfile); + + cl = "\"" + cl + "\" /P /DRC_INVOKED "; + + // call cl in object dir so the .i is generated there + std::string objdir; + { + std::string::size_type pos = objfile.rfind("\\"); + if (pos != std::string::npos) { + objdir = objfile.substr(0, pos); + } + } + + // extract dependencies with cl.exe + int exit_code = process(srcfilename, dfile, objfile, + prefix, cl + nol + show + clrest, objdir, true); + + if (exit_code != 0) + return exit_code; + + // compile rc file with rc.exe + return process(srcfilename, "" , objfile, prefix, binpath + " " + rest); + } + + usage("Invalid language specified."); + return 1; +} |