• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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