blob: c362d59f126cd061d85403339ff57e1ec114aca1 [file] [log] [blame]
Lalit Magantib07532d2021-07-01 22:13:57 +01001/*
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 "tools/proto_merger/proto_merger.h"
18
19#include "perfetto/base/logging.h"
20#include "perfetto/base/status.h"
21#include "perfetto/ext/base/optional.h"
22
23namespace perfetto {
24namespace proto_merger {
25namespace {
26
27template <typename Key, typename Value>
28base::Optional<Value> FindInMap(const std::map<Key, Value>& map,
29 const Key& key) {
30 auto it = map.find(key);
31 return it == map.end() ? base::nullopt : base::make_optional(it->second);
32}
33
34// Finds the given 'name' in the vector by comparing against
35// the field named 'name' for each item in the vector.
Lalit Magantidade39c2021-07-08 19:30:49 +010036// T is ProtoFile::Enum, ProtoFile::Oneof or ProtoFile::Message.
Lalit Magantib07532d2021-07-01 22:13:57 +010037template <typename T>
38const T* FindByName(const std::vector<T>& items, const std::string& name) {
39 for (const auto& item : items) {
40 if (item.name == name)
41 return &item;
42 }
43 return nullptr;
44}
45
46// Compute the items present in the |input| vector but deleted in
47// the |upstream| vector by looking at the field |name|.
Lalit Magantidade39c2021-07-08 19:30:49 +010048// T is ProtoFile::Enum, ProtoFile::Oneof or ProtoFile::Message.
Lalit Magantib07532d2021-07-01 22:13:57 +010049template <typename T>
50std::vector<T> ComputeDeletedByName(const std::vector<T>& input,
51 const std::vector<T>& upstream) {
52 std::vector<T> deleted;
53 std::set<std::string> seen;
54 for (const auto& upstream_item : upstream) {
55 auto* input_item = FindByName(input, upstream_item.name);
56 if (!input_item)
57 continue;
58 seen.insert(input_item->name);
59 }
60
61 for (const auto& input_item : input) {
62 if (seen.count(input_item.name))
63 continue;
64 deleted.emplace_back(input_item);
65 }
66 return deleted;
67}
68
69// Finds the given 'number' in the vector by comparing against
70// the field named 'number for each item in the vector.
Lalit Magantidade39c2021-07-08 19:30:49 +010071// T is ProtoFile::EnumValue or ProtoFile::Field.
Lalit Magantib07532d2021-07-01 22:13:57 +010072template <typename T>
73const T* FindByNumber(const std::vector<T>& items, int number) {
74 for (const auto& item : items) {
75 if (item.number == number)
76 return &item;
77 }
78 return nullptr;
79}
80
81// Compute the items present in the |input| vector but deleted in
82// the |upstream| vector by looking at the field |number|.
Lalit Magantidade39c2021-07-08 19:30:49 +010083// T is ProtoFile::EnumValue or ProtoFile::Field.
Lalit Magantib07532d2021-07-01 22:13:57 +010084template <typename T>
85std::vector<T> ComputeDeletedByNumber(const std::vector<T>& input,
86 const std::vector<T>& upstream) {
87 std::vector<T> deleted;
88 std::set<int> seen;
89 for (const auto& upstream_item : upstream) {
90 auto* input_item = FindByNumber(input, upstream_item.number);
91 if (!input_item)
92 continue;
93 seen.insert(input_item->number);
94 }
95
96 for (const auto& input_item : input) {
97 if (seen.count(input_item.number))
98 continue;
99 deleted.emplace_back(input_item);
100 }
101 return deleted;
102}
103
104ProtoFile::Enum::Value MergeEnumValue(const ProtoFile::Enum::Value& input,
105 const ProtoFile::Enum::Value& upstream) {
106 PERFETTO_CHECK(input.number == upstream.number);
107
108 ProtoFile::Enum::Value out;
109 out.name = upstream.name;
110
111 // Get the comments from the source of truth.
112 out.leading_comments = upstream.leading_comments;
113 out.trailing_comments = upstream.trailing_comments;
114
115 // Get everything else from the input.
116 out.number = input.number;
117 out.options = input.options;
118 return out;
119}
120
121ProtoFile::Enum MergeEnum(const ProtoFile::Enum& input,
122 const ProtoFile::Enum& upstream) {
123 PERFETTO_CHECK(input.name == upstream.name);
124
125 ProtoFile::Enum out;
126 out.name = upstream.name;
127
128 // Get the comments from the source of truth.
129 out.leading_comments = upstream.leading_comments;
130 out.trailing_comments = upstream.trailing_comments;
131
132 for (const auto& upstream_value : upstream.values) {
133 // If an enum is allowlisted, we implicitly assume that all its
134 // values are also allowed. Therefore, if the value doesn't exist
135 // in the input, just take it from the source of truth.
136 auto* input_value = FindByNumber(input.values, upstream_value.number);
137 auto out_value = input_value ? MergeEnumValue(*input_value, upstream_value)
138 : upstream_value;
139 out.values.emplace_back(std::move(out_value));
140 }
141
142 // Compute all the values present in the input but deleted in the
143 // source of truth.
144 out.deleted_values = ComputeDeletedByNumber(input.values, upstream.values);
145 return out;
146}
147
148std::vector<ProtoFile::Enum> MergeEnums(
149 const std::vector<ProtoFile::Enum>& input,
150 const std::vector<ProtoFile::Enum>& upstream,
151 const std::set<std::string>& allowlist) {
152 std::vector<ProtoFile::Enum> out;
153 for (const auto& upstream_enum : upstream) {
154 auto* input_enum = FindByName(input, upstream_enum.name);
155 if (!input_enum) {
156 // If the enum is missing from the input but is present
157 // in the allowlist, take the whole enum from the
158 // source of truth.
159 if (allowlist.count(upstream_enum.name))
160 out.emplace_back(upstream_enum);
161 continue;
162 }
163
164 // Otherwise, merge the enums from the input and source of truth.
165 out.emplace_back(MergeEnum(*input_enum, upstream_enum));
166 }
167 return out;
168}
169
170base::Status MergeField(const ProtoFile::Field& input,
171 const ProtoFile::Field& upstream,
172 ProtoFile::Field& out) {
173 PERFETTO_CHECK(input.number == upstream.number);
174
175 if (input.packageless_type != upstream.packageless_type) {
176 return base::ErrStatus(
177 "The type of field with id %d and name %s (source of truth name: %s) "
178 "changed from %s to %s. Please resolve conflict manually before "
179 "rerunning.",
180 input.number, input.name.c_str(), upstream.name.c_str(),
181 input.packageless_type.c_str(), upstream.packageless_type.c_str());
182 }
183
184 // If the packageless type mathces, the type should also match.
185 PERFETTO_CHECK(input.type == upstream.type);
186
187 // Get the comments, label and the name from the source of truth.
188 out.leading_comments = upstream.leading_comments;
189 out.trailing_comments = upstream.trailing_comments;
190 out.label = upstream.label;
191 out.name = upstream.name;
192
193 // Get everything else from the input.
194 out.number = input.number;
195 out.options = input.options;
196 out.packageless_type = input.packageless_type;
197 out.type = input.type;
198
199 return base::OkStatus();
200}
201
202base::Status MergeFields(const std::vector<ProtoFile::Field>& input,
203 const std::vector<ProtoFile::Field>& upstream,
204 const std::set<int>& allowlist,
205 std::vector<ProtoFile::Field>& out) {
206 for (const auto& upstream_field : upstream) {
207 auto* input_field = FindByNumber(input, upstream_field.number);
208 if (!input_field) {
209 // If the field is missing from the input but is present
210 // in the allowlist, take the whole field from the
211 // source of truth.
212 if (allowlist.count(upstream_field.number))
213 out.emplace_back(upstream_field);
214 continue;
215 }
216
217 // Otherwise, merge the fields from the input and source of truth.
218 ProtoFile::Field out_field;
219 base::Status status = MergeField(*input_field, upstream_field, out_field);
220 if (!status.ok())
221 return status;
222 out.emplace_back(std::move(out_field));
223 }
224 return base::OkStatus();
225}
226
227// We call both of these just "Merge" so that |MergeRecursive| below can
228// reference them with the same name.
229base::Status Merge(const ProtoFile::Oneof& input,
230 const ProtoFile::Oneof& upstream,
231 const Allowlist::Oneof& allowlist,
232 ProtoFile::Oneof& out);
233
234base::Status Merge(const ProtoFile::Message& input,
235 const ProtoFile::Message& upstream,
236 const Allowlist::Message& allowlist,
237 ProtoFile::Message& out);
238
239template <typename T, typename AllowlistType>
240base::Status MergeRecursive(
241 const std::vector<T>& input,
242 const std::vector<T>& upstream,
243 const std::map<std::string, AllowlistType>& allowlist_map,
244 std::vector<T>& out) {
245 for (const auto& upstream_item : upstream) {
246 auto opt_allowlist = FindInMap(allowlist_map, upstream_item.name);
247 auto* input_item = FindByName(input, upstream_item.name);
248
249 // If the value is not present in the input and the allowlist doesn't
250 // exist either, this field is not approved so should not be included
251 // in the output.
252 if (!input_item && !opt_allowlist)
253 continue;
254
255 // If the input value doesn't exist, create a fake "input" that we can pass
256 // to the merge functon. This basically has the effect that the upstream
257 // item is taken but *not* recrusively; i.e. any fields which are inside the
258 // message/oneof are checked against the allowlist individually. If we just
259 // took the whole upstream here, we could add fields which were not
260 // allowlisted.
261 T input_or_fake;
262 if (input_item) {
263 input_or_fake = *input_item;
264 } else {
265 input_or_fake.name = upstream_item.name;
266 }
267
268 auto allowlist = opt_allowlist.value_or(AllowlistType{});
269 T out_item;
270 auto status = Merge(input_or_fake, upstream_item, allowlist, out_item);
271 if (!status.ok())
272 return status;
273 out.emplace_back(std::move(out_item));
274 }
275 return base::OkStatus();
276}
277
278base::Status Merge(const ProtoFile::Oneof& input,
279 const ProtoFile::Oneof& upstream,
280 const Allowlist::Oneof& allowlist,
281 ProtoFile::Oneof& out) {
282 PERFETTO_CHECK(input.name == upstream.name);
283 out.name = input.name;
284
285 // Get the comments from the source of truth.
286 out.leading_comments = upstream.leading_comments;
287 out.trailing_comments = upstream.trailing_comments;
288
289 // Compute all the fields present in the input but deleted in the
290 // source of truth.
291 out.deleted_fields = ComputeDeletedByNumber(input.fields, upstream.fields);
292
293 // Finish by merging the list of fields.
294 return MergeFields(input.fields, upstream.fields, allowlist, out.fields);
295}
296
297base::Status Merge(const ProtoFile::Message& input,
298 const ProtoFile::Message& upstream,
299 const Allowlist::Message& allowlist,
300 ProtoFile::Message& out) {
301 PERFETTO_CHECK(input.name == upstream.name);
302 out.name = input.name;
303
304 // Get the comments from the source of truth.
305 out.leading_comments = upstream.leading_comments;
306 out.trailing_comments = upstream.trailing_comments;
307
308 // Compute all the values present in the input but deleted in the
309 // source of truth.
310 out.deleted_enums = ComputeDeletedByName(input.enums, upstream.enums);
311 out.deleted_nested_messages =
312 ComputeDeletedByName(input.nested_messages, upstream.nested_messages);
313 out.deleted_oneofs = ComputeDeletedByName(input.oneofs, upstream.oneofs);
314 out.deleted_fields = ComputeDeletedByNumber(input.fields, upstream.fields);
315
316 // Merge any nested enum types.
317 out.enums = MergeEnums(input.enums, upstream.enums, allowlist.enums);
318
319 // Merge any nested message types.
320 auto status = MergeRecursive(input.nested_messages, upstream.nested_messages,
321 allowlist.nested_messages, out.nested_messages);
322 if (!status.ok())
323 return status;
324
325 // Merge any oneofs.
326 status = MergeRecursive(input.oneofs, upstream.oneofs, allowlist.oneofs,
327 out.oneofs);
328 if (!status.ok())
329 return status;
330
331 // Finish by merging the list of fields.
332 return MergeFields(input.fields, upstream.fields, allowlist.fields,
333 out.fields);
334}
335
336} // namespace
337
338base::Status MergeProtoFiles(const ProtoFile& input,
339 const ProtoFile& upstream,
340 const Allowlist& allowlist,
341 ProtoFile& out) {
342 // Compute all the enums and messages present in the input but deleted in the
343 // source of truth.
344 out.deleted_enums = ComputeDeletedByName(input.enums, upstream.enums);
345 out.deleted_messages =
346 ComputeDeletedByName(input.messages, upstream.messages);
347
348 // Merge the top-level enums.
349 out.enums = MergeEnums(input.enums, upstream.enums, allowlist.enums);
350
351 // Finish by merging the top-level messages.
352 return MergeRecursive(input.messages, upstream.messages, allowlist.messages,
353 out.messages);
354}
355
356} // namespace proto_merger
357} // namespace perfetto