Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(163)

Side by Side Diff: tools/gn/ninja_target_writer.cc

Issue 21114002: Add initial prototype for the GN meta-buildsystem. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: add owners and readme Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/gn/ninja_target_writer.h ('k') | tools/gn/ninja_toolchain_writer.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « tools/gn/ninja_target_writer.h ('k') | tools/gn/ninja_toolchain_writer.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698