Imported Upstream version 2.8.9
[platform/upstream/cmake.git] / Source / cmcldeps.cxx
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15
16 // Wrapper around cl that adds /showIncludes to command line, and uses that to
17 // generate .d files that match the style from gcc -MD.
18 //
19 // /showIncludes is equivalent to -MD, not -MMD, that is, system headers are
20 // included.
21
22
23 #include <windows.h>
24 #include <sstream>
25 #include <cmSystemTools.h>
26
27 // We don't want any wildcard expansion.
28 // See http://msdn.microsoft.com/en-us/library/zay8tzh6(v=vs.85).aspx
29 void _setargv() {}
30
31 static void Fatal(const char* msg, ...) {
32   va_list ap;
33   fprintf(stderr, "ninja: FATAL: ");
34   va_start(ap, msg);
35   vfprintf(stderr, msg, ap);
36   va_end(ap);
37   fprintf(stderr, "\n");
38   // On Windows, some tools may inject extra threads.
39   // exit() may block on locks held by those threads, so forcibly exit.
40   fflush(stderr);
41   fflush(stdout);
42   ExitProcess(1);
43 }
44
45 static void usage(const char* msg) {
46   Fatal("%s\n\nusage:\n    "
47           "cmcldeps "
48           "<language C, CXX or RC>  "
49           "<source file path>  "
50           "<output path for *.d file>  "
51           "<output path for *.obj file>  "
52           "<prefix of /showIncludes>  "
53           "<path to cl.exe>  "
54           "<path to tool (cl or rc)>  "
55           "<rest of command ...>\n", msg);
56 }
57
58 static std::string trimLeadingSpace(const std::string& cmdline) {
59   int i = 0;
60   for (; cmdline[i] == ' '; ++i)
61     ;
62   return cmdline.substr(i);
63 }
64
65 static void doEscape(std::string& str, const std::string& search,
66                                        const std::string& repl) {
67   std::string::size_type pos = 0;
68   while ((pos = str.find(search, pos)) != std::string::npos) {
69     str.replace(pos, search.size(), repl);
70     pos += repl.size();
71   }
72 }
73
74 // Strips one argument from the cmdline and returns it. "surrounding quotes"
75 // are removed from the argument if there were any.
76 static std::string getArg(std::string& cmdline) {
77   std::string ret;
78   bool in_quoted = false;
79   unsigned int i = 0;
80
81   cmdline = trimLeadingSpace(cmdline);
82
83   for (;; ++i) {
84     if (i >= cmdline.size())
85       usage("Couldn't parse arguments.");
86     if (!in_quoted && cmdline[i] == ' ')
87       break; // "a b" "x y"
88     if (cmdline[i] == '"')
89       in_quoted = !in_quoted;
90   }
91
92   ret = cmdline.substr(0, i);
93   if (ret[0] == '"' && ret[i - 1] == '"')
94     ret = ret.substr(1, ret.size() - 2);
95   cmdline = cmdline.substr(i);
96   return ret;
97 }
98
99 static void parseCommandLine(LPTSTR wincmdline,
100                              std::string& lang,
101                              std::string& srcfile,
102                              std::string& dfile,
103                              std::string& objfile,
104                              std::string& prefix,
105                              std::string& clpath,
106                              std::string& binpath,
107                              std::string& rest) {
108   std::string cmdline(wincmdline);
109   /* self */ getArg(cmdline);
110   lang = getArg(cmdline);
111   srcfile = getArg(cmdline);
112   dfile = getArg(cmdline);
113   objfile = getArg(cmdline);
114   prefix = getArg(cmdline);
115   clpath = getArg(cmdline);
116   binpath = getArg(cmdline);
117   rest = trimLeadingSpace(cmdline);
118 }
119
120 static void outputDepFile(const std::string& dfile, const std::string& objfile,
121         std::vector<std::string>& incs) {
122
123   if (dfile.empty())
124     return;
125
126   // strip duplicates
127   std::sort(incs.begin(), incs.end());
128   incs.erase(std::unique(incs.begin(), incs.end()), incs.end());
129
130   FILE* out = fopen(dfile.c_str(), "wb");
131
132   // FIXME should this be fatal or not? delete obj? delete d?
133   if (!out)
134     return;
135
136   std::string tmp = objfile;
137   doEscape(tmp, " ", "\\ ");
138   fprintf(out, "%s: \\\n", tmp.c_str());
139
140   std::vector<std::string>::iterator it = incs.begin();
141   for (; it != incs.end(); ++it) {
142     tmp = *it;
143     doEscape(tmp, "\\", "/");
144     doEscape(tmp, " ", "\\ ");
145     fprintf(out, "%s \\\n", tmp.c_str());
146   }
147
148   fprintf(out, "\n");
149   fclose(out);
150 }
151
152
153 bool startsWith(const std::string& str, const std::string& what) {
154   return str.compare(0, what.size(), what) == 0;
155 }
156
157 bool contains(const std::string& str, const std::string& what) {
158   return str.find(what) != std::string::npos;
159 }
160
161 std::string replace(const std::string& str, const std::string& what,
162                     const std::string& replacement) {
163   size_t pos = str.find(what);
164   if (pos == std::string::npos)
165     return str;
166   std::string replaced = str;
167   return replaced.replace(pos, what.size(), replacement);
168 }
169
170
171
172 static int process( const std::string& srcfilename,
173                     const std::string& dfile,
174                     const std::string& objfile,
175                     const std::string& prefix,
176                     const std::string& cmd,
177                     const std::string& dir = "",
178                     bool quiet = false)
179 {
180   std::string output;
181   // break up command line into a vector
182   std::vector<std::string> args;
183   cmSystemTools::ParseWindowsCommandLine(cmd.c_str(), args);
184   // convert to correct vector type for RunSingleCommand
185   std::vector<cmStdString> command;
186   for(std::vector<std::string>::iterator i = args.begin();
187       i != args.end(); ++i)
188     {
189     command.push_back(i->c_str());
190     }
191   // run the command
192   int exit_code = 0;
193   bool run = cmSystemTools::RunSingleCommand(command, &output, &exit_code,
194                                     dir.c_str(), cmSystemTools::OUTPUT_NONE);
195
196   // process the include directives and output everything else
197   std::stringstream ss(output);
198   std::string line;
199   std::vector<std::string> includes;
200   bool isFirstLine = true; // cl prints always first the source filename
201   while (std::getline(ss, line)) {
202     if (startsWith(line, prefix)) {
203       std::string inc = trimLeadingSpace(line.substr(prefix.size()).c_str());
204       if (inc[inc.size() - 1] == '\r') // blech, stupid \r\n
205         inc = inc.substr(0, inc.size() - 1);
206       includes.push_back(inc);
207     } else {
208       if (!isFirstLine || !startsWith(line, srcfilename)) {
209         if (!quiet || exit_code != 0) {
210           fprintf(stdout, "%s\n", line.c_str());
211         }
212       } else {
213         isFirstLine = false;
214       }
215     }
216   }
217
218   // don't update .d until/unless we succeed compilation
219   if (run && exit_code == 0)
220     outputDepFile(dfile, objfile, includes);
221
222   return exit_code;
223 }
224
225
226 int main() {
227
228   // Use the Win32 api instead of argc/argv so we can avoid interpreting the
229   // rest of command line after the .d and .obj. Custom parsing seemed
230   // preferable to the ugliness you get into in trying to re-escape quotes for
231   // subprocesses, so by avoiding argc/argv, the subprocess is called with
232   // the same command line verbatim.
233
234   std::string lang, srcfile, dfile, objfile, prefix, cl, binpath, rest;
235   parseCommandLine(GetCommandLine(), lang, srcfile, dfile, objfile,
236                                      prefix, cl, binpath, rest);
237
238   // needed to suppress filename output of msvc tools
239   std::string srcfilename;
240   std::string::size_type pos = srcfile.rfind("\\");
241   if (pos != std::string::npos) {
242     srcfilename = srcfile.substr(pos + 1);
243   }
244
245   std::string nol = " /nologo ";
246   std::string show = " /showIncludes ";
247   if (lang == "C" || lang == "CXX") {
248     return process(srcfilename, dfile, objfile, prefix,
249                    binpath + nol + show + rest);
250   } else if (lang == "RC") {
251     // "misuse" cl.exe to get headers from .rc files
252
253     std::string clrest = rest;
254     // rc: /fo x.dir\x.rc.res  ->  cl: /out:x.dir\x.rc.res.dep.obj
255     clrest = replace(clrest, "/fo", "/out:");
256     clrest = replace(clrest, objfile, objfile + ".dep.obj ");
257
258     // rc: src\x\x.rc  ->  cl: /Tc src\x\x.rc
259     if (srcfile.find(" ") != std::string::npos)
260       srcfile = "\"" + srcfile + "\"";
261     clrest = replace(clrest, srcfile, "/Tc " + srcfile);
262
263     cl = "\"" + cl + "\" /P /DRC_INVOKED ";
264
265     // call cl in object dir so the .i is generated there
266     std::string objdir;
267     std::string::size_type pos = objfile.rfind("\\");
268     if (pos != std::string::npos) {
269       objdir = objfile.substr(0, pos);
270     }
271
272     // extract dependencies with cl.exe
273     int exit_code = process(srcfilename, dfile, objfile,
274                             prefix, cl + nol + show + clrest, objdir, true);
275
276     if (exit_code != 0)
277       return exit_code;
278
279     // compile rc file with rc.exe
280     return process(srcfilename, "" , objfile, prefix, binpath + " " + rest);
281   }
282
283   usage("Invalid language specified.");
284   return 1;
285 }