OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "tools/gn/ninja_target_writer.h" | |
6 | |
7 #include <fstream> | |
8 #include <sstream> | |
9 | |
10 #include "base/file_util.h" | |
11 #include "base/logging.h" | |
12 #include "base/strings/string_util.h" | |
13 #include "tools/gn/config_values_extractors.h" | |
14 #include "tools/gn/err.h" | |
15 #include "tools/gn/escape.h" | |
16 #include "tools/gn/file_template.h" | |
17 #include "tools/gn/location.h" | |
18 #include "tools/gn/path_output.h" | |
19 #include "tools/gn/scheduler.h" | |
20 #include "tools/gn/string_utils.h" | |
21 #include "tools/gn/target.h" | |
22 | |
23 namespace { | |
24 | |
25 static const char kCustomTargetSourceKey[] = "{{source}}"; | |
26 static const char kCustomTargetSourceNamePartKey[] = "{{source_name_part}}"; | |
27 | |
28 struct DefineWriter { | |
29 void operator()(const std::string& s, std::ostream& out) const { | |
30 out << " -D" << s; | |
31 } | |
32 }; | |
33 | |
34 struct IncludeWriter { | |
35 IncludeWriter(PathOutput& path_output, | |
36 const NinjaHelper& h) | |
37 : helper(h), | |
38 path_output_(path_output), | |
39 old_inhibit_quoting_(path_output.inhibit_quoting()) { | |
40 // Inhibit quoting since we'll put quotes around the whole thing ourselves. | |
41 // Since we're writing in NINJA escaping mode, this won't actually do | |
42 // anything, but I think we may need to change to shell-and-then-ninja | |
43 // escaping for this in the future. | |
44 path_output_.set_inhibit_quoting(true); | |
45 } | |
46 ~IncludeWriter() { | |
47 path_output_.set_inhibit_quoting(old_inhibit_quoting_); | |
48 } | |
49 | |
50 void operator()(const SourceDir& d, std::ostream& out) const { | |
51 out << " \"-I"; | |
52 // It's important not to include the trailing slash on directories or on | |
53 // Windows it will be a backslash and the compiler might think we're | |
54 // escaping the quote! | |
55 path_output_.WriteDir(out, d, PathOutput::DIR_NO_LAST_SLASH); | |
56 out << "\""; | |
57 } | |
58 | |
59 const NinjaHelper& helper; | |
60 PathOutput& path_output_; | |
61 bool old_inhibit_quoting_; // So we can put the PathOutput back. | |
62 }; | |
63 | |
64 } // namespace | |
65 | |
66 NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out) | |
67 : settings_(target->settings()), | |
68 target_(target), | |
69 out_(out), | |
70 path_output_(settings_->build_settings()->build_dir(), | |
71 ESCAPE_NINJA, true), | |
72 helper_(settings_->build_settings()) { | |
73 } | |
74 | |
75 NinjaTargetWriter::~NinjaTargetWriter() { | |
76 } | |
77 | |
78 void NinjaTargetWriter::Run() { | |
79 out_ << "arch = environment.x86\n"; | |
80 | |
81 if (target_->output_type() == Target::COPY_FILES) { | |
82 WriteCopyRules(); | |
83 } else if (target_->output_type() == Target::CUSTOM) { | |
84 WriteCustomRules(); | |
85 } else { | |
86 WriteCompilerVars(); | |
87 | |
88 std::vector<OutputFile> obj_files; | |
89 WriteSources(&obj_files); | |
90 | |
91 WriteLinkerStuff(obj_files); | |
92 } | |
93 } | |
94 | |
95 // static | |
96 void NinjaTargetWriter::RunAndWriteFile(const Target* target) { | |
97 if (target->output_type() == Target::NONE) | |
98 return; | |
99 | |
100 const Settings* settings = target->settings(); | |
101 NinjaHelper helper(settings->build_settings()); | |
102 | |
103 base::FilePath ninja_file(settings->build_settings()->GetFullPath( | |
104 helper.GetNinjaFileForTarget(target).GetSourceFile( | |
105 settings->build_settings()))); | |
106 | |
107 file_util::CreateDirectory(ninja_file.DirName()); | |
108 | |
109 // It's rediculously faster to write to a string and then write that to | |
110 // disk in one operation than to use an fstream here. | |
111 std::stringstream file; | |
112 if (file.fail()) { | |
113 g_scheduler->FailWithError( | |
114 Err(Location(), "Error writing ninja file.", | |
115 "Unable to open \"" + FilePathToUTF8(ninja_file) + "\"\n" | |
116 "for writing.")); | |
117 return; | |
118 } | |
119 | |
120 NinjaTargetWriter gen(target, file); | |
121 gen.Run(); | |
122 | |
123 std::string contents = file.str(); | |
124 file_util::WriteFile(ninja_file, contents.c_str(), contents.size()); | |
125 } | |
126 | |
127 void NinjaTargetWriter::WriteCopyRules() { | |
128 // The dest dir should be inside the output dir so we can just remove the | |
129 // prefix and get ninja-relative paths. | |
130 const std::string& output_dir = | |
131 settings_->build_settings()->build_dir().value(); | |
132 const std::string& dest_dir = target_->destdir().value(); | |
133 DCHECK(StartsWithASCII(dest_dir, output_dir, true)); | |
134 std::string relative_dest_dir(&dest_dir[output_dir.size()], | |
135 dest_dir.size() - output_dir.size()); | |
136 | |
137 const Target::FileList& sources = target_->sources(); | |
138 std::vector<OutputFile> dest_files; | |
139 dest_files.reserve(sources.size()); | |
140 | |
141 // Write out rules for each file copied. | |
142 for (size_t i = 0; i < sources.size(); i++) { | |
143 const SourceFile& input_file = sources[i]; | |
144 | |
145 // The files should have the same name but in the dest dir. | |
146 base::StringPiece name_part = FindFilename(&input_file.value()); | |
147 OutputFile dest_file(relative_dest_dir); | |
148 AppendStringPiece(&dest_file.value(), name_part); | |
149 | |
150 dest_files.push_back(dest_file); | |
151 | |
152 out_ << "build "; | |
153 path_output_.WriteFile(out_, dest_file); | |
154 out_ << ": copy "; | |
155 path_output_.WriteFile(out_, input_file); | |
156 out_ << std::endl; | |
157 } | |
158 | |
159 // Write out the rule for the target to copy all of them. | |
160 out_ << std::endl << "build "; | |
161 path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_)); | |
162 out_ << ": stamp"; | |
163 for (size_t i = 0; i < dest_files.size(); i++) { | |
164 out_ << " "; | |
165 path_output_.WriteFile(out_, dest_files[i]); | |
166 } | |
167 out_ << std::endl; | |
168 | |
169 // TODO(brettw) need some kind of stamp file for depending on this, as well | |
170 // as order_only=prebuild. | |
171 } | |
172 | |
173 void NinjaTargetWriter::WriteCustomRules() { | |
174 // Make a unique name for this rule. | |
175 std::string target_label = target_->label().GetUserVisibleName(true); | |
176 std::string custom_rule_name(target_label); | |
177 ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name); | |
178 custom_rule_name.append("_rule"); | |
179 | |
180 // Run the script from the dir of the BUILD file. This has no trailing | |
181 // slash. | |
182 const SourceDir& script_cd = target_->label().dir(); | |
183 std::string script_cd_to_root = InvertDir(script_cd); | |
184 if (script_cd_to_root.empty()) { | |
185 script_cd_to_root = "."; | |
186 } else { | |
187 // Remove trailing slash | |
188 DCHECK(script_cd_to_root[script_cd_to_root.size() - 1] == '/'); | |
189 script_cd_to_root.resize(script_cd_to_root.size() - 1); | |
190 } | |
191 | |
192 std::string script_relative_to_cd = | |
193 script_cd_to_root + target_->script().value(); | |
194 | |
195 bool no_sources = target_->sources().empty(); | |
196 | |
197 // Use a unique name for the response file when there are multiple build | |
198 // steps so that they don't stomp on each other. | |
199 std::string rspfile = custom_rule_name; | |
200 if (!no_sources) | |
201 rspfile += ".$unique_name"; | |
202 rspfile += ".rsp"; | |
203 | |
204 // First write the custom rule. | |
205 out_ << "rule " << custom_rule_name << std::endl; | |
206 out_ << " command = $pythonpath gyp-win-tool action-wrapper $arch " | |
207 << rspfile << " "; | |
208 path_output_.WriteDir(out_, script_cd, PathOutput::DIR_NO_LAST_SLASH); | |
209 out_ << std::endl; | |
210 out_ << " description = CUSTOM " << target_label << std::endl; | |
211 out_ << " restat = 1" << std::endl; | |
212 out_ << " rspfile = " << rspfile << std::endl; | |
213 | |
214 // The build command goes in the rsp file. | |
215 out_ << " rspfile_content = $pythonpath " << script_relative_to_cd; | |
216 for (size_t i = 0; i < target_->script_args().size(); i++) { | |
217 const std::string& arg = target_->script_args()[i]; | |
218 out_ << " "; | |
219 WriteCustomArg(arg); | |
220 } | |
221 out_ << std::endl << std::endl; | |
222 | |
223 // Precompute the common dependencies for each step. This includes the | |
224 // script itself (changing the script should force a rebuild) and any data | |
225 // files. | |
226 std::ostringstream common_deps_stream; | |
227 path_output_.WriteFile(common_deps_stream, target_->script()); | |
228 const Target::FileList& datas = target_->data(); | |
229 for (size_t i = 0; i < datas.size(); i++) { | |
230 common_deps_stream << " "; | |
231 path_output_.WriteFile(common_deps_stream, datas[i]); | |
232 } | |
233 const std::string& common_deps = common_deps_stream.str(); | |
234 | |
235 // Collects all output files for writing below. | |
236 std::vector<OutputFile> output_files; | |
237 | |
238 if (no_sources) { | |
239 // No sources, write a rule that invokes the script once with the | |
240 // outputs as outputs, and the data as inputs. | |
241 out_ << "build"; | |
242 const Target::FileList& outputs = target_->outputs(); | |
243 for (size_t i = 0; i < outputs.size(); i++) { | |
244 OutputFile output_path( | |
245 RemovePrefix(outputs[i].value(), | |
246 settings_->build_settings()->build_dir().value())); | |
247 output_files.push_back(output_path); | |
248 out_ << " "; | |
249 path_output_.WriteFile(out_, output_path); | |
250 } | |
251 out_ << ": " << custom_rule_name << " " << common_deps << std::endl; | |
252 } else { | |
253 // Write separate rules for each input source file. | |
254 WriteCustomSourceRules(custom_rule_name, common_deps, script_cd, | |
255 script_cd_to_root, &output_files); | |
256 } | |
257 out_ << std::endl; | |
258 | |
259 // Last write a stamp rule to collect all outputs. | |
260 out_ << "build "; | |
261 path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_)); | |
262 out_ << ": stamp"; | |
263 for (size_t i = 0; i < output_files.size(); i++) { | |
264 out_ << " "; | |
265 path_output_.WriteFile(out_, output_files[i]); | |
266 } | |
267 out_ << std::endl; | |
268 } | |
269 | |
270 void NinjaTargetWriter::WriteCustomArg(const std::string& arg) { | |
271 // This can be optimized if it's called a lot. | |
272 EscapeOptions options; | |
273 options.mode = ESCAPE_NINJA; | |
274 std::string output_str = EscapeString(arg, options); | |
275 | |
276 // Do this substitution after escaping our our $ will be escaped (which we | |
277 // don't want). | |
278 ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSource, | |
279 "${source}"); | |
280 ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSourceNamePart, | |
281 "${source_name_part}"); | |
282 out_ << output_str; | |
283 } | |
284 | |
285 void NinjaTargetWriter::WriteCustomSourceRules( | |
286 const std::string& custom_rule_name, | |
287 const std::string& common_deps, | |
288 const SourceDir& script_cd, | |
289 const std::string& script_cd_to_root, | |
290 std::vector<OutputFile>* output_files) { | |
291 // Construct the template for generating the output files from each source. | |
292 const Target::FileList& outputs = target_->outputs(); | |
293 std::vector<std::string> output_template_args; | |
294 for (size_t i = 0; i < outputs.size(); i++) { | |
295 // All outputs should be in the output dir. | |
296 output_template_args.push_back( | |
297 RemovePrefix(outputs[i].value(), | |
298 settings_->build_settings()->build_dir().value())); | |
299 } | |
300 FileTemplate output_template(output_template_args); | |
301 | |
302 // Prevent re-allocating each time by initializing outside the loop. | |
303 std::vector<std::string> output_template_result; | |
304 | |
305 // Path output formatter for wrigin source paths passed to the script. | |
306 PathOutput script_source_path_output(script_cd, ESCAPE_SHELL, true); | |
307 | |
308 const Target::FileList& sources = target_->sources(); | |
309 for (size_t i = 0; i < sources.size(); i++) { | |
310 // Write outputs for this source file computed by the template. | |
311 out_ << "build"; | |
312 output_template.ApplyString(sources[i].value(), &output_template_result); | |
313 for (size_t out_i = 0; out_i < output_template_result.size(); out_i++) { | |
314 OutputFile output_path(output_template_result[out_i]); | |
315 output_files->push_back(output_path); | |
316 out_ << " "; | |
317 path_output_.WriteFile(out_, output_path); | |
318 } | |
319 | |
320 out_ << ": " << custom_rule_name | |
321 << " " << common_deps | |
322 << " "; | |
323 path_output_.WriteFile(out_, sources[i]); | |
324 out_ << std::endl; | |
325 | |
326 out_ << " unique_name = " << i << std::endl; | |
327 | |
328 // The source file here should be relative to the script directory since | |
329 // this is the variable passed to the script. Here we slightly abuse the | |
330 // OutputFile object by putting a non-output-relative path in it to signal | |
331 // that the PathWriter should not prepend directories. | |
332 out_ << " source = "; | |
333 script_source_path_output.WriteFile(out_, sources[i]); | |
334 out_ << std::endl; | |
335 | |
336 out_ << " source_name_part = " | |
337 << FindFilenameNoExtension(&sources[i].value()).as_string() | |
338 << std::endl; | |
339 } | |
340 } | |
341 | |
342 void NinjaTargetWriter::WriteCompilerVars() { | |
343 // Defines. | |
344 out_ << "defines ="; | |
345 RecursiveTargetConfigToStream(target_, &ConfigValues::defines, | |
346 DefineWriter(), out_); | |
347 out_ << std::endl; | |
348 | |
349 // Includes. | |
350 out_ << "includes ="; | |
351 RecursiveTargetConfigToStream(target_, &ConfigValues::includes, | |
352 IncludeWriter(path_output_, helper_), out_); | |
353 | |
354 out_ << std::endl; | |
355 | |
356 // C flags and friends. | |
357 out_ << "cflags ="; | |
358 RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags, out_); | |
359 out_ << std::endl; | |
360 out_ << "cflags_c ="; | |
361 RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, out_); | |
362 out_ << std::endl; | |
363 out_ << "cflags_cc ="; | |
364 RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc, out_); | |
365 out_ << std::endl; | |
366 | |
367 out_ << std::endl; | |
368 } | |
369 | |
370 void NinjaTargetWriter::WriteSources( | |
371 std::vector<OutputFile>* object_files) { | |
372 const Target::FileList& sources = target_->sources(); | |
373 object_files->reserve(sources.size()); | |
374 | |
375 for (size_t i = 0; i < sources.size(); i++) { | |
376 const SourceFile& input_file = sources[i]; | |
377 | |
378 SourceFileType input_file_type = GetSourceFileType(input_file, | |
379 settings_->target_os()); | |
380 if (input_file_type == SOURCE_UNKNOWN) | |
381 continue; // Skip unknown file types. | |
382 const char* command = GetCommandForSourceType(input_file_type); | |
383 if (!command) | |
384 continue; // Skip files not needing compilation. | |
385 | |
386 OutputFile output_file = helper_.GetOutputFileForSource( | |
387 target_, input_file, input_file_type); | |
388 object_files->push_back(output_file); | |
389 | |
390 out_ << "build "; | |
391 path_output_.WriteFile(out_, output_file); | |
392 out_ << ": " << command << " "; | |
393 path_output_.WriteFile(out_, input_file); | |
394 out_ << std::endl; | |
395 } | |
396 out_ << std::endl; | |
397 } | |
398 | |
399 void NinjaTargetWriter::WriteLinkerStuff( | |
400 const std::vector<OutputFile>& object_files) { | |
401 // Manifest file on Windows. | |
402 // TODO(brettw) this seems not to be necessary for static libs, skip in | |
403 // that case? | |
404 OutputFile windows_manifest; | |
405 if (settings_->IsWin()) { | |
406 windows_manifest.value().assign(helper_.GetTargetOutputDir(target_)); | |
407 windows_manifest.value().append(target_->label().name()); | |
408 windows_manifest.value().append(".intermediate.manifest"); | |
409 out_ << "manifests = "; | |
410 path_output_.WriteFile(out_, windows_manifest); | |
411 out_ << std::endl; | |
412 } | |
413 | |
414 // Linker flags, append manifest flag on Windows to reference our file. | |
415 out_ << "ldflags ="; | |
416 RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags, out_); | |
417 if (settings_->IsWin()) | |
418 out_ << " /MANIFEST /ManifestFile:"; | |
419 path_output_.WriteFile(out_, windows_manifest); | |
420 { // HACK ERASEME BRETTW FIXME | |
421 out_ << " /DEBUG /MACHINE:X86 /LIBPATH:\"C:\\Program Files (x86)\\Windows Ki
ts\\8.0\\Lib\\win8\\um\\x86\" /DELAYLOAD:dbghelp.dll /DELAYLOAD:dwmapi.dll /DELA
YLOAD:shell32.dll /DELAYLOAD:uxtheme.dll /safeseh /dynamicbase /ignore:4199 /ign
ore:4221 /nxcompat /SUBSYSTEM:CONSOLE /INCREMENTAL /FIXED:NO /DYNAMICBASE:NO win
inet.lib dnsapi.lib version.lib msimg32.lib ws2_32.lib usp10.lib psapi.lib dbghe
lp.lib winmm.lib shlwapi.lib kernel32.lib gdi32.lib winspool.lib comdlg32.lib ad
vapi32.lib shell32.lib ole32.lib oleaut32.lib user32.lib uuid.lib odbc32.lib odb
ccp32.lib delayimp.lib /NXCOMPAT"; | |
422 } | |
423 out_ << std::endl; | |
424 | |
425 // Libraries to link. | |
426 out_ << "libs =" << std::endl; | |
427 | |
428 // The external output file is the one that other libs depend on. | |
429 OutputFile external_output_file = helper_.GetTargetOutputFile(target_); | |
430 | |
431 // The internal output file is the "main thing" we think we're making. In | |
432 // the case of shared libraries, this is the shared library and the external | |
433 // output file is the import library. In other cases, the internal one and | |
434 // the external one are the same. | |
435 OutputFile internal_output_file; | |
436 if (target_->output_type() == Target::SHARED_LIBRARY) { | |
437 if (settings_->IsWin()) { | |
438 internal_output_file = OutputFile(target_->label().name() + ".dll"); | |
439 } else { | |
440 NOTREACHED(); // TODO(brettw) write this. | |
441 } | |
442 } else { | |
443 internal_output_file = external_output_file; | |
444 } | |
445 | |
446 // TODO(brettw) should we append data files to this? | |
447 | |
448 // In Python see "self.ninja.build(output, command, input," | |
449 out_ << "build "; | |
450 path_output_.WriteFile(out_, internal_output_file); | |
451 if (external_output_file != internal_output_file) { | |
452 out_ << " "; | |
453 path_output_.WriteFile(out_, external_output_file); | |
454 } | |
455 out_ << ": " << GetCommandForTargetType(); | |
456 for (size_t i = 0; i < object_files.size(); i++) { | |
457 out_ << " "; | |
458 path_output_.WriteFile(out_, object_files[i]); | |
459 } | |
460 | |
461 if (target_->output_type() == Target::EXECUTABLE || | |
462 target_->output_type() == Target::SHARED_LIBRARY || | |
463 target_->output_type() == Target::LOADABLE_MODULE) { | |
464 const std::vector<const Target*>& deps = target_->deps(); | |
465 const std::set<const Target*>& inherited = target_->inherited_libraries(); | |
466 | |
467 // Now append linkable libraries to the linker command. | |
468 for (size_t i = 0; i < deps.size(); i++) { | |
469 if (deps[i]->IsLinkable() && | |
470 inherited.find(deps[i]) == inherited.end()) { | |
471 out_ << " "; | |
472 path_output_.WriteFile(out_, | |
473 helper_.GetTargetOutputFile(target_->deps()[i])); | |
474 } | |
475 } | |
476 for (std::set<const Target*>::const_iterator i = inherited.begin(); | |
477 i != inherited.end(); ++i) { | |
478 out_ << " "; | |
479 path_output_.WriteFile(out_, helper_.GetTargetOutputFile(*i)); | |
480 } | |
481 } | |
482 out_ << std::endl; | |
483 | |
484 if (target_->output_type() == Target::SHARED_LIBRARY) { | |
485 out_ << " soname = "; | |
486 path_output_.WriteFile(out_, internal_output_file); | |
487 out_ << std::endl; | |
488 | |
489 out_ << " lib = "; | |
490 path_output_.WriteFile(out_, internal_output_file); | |
491 out_ << std::endl; | |
492 | |
493 out_ << " dll = "; | |
494 path_output_.WriteFile(out_, internal_output_file); | |
495 out_ << std::endl; | |
496 | |
497 if (settings_->IsWin()) { | |
498 out_ << " implibflag = /IMPLIB:"; | |
499 path_output_.WriteFile(out_, external_output_file); | |
500 out_ << std::endl; | |
501 } | |
502 } | |
503 | |
504 // TODO(brettw) postbuild steps here. | |
505 | |
506 out_ << std::endl; | |
507 } | |
508 | |
509 const char* NinjaTargetWriter::GetCommandForSourceType( | |
510 SourceFileType type) const { | |
511 if (type == SOURCE_C) | |
512 return "cc"; | |
513 if (type == SOURCE_CC) | |
514 return "cxx"; | |
515 | |
516 // TODO(brettw) asm files. | |
517 | |
518 if (settings_->IsMac()) { | |
519 if (type == SOURCE_M) | |
520 return "objc"; | |
521 if (type == SOURCE_MM) | |
522 return "objcxx"; | |
523 } | |
524 | |
525 if (settings_->IsWin()) { | |
526 if (type == SOURCE_RC) | |
527 return "rc"; | |
528 } | |
529 | |
530 // TODO(brettw) stuff about "S" files on non-Windows. | |
531 return NULL; | |
532 } | |
533 | |
534 const char* NinjaTargetWriter::GetCommandForTargetType() const { | |
535 if (target_->output_type() == Target::NONE) { | |
536 NOTREACHED(); | |
537 return ""; | |
538 } | |
539 | |
540 if (target_->output_type() == Target::STATIC_LIBRARY) { | |
541 // TODO(brettw) stuff about standalong static libraryes on Unix in | |
542 // WriteTarget in the Python one, and lots of postbuild steps. | |
543 return "alink"; | |
544 } | |
545 | |
546 if (target_->output_type() == Target::SHARED_LIBRARY) | |
547 return "solink"; | |
548 | |
549 return "link"; | |
550 } | |
OLD | NEW |