1 // Copyright (c) 2016 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 "gn/json_project_writer.h"
6
7 #include <algorithm>
8 #include <fstream>
9 #include <memory>
10 #include <unordered_map>
11 #include <vector>
12
13 #include "base/command_line.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/json/json_writer.h"
17 #include "base/json/string_escape.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "gn/builder.h"
20 #include "gn/commands.h"
21 #include "gn/deps_iterator.h"
22 #include "gn/desc_builder.h"
23 #include "gn/exec_process.h"
24 #include "gn/filesystem_utils.h"
25 #include "gn/scheduler.h"
26 #include "gn/settings.h"
27 #include "gn/string_output_buffer.h"
28
29 // Structure of JSON output file
30 // {
31 // "build_settings" : {
32 // "root_path" : "absolute path of project root",
33 // "build_dir" : "build directory (project relative)",
34 // "default_toolchain" : "name of default toolchain"
35 // }
36 // "targets" : {
37 // "target x full label" : { target x properties },
38 // "target y full label" : { target y properties },
39 // ...
40 // }
41 // }
42 // See desc_builder.cc for overview of target properties
43
44 namespace {
45
AddTargetDependencies(const Target * target,TargetSet * deps)46 void AddTargetDependencies(const Target* target, TargetSet* deps) {
47 for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
48 if (deps->add(pair.ptr)) {
49 AddTargetDependencies(pair.ptr, deps);
50 }
51 }
52 }
53
54 // Filters targets according to filter string; Will also recursively
55 // add dependent targets.
FilterTargets(const BuildSettings * build_settings,std::vector<const Target * > & all_targets,std::vector<const Target * > * targets,const std::string & dir_filter_string,Err * err)56 bool FilterTargets(const BuildSettings* build_settings,
57 std::vector<const Target*>& all_targets,
58 std::vector<const Target*>* targets,
59 const std::string& dir_filter_string,
60 Err* err) {
61 if (dir_filter_string.empty()) {
62 *targets = all_targets;
63 } else {
64 targets->reserve(all_targets.size());
65 std::vector<LabelPattern> filters;
66 if (!commands::FilterPatternsFromString(build_settings, dir_filter_string,
67 &filters, err)) {
68 return false;
69 }
70 commands::FilterTargetsByPatterns(all_targets, filters, targets);
71
72 TargetSet target_set(targets->begin(), targets->end());
73 for (const auto* target : *targets)
74 AddTargetDependencies(target, &target_set);
75
76 targets->clear();
77 targets->insert(targets->end(), target_set.begin(), target_set.end());
78 }
79
80 // Sort the list of targets per-label to get a consistent ordering of them
81 // in the generated project (and thus stability of the file generated).
82 std::sort(targets->begin(), targets->end(),
83 [](const Target* a, const Target* b) {
84 return a->label().name() < b->label().name();
85 });
86
87 return true;
88 }
89
InvokePython(const BuildSettings * build_settings,const base::FilePath & python_script_path,const std::string & python_script_extra_args,const base::FilePath & output_path,bool quiet,Err * err)90 bool InvokePython(const BuildSettings* build_settings,
91 const base::FilePath& python_script_path,
92 const std::string& python_script_extra_args,
93 const base::FilePath& output_path,
94 bool quiet,
95 Err* err) {
96 const base::FilePath& python_path = build_settings->python_path();
97 base::CommandLine cmdline(python_path);
98 cmdline.AppendArg("--");
99 cmdline.AppendArgPath(python_script_path);
100 cmdline.AppendArgPath(output_path);
101 if (!python_script_extra_args.empty()) {
102 cmdline.AppendArg(python_script_extra_args);
103 }
104 base::FilePath startup_dir =
105 build_settings->GetFullPath(build_settings->build_dir());
106
107 std::string output;
108 std::string stderr_output;
109
110 int exit_code = 0;
111 if (!internal::ExecProcess(cmdline, startup_dir, &output, &stderr_output,
112 &exit_code)) {
113 *err =
114 Err(Location(), "Could not execute python.",
115 "I was trying to execute \"" + FilePathToUTF8(python_path) + "\".");
116 return false;
117 }
118
119 if (!quiet) {
120 printf("%s", output.c_str());
121 fprintf(stderr, "%s", stderr_output.c_str());
122 }
123
124 if (exit_code != 0) {
125 *err = Err(Location(), "Python has quit with exit code " +
126 base::IntToString(exit_code) + ".");
127 return false;
128 }
129
130 return true;
131 }
132
133 } // namespace
134
RunAndWriteFiles(const BuildSettings * build_settings,const Builder & builder,const std::string & file_name,const std::string & exec_script,const std::string & exec_script_extra_args,const std::string & dir_filter_string,bool quiet,Err * err)135 bool JSONProjectWriter::RunAndWriteFiles(
136 const BuildSettings* build_settings,
137 const Builder& builder,
138 const std::string& file_name,
139 const std::string& exec_script,
140 const std::string& exec_script_extra_args,
141 const std::string& dir_filter_string,
142 bool quiet,
143 Err* err) {
144 SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
145 Value(nullptr, file_name), err);
146 if (output_file.is_null()) {
147 return false;
148 }
149
150 base::FilePath output_path = build_settings->GetFullPath(output_file);
151
152 std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
153 std::vector<const Target*> targets;
154 if (!FilterTargets(build_settings, all_targets, &targets, dir_filter_string,
155 err)) {
156 return false;
157 }
158
159 StringOutputBuffer json = GenerateJSON(build_settings, targets);
160 if (!json.ContentsEqual(output_path)) {
161 if (!json.WriteToFile(output_path, err)) {
162 return false;
163 }
164
165 if (!exec_script.empty()) {
166 SourceFile script_file;
167 if (exec_script[0] != '/') {
168 // Relative path, assume the base is in build_dir.
169 script_file = build_settings->build_dir().ResolveRelativeFile(
170 Value(nullptr, exec_script), err);
171 if (script_file.is_null()) {
172 return false;
173 }
174 } else {
175 script_file = SourceFile(exec_script);
176 }
177 base::FilePath script_path = build_settings->GetFullPath(script_file);
178 return InvokePython(build_settings, script_path, exec_script_extra_args,
179 output_path, quiet, err);
180 }
181 }
182
183 return true;
184 }
185
186 namespace {
187
188 // NOTE: Intentional macro definition allows compile-time string concatenation.
189 // (see usage below).
190 #if defined(OS_WINDOWS)
191 #define LINE_ENDING "\r\n"
192 #else
193 #define LINE_ENDING "\n"
194 #endif
195
196 // Helper class to output a, potentially very large, JSON file to a
197 // StringOutputBuffer. Note that sorting the keys, if desired, is left to
198 // the user (unlike base::JSONWriter). This allows rendering to be performed
199 // in series of incremental steps. Usage is:
200 //
201 // 1) Create instance, passing a StringOutputBuffer reference as the
202 // destination.
203 //
204 // 2) Add keys and values using one of the following:
205 //
206 // a) AddString(key, string_value) to add one string value.
207 //
208 // b) BeginList(key), AddListItem(), EndList() to add a string list.
209 // NOTE: Only lists of strings are supported here.
210 //
211 // c) BeginDict(key), ... add other keys, followed by EndDict() to add
212 // a dictionary key.
213 //
214 // 3) Call Close() or destroy the instance to finalize the output.
215 //
216 class SimpleJSONWriter {
217 public:
218 // Constructor.
SimpleJSONWriter(StringOutputBuffer & out)219 SimpleJSONWriter(StringOutputBuffer& out) : out_(out) {
220 out_ << "{" LINE_ENDING;
221 SetIndentation(1u);
222 }
223
224 // Destructor.
~SimpleJSONWriter()225 ~SimpleJSONWriter() { Close(); }
226
227 // Closing finalizes the output.
Close()228 void Close() {
229 if (indentation_ > 0) {
230 DCHECK(indentation_ == 1u);
231 if (comma_.size())
232 out_ << LINE_ENDING;
233
234 out_ << "}" LINE_ENDING;
235 SetIndentation(0);
236 }
237 }
238
239 // Add new string-valued key.
AddString(std::string_view key,std::string_view value)240 void AddString(std::string_view key, std::string_view value) {
241 if (comma_.size()) {
242 out_ << comma_;
243 }
244 AddMargin() << Escape(key) << ": " << Escape(value);
245 comma_ = "," LINE_ENDING;
246 }
247
248 // Begin a new list. Must be followed by zero or more AddListItem() calls,
249 // then by EndList().
BeginList(std::string_view key)250 void BeginList(std::string_view key) {
251 if (comma_.size())
252 out_ << comma_;
253 AddMargin() << Escape(key) << ": [ ";
254 comma_ = {};
255 }
256
257 // Add a new list item. For now only string values are supported.
AddListItem(std::string_view item)258 void AddListItem(std::string_view item) {
259 if (comma_.size())
260 out_ << comma_;
261 out_ << Escape(item);
262 comma_ = ", ";
263 }
264
265 // End current list.
EndList()266 void EndList() {
267 out_ << " ]";
268 comma_ = "," LINE_ENDING;
269 }
270
271 // Begin new dictionaary. Must be followed by zero or more other key
272 // additions, then a call to EndDict().
BeginDict(std::string_view key)273 void BeginDict(std::string_view key) {
274 if (comma_.size())
275 out_ << comma_;
276
277 AddMargin() << Escape(key) << ": {";
278 SetIndentation(indentation_ + 1);
279 comma_ = LINE_ENDING;
280 }
281
282 // End current dictionary.
EndDict()283 void EndDict() {
284 if (comma_.size())
285 out_ << LINE_ENDING;
286
287 SetIndentation(indentation_ - 1);
288 AddMargin() << "}";
289 comma_ = "," LINE_ENDING;
290 }
291
292 // Add a dictionary-valued key, whose value is already formatted as a valid
293 // JSON string. Useful to insert the output of base::JSONWriter::Write()
294 // into the target buffer.
AddJSONDict(std::string_view key,std::string_view json)295 void AddJSONDict(std::string_view key, std::string_view json) {
296 if (comma_.size())
297 out_ << comma_;
298 AddMargin() << Escape(key) << ": ";
299 if (json.empty()) {
300 out_ << "{ }";
301 } else {
302 DCHECK(json[0] == '{');
303 bool first_line = true;
304 do {
305 size_t line_end = json.find('\n');
306
307 // NOTE: Do not add margin if original input line is empty.
308 // This needs to deal with CR/LF which are part of |json| on Windows
309 // only, due to the way base::JSONWriter::Write() is implemented.
310 bool line_empty = (line_end == 0 || (line_end == 1 && json[0] == '\r'));
311 if (!first_line && !line_empty)
312 AddMargin();
313
314 if (line_end == std::string_view::npos) {
315 out_ << json;
316 ;
317 comma_ = {};
318 return;
319 }
320 // Important: do not add the final newline.
321 out_ << json.substr(
322 0, (line_end == json.size() - 1) ? line_end : line_end + 1);
323 json.remove_prefix(line_end + 1);
324 first_line = false;
325 } while (!json.empty());
326 }
327 comma_ = "," LINE_ENDING;
328 }
329
330 private:
331 // Return the JSON-escape version of |str|.
Escape(std::string_view str)332 static std::string Escape(std::string_view str) {
333 std::string result;
334 base::EscapeJSONString(str, true, &result);
335 return result;
336 }
337
338 // Adjust indentation level.
SetIndentation(size_t indentation)339 void SetIndentation(size_t indentation) { indentation_ = indentation; }
340
341 // Append margin, and return reference to output buffer.
AddMargin() const342 StringOutputBuffer& AddMargin() const {
343 static const char kMargin[17] = " ";
344 size_t margin_len = indentation_ * 3;
345 while (margin_len > 0) {
346 size_t span = (margin_len > 16u) ? 16u : margin_len;
347 out_.Append(kMargin, span);
348 margin_len -= span;
349 }
350 return out_;
351 }
352
353 size_t indentation_ = 0;
354 std::string_view comma_;
355 StringOutputBuffer& out_;
356 };
357
358 } // namespace
359
GenerateJSON(const BuildSettings * build_settings,std::vector<const Target * > & all_targets)360 StringOutputBuffer JSONProjectWriter::GenerateJSON(
361 const BuildSettings* build_settings,
362 std::vector<const Target*>& all_targets) {
363 Label default_toolchain_label;
364 if (!all_targets.empty())
365 default_toolchain_label =
366 all_targets[0]->settings()->default_toolchain_label();
367
368 StringOutputBuffer out;
369
370 // Sort the targets according to their human visible labels first.
371 std::unordered_map<const Target*, std::string> target_labels;
372 for (const Target* target : all_targets) {
373 target_labels[target] =
374 target->label().GetUserVisibleName(default_toolchain_label);
375 }
376
377 std::vector<const Target*> sorted_targets(all_targets.begin(),
378 all_targets.end());
379 std::sort(sorted_targets.begin(), sorted_targets.end(),
380 [&target_labels](const Target* a, const Target* b) {
381 return target_labels[a] < target_labels[b];
382 });
383
384 SimpleJSONWriter json_writer(out);
385
386 // IMPORTANT: Keep the keys sorted when adding them to |json_writer|.
387
388 json_writer.BeginDict("build_settings");
389 {
390 json_writer.AddString("build_dir", build_settings->build_dir().value());
391
392 json_writer.AddString("default_toolchain",
393 default_toolchain_label.GetUserVisibleName(false));
394
395 json_writer.BeginList("gen_input_files");
396
397 // Other files read by the build.
398 std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
399
400 const InputFileManager* input_file_manager =
401 g_scheduler->input_file_manager();
402
403 VectorSetSorter<base::FilePath> sorter(
404 input_file_manager->GetInputFileCount() + other_files.size());
405
406 input_file_manager->AddAllPhysicalInputFileNamesToVectorSetSorter(&sorter);
407
408 sorter.Add(other_files.begin(), other_files.end());
409
410 std::string build_path = FilePathToUTF8(build_settings->root_path());
411 auto item_callback = [&json_writer,
412 &build_path](const base::FilePath& input_file) {
413 std::string file;
414 if (MakeAbsolutePathRelativeIfPossible(
415 build_path, FilePathToUTF8(input_file), &file)) {
416 json_writer.AddListItem(file);
417 }
418 };
419 sorter.IterateOver(item_callback);
420
421 json_writer.EndList(); // gen_input_files
422
423 json_writer.AddString("root_path", build_settings->root_path_utf8());
424 }
425 json_writer.EndDict(); // build_settings
426
427 std::map<Label, const Toolchain*> toolchains;
428 json_writer.BeginDict("targets");
429 {
430 for (const auto* target : sorted_targets) {
431 auto description =
432 DescBuilder::DescriptionForTarget(target, "", false, false, false);
433 // Outputs need to be asked for separately.
434 auto outputs = DescBuilder::DescriptionForTarget(target, "source_outputs",
435 false, false, false);
436 base::DictionaryValue* outputs_value = nullptr;
437 if (outputs->GetDictionary("source_outputs", &outputs_value) &&
438 !outputs_value->empty()) {
439 description->MergeDictionary(outputs.get());
440 }
441
442 std::string json_dict;
443 base::JSONWriter::WriteWithOptions(*description.get(),
444 base::JSONWriter::OPTIONS_PRETTY_PRINT,
445 &json_dict);
446 json_writer.AddJSONDict(target_labels[target], json_dict);
447 toolchains[target->toolchain()->label()] = target->toolchain();
448 }
449 }
450 json_writer.EndDict(); // targets
451
452 json_writer.BeginDict("toolchains");
453 {
454 for (const auto& tool_chain_kv : toolchains) {
455 base::Value toolchain{base::Value::Type::DICTIONARY};
456 const auto& tools = tool_chain_kv.second->tools();
457 for (const auto& tool_kv : tools) {
458 // Do not list builtin tools
459 if (tool_kv.second->AsBuiltin())
460 continue;
461 base::Value tool_info{base::Value::Type::DICTIONARY};
462 auto setIfNotEmptry = [&](const auto& key, const auto& value) {
463 if (value.size())
464 tool_info.SetKey(key, base::Value{value});
465 };
466 auto setSubstitutionList = [&](const auto& key,
467 const SubstitutionList& list) {
468 if (list.list().empty())
469 return;
470 base::Value values{base::Value::Type::LIST};
471 for (const auto& v : list.list())
472 values.GetList().emplace_back(base::Value{v.AsString()});
473 tool_info.SetKey(key, std::move(values));
474 };
475 const auto& tool = tool_kv.second;
476 setIfNotEmptry("command", tool->command().AsString());
477 setIfNotEmptry("command_launcher", tool->command_launcher());
478 setIfNotEmptry("default_output_extension",
479 tool->default_output_extension());
480 setIfNotEmptry("default_output_dir",
481 tool->default_output_dir().AsString());
482 setIfNotEmptry("depfile", tool->depfile().AsString());
483 setIfNotEmptry("description", tool->description().AsString());
484 setIfNotEmptry("framework_switch", tool->framework_switch());
485 setIfNotEmptry("weak_framework_switch", tool->weak_framework_switch());
486 setIfNotEmptry("framework_dir_switch", tool->framework_dir_switch());
487 setIfNotEmptry("lib_switch", tool->lib_switch());
488 setIfNotEmptry("lib_dir_switch", tool->lib_dir_switch());
489 setIfNotEmptry("linker_arg", tool->linker_arg());
490 setSubstitutionList("outputs", tool->outputs());
491 setSubstitutionList("partial_outputs", tool->partial_outputs());
492 setSubstitutionList("runtime_outputs", tool->runtime_outputs());
493 setIfNotEmptry("output_prefix", tool->output_prefix());
494
495 toolchain.SetKey(tool_kv.first, std::move(tool_info));
496 }
497 std::string json_dict;
498 base::JSONWriter::WriteWithOptions(
499 toolchain, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_dict);
500 json_writer.AddJSONDict(tool_chain_kv.first.GetUserVisibleName(false), json_dict);
501 }
502 }
503 json_writer.EndDict(); // toolchains
504
505 json_writer.Close();
506
507 return out;
508 }
509
RenderJSON(const BuildSettings * build_settings,std::vector<const Target * > & all_targets)510 std::string JSONProjectWriter::RenderJSON(
511 const BuildSettings* build_settings,
512 std::vector<const Target*>& all_targets) {
513 StringOutputBuffer storage = GenerateJSON(build_settings, all_targets);
514 return storage.str();
515 }
516