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/gyp_binary_target_writer.h"
6
7#include <set>
8
9#include "base/logging.h"
10#include "tools/gn/builder_record.h"
11#include "tools/gn/config_values_extractors.h"
12#include "tools/gn/err.h"
13#include "tools/gn/escape.h"
14#include "tools/gn/filesystem_utils.h"
15#include "tools/gn/settings.h"
16#include "tools/gn/target.h"
17
18namespace {
19
20// This functor is used to capture the output of RecursiveTargetConfigToStream
21// in an vector.
22template<typename T>
23struct Accumulator {
24  Accumulator(std::vector<T>* result_in) : result(result_in) {}
25
26  void operator()(const T& s, std::ostream&) const {
27    result->push_back(s);
28  }
29
30  std::vector<T>* result;
31};
32
33// Writes the given array values. The array should already be declared with the
34// opening "[" written to the output. The function will not write the
35// terminating "]" either.
36void WriteArrayValues(std::ostream& out,
37                      const std::vector<std::string>& values) {
38  EscapeOptions options;
39  options.mode = ESCAPE_JSON;
40  for (size_t i = 0; i < values.size(); i++) {
41    out << " '";
42    EscapeStringToStream(out, values[i], options);
43    out << "',";
44  }
45}
46
47// Returns the value from the already-filled in cflags_* for the optimization
48// level to set in the GYP file. Additionally, this removes the flag from the
49// given vector so we don't get duplicates.
50std::string GetVCOptimization(std::vector<std::string>* cflags) {
51  // Searches for the "/O?" option and returns the corresponding GYP value.
52  for (size_t i = 0; i < cflags->size(); i++) {
53    const std::string& cur = (*cflags)[i];
54    if (cur.size() == 3 && cur[0] == '/' && cur[1] == 'O') {
55      char level = cur[2];
56      cflags->erase(cflags->begin() + i);  // Invalidates |cur|!
57      switch (level) {
58        case 'd': return "'0'";
59        case '1': return "'1'";
60        case '2': return "'2'";
61        case 'x': return "'3'";
62        default:  return "'2'";
63      }
64    }
65  }
66  return "'2'";  // Default value.
67}
68
69// Finds all values from the given getter from all configs in the given list,
70// and adds them to the given result vector.
71template<typename T>
72void FillConfigListValues(
73    const LabelConfigVector& configs,
74    const std::vector<T>& (ConfigValues::* getter)() const,
75    std::vector<T>* result) {
76  for (size_t config_i = 0; config_i < configs.size(); config_i++) {
77    const std::vector<T>& values =
78        (configs[config_i].ptr->config_values().*getter)();
79    for (size_t val_i = 0; val_i < values.size(); val_i++)
80      result->push_back(values[val_i]);
81  }
82}
83
84}  // namespace
85
86GypBinaryTargetWriter::Flags::Flags() {}
87GypBinaryTargetWriter::Flags::~Flags() {}
88
89GypBinaryTargetWriter::GypBinaryTargetWriter(const TargetGroup& group,
90                                             const Toolchain* debug_toolchain,
91                                             const SourceDir& gyp_dir,
92                                             std::ostream& out)
93    : GypTargetWriter(group.debug->item()->AsTarget(), debug_toolchain,
94                      gyp_dir, out),
95      group_(group) {
96}
97
98GypBinaryTargetWriter::~GypBinaryTargetWriter() {
99}
100
101void GypBinaryTargetWriter::Run() {
102  int indent = 4;
103
104  Indent(indent) << "{\n";
105
106  WriteName(indent + kExtraIndent);
107  WriteType(indent + kExtraIndent);
108
109  if (target_->settings()->IsLinux())
110    WriteLinuxConfiguration(indent + kExtraIndent);
111  else if (target_->settings()->IsWin())
112    WriteVCConfiguration(indent + kExtraIndent);
113  else if (target_->settings()->IsMac())
114    WriteMacConfiguration(indent + kExtraIndent);
115  WriteDirectDependentSettings(indent + kExtraIndent);
116  WriteAllDependentSettings(indent + kExtraIndent);
117
118  Indent(indent) << "},\n";
119}
120
121void GypBinaryTargetWriter::WriteName(int indent) {
122  std::string name = helper_.GetNameForTarget(target_);
123  Indent(indent) << "'target_name': '" << name << "',\n";
124
125  std::string product_name;
126  if (target_->output_name().empty())
127    product_name = target_->label().name();
128  else
129    product_name = name;
130
131  // TODO(brettw) GN knows not to prefix targets starting with "lib" with
132  // another "lib" on Linux, but GYP doesn't. We need to rename applicable
133  // targets here.
134
135  Indent(indent) << "'product_name': '" << product_name << "',\n";
136}
137
138void GypBinaryTargetWriter::WriteType(int indent) {
139  Indent(indent) << "'type': ";
140  switch (target_->output_type()) {
141    case Target::EXECUTABLE:
142      out_ << "'executable',\n";
143      break;
144    case Target::STATIC_LIBRARY:
145      out_ << "'static_library',\n";
146      break;
147    case Target::SHARED_LIBRARY:
148      out_ << "'shared_library',\n";
149      break;
150    case Target::SOURCE_SET:
151      out_ << "'static_library',\n";  // TODO(brettw) fixme.
152      break;
153    default:
154      NOTREACHED();
155  }
156
157  if (target_->hard_dep())
158    Indent(indent) << "'hard_dependency': 1,\n";
159}
160
161void GypBinaryTargetWriter::WriteVCConfiguration(int indent) {
162  Indent(indent) << "'configurations': {\n";
163
164  Indent(indent + kExtraIndent) << "'Debug': {\n";
165  Flags debug_flags(FlagsFromTarget(group_.debug->item()->AsTarget()));
166  WriteVCFlags(debug_flags, indent + kExtraIndent * 2);
167  Indent(indent + kExtraIndent) << "},\n";
168
169  Indent(indent + kExtraIndent) << "'Release': {\n";
170  Flags release_flags(FlagsFromTarget(group_.release->item()->AsTarget()));
171  WriteVCFlags(release_flags, indent + kExtraIndent * 2);
172  Indent(indent + kExtraIndent) << "},\n";
173
174  // Note that we always need Debug_x64 and Release_x64 defined or GYP will get
175  // confused, but we ca leave them empty if there's no 64-bit target.
176  Indent(indent + kExtraIndent) << "'Debug_x64': {\n";
177  if (group_.debug64) {
178    Flags flags(FlagsFromTarget(group_.debug64->item()->AsTarget()));
179    WriteVCFlags(flags, indent + kExtraIndent * 2);
180  }
181  Indent(indent + kExtraIndent) << "},\n";
182
183  Indent(indent + kExtraIndent) << "'Release_x64': {\n";
184  if (group_.release64) {
185    Flags flags(FlagsFromTarget(group_.release64->item()->AsTarget()));
186    WriteVCFlags(flags, indent + kExtraIndent * 2);
187  }
188  Indent(indent + kExtraIndent) << "},\n";
189
190  Indent(indent) << "},\n";
191
192  WriteSources(target_, indent);
193  WriteDeps(target_, indent);
194}
195
196void GypBinaryTargetWriter::WriteLinuxConfiguration(int indent) {
197  // The Linux stuff works differently. On Linux we support cross-compiles and
198  // all ninja generators know to look for target conditions. Other platforms'
199  // generators don't all do this, so we can't have the same GYP structure.
200  Indent(indent) << "'target_conditions': [\n";
201  // The host toolset is configured for the current computer, we will only have
202  // this when doing cross-compiles.
203  if (group_.host_debug && group_.host_release) {
204    Indent(indent + kExtraIndent) << "['_toolset == \"host\"', {\n";
205    Indent(indent + kExtraIndent * 2) << "'configurations': {\n";
206    Indent(indent + kExtraIndent * 3) << "'Debug': {\n";
207    WriteLinuxFlagsForTarget(group_.host_debug->item()->AsTarget(),
208                             indent + kExtraIndent * 4);
209    Indent(indent + kExtraIndent * 3) << "},\n";
210    Indent(indent + kExtraIndent * 3) << "'Release': {\n";
211    WriteLinuxFlagsForTarget(group_.host_release->item()->AsTarget(),
212                             indent + kExtraIndent * 4);
213    Indent(indent + kExtraIndent * 3) << "},\n";
214    Indent(indent + kExtraIndent * 2) << "}\n";
215
216    // The sources are per-toolset but shared between debug & release.
217    WriteSources(group_.host_debug->item()->AsTarget(),
218                 indent + kExtraIndent * 2);
219
220    Indent(indent + kExtraIndent) << "],\n";
221  }
222
223  // The target toolset is the "regular" one.
224  Indent(indent + kExtraIndent) << "['_toolset == \"target\"', {\n";
225  Indent(indent + kExtraIndent * 2) << "'configurations': {\n";
226  Indent(indent + kExtraIndent * 3) << "'Debug': {\n";
227  WriteLinuxFlagsForTarget(group_.debug->item()->AsTarget(),
228                           indent + kExtraIndent * 4);
229  Indent(indent + kExtraIndent * 3) << "},\n";
230  Indent(indent + kExtraIndent * 3) << "'Release': {\n";
231  WriteLinuxFlagsForTarget(group_.release->item()->AsTarget(),
232                           indent + kExtraIndent * 4);
233  Indent(indent + kExtraIndent * 3) << "},\n";
234  Indent(indent + kExtraIndent * 2) << "},\n";
235
236  WriteSources(target_, indent + kExtraIndent * 2);
237
238  Indent(indent + kExtraIndent) << "},],\n";
239  Indent(indent) << "],\n";
240
241  // Deps in GYP can not vary based on the toolset.
242  WriteDeps(target_, indent);
243}
244
245void GypBinaryTargetWriter::WriteMacConfiguration(int indent) {
246  Indent(indent) << "'configurations': {\n";
247
248  Indent(indent + kExtraIndent) << "'Debug': {\n";
249  Flags debug_flags(FlagsFromTarget(group_.debug->item()->AsTarget()));
250  WriteMacFlags(debug_flags, indent + kExtraIndent * 2);
251  Indent(indent + kExtraIndent) << "},\n";
252
253  Indent(indent + kExtraIndent) << "'Release': {\n";
254  Flags release_flags(FlagsFromTarget(group_.release->item()->AsTarget()));
255  WriteMacFlags(release_flags, indent + kExtraIndent * 2);
256  Indent(indent + kExtraIndent) << "},\n";
257
258  Indent(indent) << "},\n";
259
260  WriteSources(target_, indent);
261  WriteDeps(target_, indent);
262}
263
264void GypBinaryTargetWriter::WriteVCFlags(Flags& flags, int indent) {
265  // Defines and includes go outside of the msvs settings.
266  WriteNamedArray("defines", flags.defines, indent);
267  WriteIncludeDirs(flags, indent);
268
269  // C flags.
270  Indent(indent) << "'msvs_settings': {\n";
271  Indent(indent + kExtraIndent) << "'VCCLCompilerTool': {\n";
272
273  // GYP always uses the VC optimization flag to add a /O? on Visual Studio.
274  // This can produce duplicate values. So look up the GYP value corresponding
275  // to the flags used, and set the same one.
276  std::string optimization = GetVCOptimization(&flags.cflags);
277  WriteNamedArray("AdditionalOptions", flags.cflags, indent + kExtraIndent * 2);
278  // TODO(brettw) cflags_c and cflags_cc!
279  Indent(indent + kExtraIndent * 2) << "'Optimization': "
280                                    << optimization << ",\n";
281  Indent(indent + kExtraIndent) << "},\n";
282
283  // Linker tool stuff.
284  Indent(indent + kExtraIndent) << "'VCLinkerTool': {\n";
285
286  // ...Library dirs.
287  EscapeOptions escape_options;
288  escape_options.mode = ESCAPE_JSON;
289  if (!flags.lib_dirs.empty()) {
290    Indent(indent + kExtraIndent * 2) << "'AdditionalLibraryDirectories': [";
291    for (size_t i = 0; i < flags.lib_dirs.size(); i++) {
292      out_ << " '";
293      EscapeStringToStream(out_,
294                           helper_.GetDirReference(flags.lib_dirs[i], false),
295                           escape_options);
296      out_ << "',";
297    }
298    out_ << " ],\n";
299  }
300
301  // ...Libraries.
302  WriteNamedArray("AdditionalDependencies", flags.libs,
303                  indent + kExtraIndent * 2);
304
305  // ...LD flags.
306  // TODO(brettw) EnableUAC defaults to on and needs to be set. Also
307  // UACExecutionLevel and UACUIAccess depends on that and defaults to 0/false.
308  WriteNamedArray("AdditionalOptions", flags.ldflags, 14);
309  Indent(indent + kExtraIndent) << "},\n";
310  Indent(indent) << "},\n";
311}
312
313void GypBinaryTargetWriter::WriteMacFlags(Flags& flags, int indent) {
314  WriteNamedArray("defines", flags.defines, indent);
315  WriteIncludeDirs(flags, indent);
316
317  // Libraries and library directories.
318  EscapeOptions escape_options;
319  escape_options.mode = ESCAPE_JSON;
320  if (!flags.lib_dirs.empty()) {
321    Indent(indent + kExtraIndent) << "'library_dirs': [";
322    for (size_t i = 0; i < flags.lib_dirs.size(); i++) {
323      out_ << " '";
324      EscapeStringToStream(out_,
325                           helper_.GetDirReference(flags.lib_dirs[i], false),
326                           escape_options);
327      out_ << "',";
328    }
329    out_ << " ],\n";
330  }
331  if (!flags.libs.empty()) {
332    Indent(indent) << "'link_settings': {\n";
333    Indent(indent + kExtraIndent) << "'libraries': [";
334    for (size_t i = 0; i < flags.libs.size(); i++) {
335      out_ << " '-l";
336      EscapeStringToStream(out_, flags.libs[i], escape_options);
337      out_ << "',";
338    }
339    out_ << " ],\n";
340    Indent(indent) << "},\n";
341  }
342
343  Indent(indent) << "'xcode_settings': {\n";
344
345  // C/C++ flags.
346  if (!flags.cflags.empty() || !flags.cflags_c.empty() ||
347      !flags.cflags_objc.empty()) {
348    Indent(indent + kExtraIndent) << "'OTHER_CFLAGS': [";
349    WriteArrayValues(out_, flags.cflags);
350    WriteArrayValues(out_, flags.cflags_c);
351    WriteArrayValues(out_, flags.cflags_objc);
352    out_ << " ],\n";
353  }
354  if (!flags.cflags.empty() || !flags.cflags_cc.empty() ||
355      !flags.cflags_objcc.empty()) {
356    Indent(indent + kExtraIndent) << "'OTHER_CPLUSPLUSFLAGS': [";
357    WriteArrayValues(out_, flags.cflags);
358    WriteArrayValues(out_, flags.cflags_cc);
359    WriteArrayValues(out_, flags.cflags_objcc);
360    out_ << " ],\n";
361  }
362
363  // Ld flags. Don't write these for static libraries. Otherwise, they'll be
364  // passed to the library tool which doesn't expect it (the toolchain does
365  // not use ldflags so these are ignored in the normal build).
366  if (target_->output_type() != Target::STATIC_LIBRARY)
367    WriteNamedArray("OTHER_LDFLAGS", flags.ldflags, indent + kExtraIndent);
368
369  // Write the compiler that XCode should use. When we're using clang, we want
370  // the custom one, otherwise don't add this and the default compiler will be
371  // used.
372  //
373  // TODO(brettw) this is a hack. We could add a way for the GN build to set
374  // these values but as far as I can see this is the only use for them, so
375  // currently we manually check the build config's is_clang value.
376  const Value* is_clang =
377      target_->settings()->base_config()->GetValue("is_clang");
378  if (is_clang && is_clang->type() == Value::BOOLEAN &&
379      is_clang->boolean_value()) {
380    base::FilePath clang_path =
381        target_->settings()->build_settings()->GetFullPath(SourceFile(
382            "//third_party/llvm-build/Release+Asserts/bin/clang"));
383    base::FilePath clang_pp_path =
384        target_->settings()->build_settings()->GetFullPath(SourceFile(
385            "//third_party/llvm-build/Release+Asserts/bin/clang++"));
386
387    Indent(indent) << "'CC': '" << FilePathToUTF8(clang_path) << "',\n";
388    Indent(indent) << "'LDPLUSPLUS': '"
389                   << FilePathToUTF8(clang_pp_path) << "',\n";
390  }
391
392  Indent(indent) << "},\n";
393}
394
395void GypBinaryTargetWriter::WriteLinuxFlagsForTarget(const Target* target,
396                                                     int indent) {
397  Flags flags(FlagsFromTarget(target));
398  WriteLinuxFlags(flags, indent);
399}
400
401void GypBinaryTargetWriter::WriteLinuxFlags(const Flags& flags, int indent) {
402  WriteIncludeDirs(flags, indent);
403  WriteNamedArray("defines",      flags.defines,      indent);
404  WriteNamedArray("cflags",       flags.cflags,       indent);
405  WriteNamedArray("cflags_c",     flags.cflags_c,     indent);
406  WriteNamedArray("cflags_cc",    flags.cflags_cc,    indent);
407  WriteNamedArray("cflags_objc",  flags.cflags_objc,  indent);
408  WriteNamedArray("cflags_objcc", flags.cflags_objcc, indent);
409
410  // Put libraries and library directories in with ldflags.
411  Indent(indent) << "'ldflags': ["; \
412  WriteArrayValues(out_, flags.ldflags);
413
414  EscapeOptions escape_options;
415  escape_options.mode = ESCAPE_JSON;
416  for (size_t i = 0; i < flags.lib_dirs.size(); i++) {
417    out_ << " '-L";
418    EscapeStringToStream(out_,
419                         helper_.GetDirReference(flags.lib_dirs[i], false),
420                         escape_options);
421    out_ << "',";
422  }
423
424  for (size_t i = 0; i < flags.libs.size(); i++) {
425    out_ << " '-l";
426    EscapeStringToStream(out_, flags.libs[i], escape_options);
427    out_ << "',";
428  }
429  out_ << " ],\n";
430}
431
432void GypBinaryTargetWriter::WriteSources(const Target* target, int indent) {
433  Indent(indent) << "'sources': [\n";
434
435  const Target::FileList& sources = target->sources();
436  for (size_t i = 0; i < sources.size(); i++) {
437    const SourceFile& input_file = sources[i];
438    Indent(indent + kExtraIndent) << "'" << helper_.GetFileReference(input_file)
439                                  << "',\n";
440  }
441
442  Indent(indent) << "],\n";
443}
444
445void GypBinaryTargetWriter::WriteDeps(const Target* target, int indent) {
446  const LabelTargetVector& deps = target->deps();
447  if (deps.empty())
448    return;
449
450  EscapeOptions escape_options;
451  escape_options.mode = ESCAPE_JSON;
452
453  Indent(indent) << "'dependencies': [\n";
454  for (size_t i = 0; i < deps.size(); i++) {
455    Indent(indent + kExtraIndent) << "'";
456    EscapeStringToStream(out_, helper_.GetFullRefForTarget(deps[i].ptr),
457                         escape_options);
458    out_ << "',\n";
459  }
460  Indent(indent) << "],\n";
461}
462
463void GypBinaryTargetWriter::WriteIncludeDirs(const Flags& flags, int indent) {
464  if (flags.include_dirs.empty())
465    return;
466
467  EscapeOptions options;
468  options.mode = ESCAPE_JSON;
469
470  Indent(indent) << "'include_dirs': [";
471  for (size_t i = 0; i < flags.include_dirs.size(); i++) {
472    out_ << " '";
473    EscapeStringToStream(out_,
474                         helper_.GetDirReference(flags.include_dirs[i], false),
475                         options);
476    out_ << "',";
477  }
478  out_ << " ],\n";
479}
480
481void GypBinaryTargetWriter::WriteDirectDependentSettings(int indent) {
482  if (target_->direct_dependent_configs().empty())
483    return;
484  Indent(indent) << "'direct_dependent_settings': {\n";
485
486  Flags flags(FlagsFromConfigList(target_->direct_dependent_configs()));
487  if (target_->settings()->IsLinux())
488    WriteLinuxFlags(flags, indent + kExtraIndent);
489  else if (target_->settings()->IsWin())
490    WriteVCFlags(flags, indent + kExtraIndent);
491  else if (target_->settings()->IsMac())
492    WriteMacFlags(flags, indent + kExtraIndent);
493  Indent(indent) << "},\n";
494}
495
496void GypBinaryTargetWriter::WriteAllDependentSettings(int indent) {
497  if (target_->all_dependent_configs().empty())
498    return;
499  Indent(indent) << "'all_dependent_settings': {\n";
500
501  Flags flags(FlagsFromConfigList(target_->all_dependent_configs()));
502  if (target_->settings()->IsLinux())
503    WriteLinuxFlags(flags, indent + kExtraIndent);
504  else if (target_->settings()->IsWin())
505    WriteVCFlags(flags, indent + kExtraIndent);
506  else if (target_->settings()->IsMac())
507    WriteMacFlags(flags, indent + kExtraIndent);
508  Indent(indent) << "},\n";
509}
510
511GypBinaryTargetWriter::Flags GypBinaryTargetWriter::FlagsFromTarget(
512    const Target* target) const {
513  Flags ret;
514
515  // Extracts a vector of the given type and name from the config values.
516  #define EXTRACT(type, name) \
517      { \
518        Accumulator<type> acc(&ret.name); \
519        RecursiveTargetConfigToStream<type>(target, &ConfigValues::name, \
520                                            acc, out_); \
521      }
522
523  EXTRACT(std::string, defines);
524  EXTRACT(SourceDir,   include_dirs);
525  EXTRACT(std::string, cflags);
526  EXTRACT(std::string, cflags_c);
527  EXTRACT(std::string, cflags_cc);
528  EXTRACT(std::string, cflags_objc);
529  EXTRACT(std::string, cflags_objcc);
530  EXTRACT(std::string, ldflags);
531
532  #undef EXTRACT
533
534  const OrderedSet<SourceDir>& all_lib_dirs = target->all_lib_dirs();
535  for (size_t i = 0; i < all_lib_dirs.size(); i++)
536    ret.lib_dirs.push_back(all_lib_dirs[i]);
537
538  const OrderedSet<std::string> all_libs = target->all_libs();
539  for (size_t i = 0; i < all_libs.size(); i++)
540    ret.libs.push_back(all_libs[i]);
541
542  return ret;
543}
544
545GypBinaryTargetWriter::Flags GypBinaryTargetWriter::FlagsFromConfigList(
546    const LabelConfigVector& configs) const {
547  Flags ret;
548
549  #define EXTRACT(type, name) \
550      FillConfigListValues<type>(configs, &ConfigValues::name, &ret.name);
551
552  EXTRACT(std::string, defines);
553  EXTRACT(SourceDir,   include_dirs);
554  EXTRACT(std::string, cflags);
555  EXTRACT(std::string, cflags_c);
556  EXTRACT(std::string, cflags_cc);
557  EXTRACT(std::string, cflags_objc);
558  EXTRACT(std::string, cflags_objcc);
559  EXTRACT(std::string, ldflags);
560  EXTRACT(SourceDir,   lib_dirs);
561  EXTRACT(std::string, libs);
562
563  #undef EXTRACT
564
565  return ret;
566}
567
568void GypBinaryTargetWriter::WriteNamedArray(
569    const char* name,
570    const std::vector<std::string>& values,
571    int indent) {
572  if (values.empty())
573    return;
574
575  EscapeOptions options;
576  options.mode = ESCAPE_JSON;
577
578  Indent(indent) << "'" << name << "': [";
579  WriteArrayValues(out_, values);
580  out_ << " ],\n";
581}
582
583