1 // Copyright (c) 2023 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/ninja_outputs_writer.h"
6
7 #include <algorithm>
8 #include <memory>
9
10 #include "base/command_line.h"
11 #include "base/files/file_path.h"
12 #include "base/json/string_escape.h"
13 #include "gn/builder.h"
14 #include "gn/commands.h"
15 #include "gn/filesystem_utils.h"
16 #include "gn/invoke_python.h"
17 #include "gn/settings.h"
18 #include "gn/string_output_buffer.h"
19
20 // NOTE: Intentional macro definition allows compile-time string concatenation.
21 // (see usage below).
22 #if defined(OS_WINDOWS)
23 #define LINE_ENDING "\r\n"
24 #else
25 #define LINE_ENDING "\n"
26 #endif
27
28 namespace {
29
30 using MapType = NinjaOutputsWriter::MapType;
31
32 // Sort the targets according to their human visible labels first.
33 struct TargetLabelPair {
TargetLabelPair__anonf9452e240111::TargetLabelPair34 TargetLabelPair(const Target* target, const Label& default_toolchain_label)
35 : target(target),
36 label(std::make_unique<std::string>(
37 target->label().GetUserVisibleName(default_toolchain_label))) {}
38
39 const Target* target;
40 std::unique_ptr<std::string> label;
41
operator <__anonf9452e240111::TargetLabelPair42 bool operator<(const TargetLabelPair& other) const {
43 return *label < *other.label;
44 }
45
46 using List = std::vector<TargetLabelPair>;
47
48 // Create list of TargetLabelPairs sorted by their target labels.
CreateSortedList__anonf9452e240111::TargetLabelPair49 static List CreateSortedList(const MapType& outputs_map,
50 const Label& default_toolchain_label) {
51 List result;
52 result.reserve(outputs_map.size());
53
54 for (const auto& output_pair : outputs_map)
55 result.emplace_back(output_pair.first, default_toolchain_label);
56
57 std::sort(result.begin(), result.end());
58 return result;
59 }
60 };
61
62 } // namespace
63
64 // static
GenerateJSON(const MapType & outputs_map)65 StringOutputBuffer NinjaOutputsWriter::GenerateJSON(
66 const MapType& outputs_map) {
67 Label default_toolchain_label;
68 if (!outputs_map.empty()) {
69 default_toolchain_label =
70 outputs_map.begin()->first->settings()->default_toolchain_label();
71 }
72
73 auto sorted_pairs =
74 TargetLabelPair::CreateSortedList(outputs_map, default_toolchain_label);
75
76 StringOutputBuffer out;
77 out.Append('{');
78
79 auto escape = [](std::string_view str) -> std::string {
80 std::string result;
81 base::EscapeJSONString(str, true, &result);
82 return result;
83 };
84
85 bool first_label = true;
86 for (const auto& pair : sorted_pairs) {
87 const Target* target = pair.target;
88 const std::string& label = *pair.label;
89
90 auto it = outputs_map.find(target);
91 CHECK(it != outputs_map.end());
92
93 if (!first_label)
94 out.Append(',');
95 first_label = false;
96
97 out.Append("\n ");
98 out.Append(escape(label));
99 out.Append(": [");
100 bool first_path = true;
101 for (const auto& output : it->second) {
102 if (!first_path)
103 out.Append(',');
104 first_path = false;
105 out.Append("\n ");
106 out.Append(escape(output.value()));
107 }
108 out.Append("\n ]");
109 }
110
111 out.Append("\n}");
112 return out;
113 }
114
RunAndWriteFiles(const MapType & outputs_map,const BuildSettings * build_settings,const std::string & file_name,const std::string & exec_script,const std::string & exec_script_extra_args,bool quiet,Err * err)115 bool NinjaOutputsWriter::RunAndWriteFiles(
116 const MapType& outputs_map,
117 const BuildSettings* build_settings,
118 const std::string& file_name,
119 const std::string& exec_script,
120 const std::string& exec_script_extra_args,
121 bool quiet,
122 Err* err) {
123 SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
124 Value(nullptr, file_name), err);
125 if (output_file.is_null()) {
126 return false;
127 }
128
129 StringOutputBuffer outputs = GenerateJSON(outputs_map);
130
131 base::FilePath output_path = build_settings->GetFullPath(output_file);
132 if (!outputs.ContentsEqual(output_path)) {
133 if (!outputs.WriteToFile(output_path, err)) {
134 return false;
135 }
136
137 if (!exec_script.empty()) {
138 SourceFile script_file;
139 if (exec_script[0] != '/') {
140 // Relative path, assume the base is in build_dir.
141 script_file = build_settings->build_dir().ResolveRelativeFile(
142 Value(nullptr, exec_script), err);
143 if (script_file.is_null()) {
144 return false;
145 }
146 } else {
147 script_file = SourceFile(exec_script);
148 }
149 base::FilePath script_path = build_settings->GetFullPath(script_file);
150 return internal::InvokePython(build_settings, script_path,
151 exec_script_extra_args, output_path, quiet,
152 err);
153 }
154 }
155
156 return true;
157 }
158