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