1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdio.h>
18 #include <string>
19
20 #include <google/protobuf/compiler/importer.h>
21 #include <google/protobuf/io/zero_copy_stream_impl.h>
22
23 #include "perfetto/base/logging.h"
24 #include "perfetto/ext/base/file_utils.h"
25 #include "perfetto/ext/base/getopt.h"
26 #include "perfetto/ext/base/scoped_file.h"
27 #include "perfetto/ext/base/string_utils.h"
28 #include "perfetto/ext/base/version.h"
29 #include "src/tools/proto_merger/allowlist.h"
30 #include "src/tools/proto_merger/proto_file.h"
31 #include "src/tools/proto_merger/proto_file_serializer.h"
32 #include "src/tools/proto_merger/proto_merger.h"
33
34 namespace perfetto {
35 namespace proto_merger {
36 namespace {
37
38 class MultiFileErrorCollectorImpl
39 : public google::protobuf::compiler::MultiFileErrorCollector {
40 public:
41 ~MultiFileErrorCollectorImpl() override;
42 void AddError(const std::string&, int, int, const std::string&) override;
43 void AddWarning(const std::string&, int, int, const std::string&) override;
44 };
45
46 MultiFileErrorCollectorImpl::~MultiFileErrorCollectorImpl() = default;
47
AddError(const std::string & filename,int line,int column,const std::string & message)48 void MultiFileErrorCollectorImpl::AddError(const std::string& filename,
49 int line,
50 int column,
51 const std::string& message) {
52 PERFETTO_ELOG("Error %s %d:%d: %s", filename.c_str(), line, column,
53 message.c_str());
54 }
55
AddWarning(const std::string & filename,int line,int column,const std::string & message)56 void MultiFileErrorCollectorImpl::AddWarning(const std::string& filename,
57 int line,
58 int column,
59 const std::string& message) {
60 PERFETTO_ELOG("Warning %s %d:%d: %s", filename.c_str(), line, column,
61 message.c_str());
62 }
63
64 struct ImportResult {
65 std::unique_ptr<google::protobuf::compiler::Importer> importer;
66 const google::protobuf::FileDescriptor* file_descriptor;
67 };
68
ImportProto(const std::string & proto_file,const std::string & proto_dir_path)69 ImportResult ImportProto(const std::string& proto_file,
70 const std::string& proto_dir_path) {
71 MultiFileErrorCollectorImpl mfe;
72
73 google::protobuf::compiler::DiskSourceTree dst;
74 dst.MapPath("", proto_dir_path);
75
76 ImportResult result;
77 result.importer.reset(new google::protobuf::compiler::Importer(&dst, &mfe));
78 result.file_descriptor = result.importer->Import(proto_file);
79 return result;
80 }
81
82 const char kUsage[] =
83 R"(Usage: proto_merger [-i input proto] [-I import dir]
84
85 -i, --input: Path to the input .proto file (relative to
86 --input-include directory). The contents of this
87 file will be updated using the upstream proto.
88 -I, --input-include: Root directory from which includes for --input
89 proto should be searched.
90 -u, --upstream: Path to the upstream .proto file; the contents of
91 this file will be used to update
92 the input proto.
93 -U, --upstream-include: Root directory from which includes for --upstream
94 proto should be searched.
95 -a, --allowlist: Allowlist file which is used to add new fields in
96 the upstream proto to the input proto.
97 -r, --upstream-root-message: Root message in the upstream proto for which new
98 fields from the allowlist will be allowed.
99 -o, --output: Output path for writing the merged proto file.
100
101 Example usage:
102
103 # Updating logs proto from Perfetto repo (must be run in G3):
104 proto_merger \
105 -u third_party/perfetto/protos/perfetto/trace/perfetto_trace.proto \
106 -U . \
107 -i <path to logs proto>/perfetto_log.proto \
108 -I . \
109 --allowlist /tmp/allowlist.txt \
110 -r perfetto.protos.Trace \
111 --output /tmp/output.proto
112 )";
113
Main(int argc,char ** argv)114 int Main(int argc, char** argv) {
115 static const option long_options[] = {
116 {"help", no_argument, nullptr, 'h'},
117 {"version", no_argument, nullptr, 'v'},
118 {"input", required_argument, nullptr, 'i'},
119 {"input-include", required_argument, nullptr, 'I'},
120 {"upstream", required_argument, nullptr, 'u'},
121 {"upstream-include", required_argument, nullptr, 'U'},
122 {"allowlist", required_argument, nullptr, 'a'},
123 {"upstream-root-message", required_argument, nullptr, 'r'},
124 {"output", required_argument, nullptr, 'o'},
125 {nullptr, 0, nullptr, 0}};
126
127 std::string input;
128 std::string input_include;
129 std::string upstream;
130 std::string upstream_include;
131 std::string allowlist;
132 std::string upstream_root_message;
133 std::string output;
134
135 for (;;) {
136 int option =
137 getopt_long(argc, argv, "hvi:I:u:U:a:r:o:", long_options, nullptr);
138
139 if (option == -1)
140 break; // EOF.
141
142 if (option == 'v') {
143 printf("%s\n", base::GetVersionString());
144 return 0;
145 }
146
147 if (option == 'i') {
148 input = optarg;
149 continue;
150 }
151
152 if (option == 'I') {
153 input_include = optarg;
154 continue;
155 }
156
157 if (option == 'u') {
158 upstream = optarg;
159 continue;
160 }
161
162 if (option == 'U') {
163 upstream_include = optarg;
164 continue;
165 }
166
167 if (option == 'a') {
168 allowlist = optarg;
169 continue;
170 }
171
172 if (option == 'r') {
173 upstream_root_message = optarg;
174 continue;
175 }
176
177 if (option == 'o') {
178 output = optarg;
179 continue;
180 }
181
182 if (option == 'h') {
183 fprintf(stdout, kUsage);
184 return 0;
185 }
186
187 fprintf(stderr, kUsage);
188 return 1;
189 }
190
191 if (input.empty()) {
192 PERFETTO_ELOG("Input proto (--input) should be specified");
193 return 1;
194 }
195
196 if (input_include.empty()) {
197 PERFETTO_ELOG(
198 "Input include directory (--input-include) should be specified");
199 return 1;
200 }
201
202 if (upstream.empty()) {
203 PERFETTO_ELOG("Upstream proto (--upstream) should be specified");
204 return 1;
205 }
206
207 if (upstream_include.empty()) {
208 PERFETTO_ELOG(
209 "Upstream include directory (--upstream-include) should be specified");
210 return 1;
211 }
212
213 if (output.empty()) {
214 PERFETTO_ELOG("Output file (--output) should be specified");
215 return 1;
216 }
217
218 if (!allowlist.empty() && upstream_root_message.empty()) {
219 PERFETTO_ELOG(
220 "Need to specifiy upstream root message (--upstream-root-message) when "
221 "specifying allowlist");
222 return 1;
223 }
224
225 std::string input_contents;
226 if (!base::ReadFile(input_include + "/" + input, &input_contents)) {
227 PERFETTO_ELOG("Failed to read input");
228 return 1;
229 }
230
231 static constexpr char kPremable[] =
232 "// --- PREAMBLE ENDS HERE - EVERYTHING BELOW AUTOGENERATED ---\n";
233 size_t input_premable_idx = input_contents.find(kPremable);
234 std::string input_preamble =
235 input_premable_idx == std::string::npos
236 ? ""
237 : input_contents.substr(0, input_premable_idx + strlen(kPremable));
238
239 ImportResult input_proto = ImportProto(input, input_include);
240 ProtoFile input_file = ProtoFileFromDescriptor(std::move(input_preamble),
241 *input_proto.file_descriptor);
242
243 ImportResult upstream_proto = ImportProto(upstream, upstream_include);
244 ProtoFile upstream_file =
245 ProtoFileFromDescriptor("", *upstream_proto.file_descriptor);
246
247 Allowlist allowed;
248 if (!allowlist.empty()) {
249 std::string allowlist_contents;
250 if (!base::ReadFile(allowlist, &allowlist_contents)) {
251 PERFETTO_ELOG("Failed to read allowlist");
252 return 1;
253 }
254
255 auto* desc = upstream_proto.importer->pool()->FindMessageTypeByName(
256 upstream_root_message);
257 if (!desc) {
258 PERFETTO_ELOG(
259 "Failed to find root message descriptor in upstream proto file");
260 return 1;
261 }
262
263 auto field_list = base::SplitString(allowlist_contents, "\n");
264 base::Status status = AllowlistFromFieldList(*desc, field_list, allowed);
265 if (!status.ok()) {
266 PERFETTO_ELOG("Failed creating allowlist: %s", status.c_message());
267 return 1;
268 }
269 }
270
271 ProtoFile merged;
272 base::Status status =
273 MergeProtoFiles(input_file, upstream_file, allowed, merged);
274 if (!status.ok()) {
275 PERFETTO_ELOG("Failed merging protos: %s", status.c_message());
276 return 1;
277 }
278
279 base::ScopedFile output_file(
280 base::OpenFile(output, O_CREAT | O_WRONLY | O_TRUNC, 0664));
281 if (!output_file) {
282 PERFETTO_ELOG("Failed opening output file: %s", output.c_str());
283 return 1;
284 }
285 std::string out = ProtoFileToDotProto(merged);
286 base::WriteAll(*output_file, out.c_str(), out.size());
287
288 return 0;
289 }
290
291 } // namespace
292 } // namespace proto_merger
293 } // namespace perfetto
294
main(int argc,char ** argv)295 int main(int argc, char** argv) {
296 return perfetto::proto_merger::Main(argc, argv);
297 }
298