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