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
18 namespace {
19
20 // This functor is used to capture the output of RecursiveTargetConfigToStream
21 // in an vector.
22 template<typename T>
23 struct Accumulator {
Accumulator__anon3f561a830111::Accumulator24 Accumulator(std::vector<T>* result_in) : result(result_in) {}
25
operator ()__anon3f561a830111::Accumulator26 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.
WriteArrayValues(std::ostream & out,const std::vector<std::string> & values)36 void 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.
GetVCOptimization(std::vector<std::string> * cflags)50 std::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.
71 template<typename T>
FillConfigListValues(const LabelConfigVector & configs,const std::vector<T> & (ConfigValues::* getter)()const,std::vector<T> * result)72 void 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
Flags()86 GypBinaryTargetWriter::Flags::Flags() {}
~Flags()87 GypBinaryTargetWriter::Flags::~Flags() {}
88
GypBinaryTargetWriter(const TargetGroup & group,const Toolchain * debug_toolchain,const SourceDir & gyp_dir,std::ostream & out)89 GypBinaryTargetWriter::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
~GypBinaryTargetWriter()98 GypBinaryTargetWriter::~GypBinaryTargetWriter() {
99 }
100
Run()101 void 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
WriteName(int indent)121 void 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
WriteType(int indent)138 void 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
WriteVCConfiguration(int indent)161 void 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
WriteLinuxConfiguration(int indent)196 void 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
WriteMacConfiguration(int indent)245 void 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
WriteVCFlags(Flags & flags,int indent)264 void 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
WriteMacFlags(Flags & flags,int indent)313 void 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
WriteLinuxFlagsForTarget(const Target * target,int indent)395 void GypBinaryTargetWriter::WriteLinuxFlagsForTarget(const Target* target,
396 int indent) {
397 Flags flags(FlagsFromTarget(target));
398 WriteLinuxFlags(flags, indent);
399 }
400
WriteLinuxFlags(const Flags & flags,int indent)401 void 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
WriteSources(const Target * target,int indent)432 void 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
WriteDeps(const Target * target,int indent)445 void 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
WriteIncludeDirs(const Flags & flags,int indent)463 void 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
WriteDirectDependentSettings(int indent)481 void 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
WriteAllDependentSettings(int indent)496 void 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
FlagsFromTarget(const Target * target) const511 GypBinaryTargetWriter::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
FlagsFromConfigList(const LabelConfigVector & configs) const545 GypBinaryTargetWriter::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
WriteNamedArray(const char * name,const std::vector<std::string> & values,int indent)568 void 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