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