|
43 | 43 |
|
44 | 44 | namespace perfetto { |
45 | 45 | namespace gen_proto_extensions { |
46 | | -namespace { |
47 | 46 |
|
48 | 47 | namespace pbzero = protos::pbzero; |
49 | 48 | using trace_processor::json::FieldResult; |
50 | 49 | using trace_processor::json::SimpleJsonParser; |
51 | 50 |
|
| 51 | +namespace { |
| 52 | + |
52 | 53 | // Sorts ranges by start and checks for validity (start <= end, no internal |
53 | 54 | // overlaps). |
54 | 55 | base::Status SortAndValidateRanges(std::vector<Range>& ranges, |
@@ -259,53 +260,14 @@ base::Status ValidateFieldNumbers( |
259 | 260 | return base::OkStatus(); |
260 | 261 | } |
261 | 262 |
|
262 | | -// Recursively collects all proto files from the registry tree. |
263 | | -struct ProtoEntry { |
264 | | - std::string proto_path; |
265 | | - std::string scope; |
266 | | - std::vector<Range> ranges; |
267 | | -}; |
268 | | - |
269 | | -base::Status CollectProtos(const std::string& json_path, |
270 | | - const std::string& root_dir, |
271 | | - std::vector<ProtoEntry>* out) { |
272 | | - std::string contents; |
273 | | - if (!base::ReadFile(json_path, &contents)) { |
274 | | - return base::ErrStatus("Failed to read '%s'", json_path.c_str()); |
275 | | - } |
276 | | - |
277 | | - ASSIGN_OR_RETURN(auto reg, ParseRegistry(contents, json_path)); |
278 | | - RETURN_IF_ERROR(ValidateRegistry(reg)); |
279 | | - |
280 | | - for (const auto& alloc : reg.allocations) { |
281 | | - if (!alloc.proto.empty() && alloc.repo.empty()) { |
282 | | - // Local proto leaf. |
283 | | - out->push_back({root_dir + "/" + alloc.proto, reg.scope, alloc.ranges}); |
284 | | - } else if (!alloc.registry.empty() && alloc.repo.empty()) { |
285 | | - // Local sub-registry. |
286 | | - std::string sub_path = root_dir + "/" + alloc.registry; |
287 | | - RETURN_IF_ERROR(CollectProtos(sub_path, root_dir, out)); |
288 | | - } |
289 | | - // Remote entries (repo is set) are skipped. |
290 | | - } |
291 | | - return base::OkStatus(); |
292 | | -} |
293 | | - |
294 | | -} // namespace |
295 | | - |
296 | | -base::StatusOr<Registry> ParseRegistry(const std::string& json_contents, |
297 | | - const std::string& source_path) { |
| 263 | +// Parses a single registry object from the current parser position. |
| 264 | +// The parser should be positioned at a JSON object with |
| 265 | +// scope/range/allocations. |
| 266 | +base::StatusOr<Registry> ParseRegistryObject(SimpleJsonParser& parser, |
| 267 | + const std::string& source_path) { |
298 | 268 | Registry reg; |
299 | 269 | reg.source_path = source_path; |
300 | 270 |
|
301 | | - SimpleJsonParser parser(json_contents); |
302 | | - { |
303 | | - auto status = parser.Parse(); |
304 | | - if (!status.ok()) |
305 | | - return base::ErrStatus("Failed to parse JSON in '%s': %s", |
306 | | - source_path.c_str(), status.message().c_str()); |
307 | | - } |
308 | | - |
309 | 271 | bool has_range = false; |
310 | 272 | bool has_ranges = false; |
311 | 273 | RETURN_IF_ERROR(parser.ForEachField([&](std::string_view key) -> FieldResult { |
@@ -462,6 +424,46 @@ base::StatusOr<Registry> ParseRegistry(const std::string& json_contents, |
462 | 424 | return std::move(reg); |
463 | 425 | } |
464 | 426 |
|
| 427 | +} // namespace |
| 428 | + |
| 429 | +base::StatusOr<std::vector<Registry>> ParseRegistryFile( |
| 430 | + const std::string& json_contents, |
| 431 | + const std::string& source_path) { |
| 432 | + SimpleJsonParser parser(json_contents); |
| 433 | + { |
| 434 | + auto status = parser.Parse(); |
| 435 | + if (!status.ok()) |
| 436 | + return base::ErrStatus("Failed to parse JSON in '%s': %s", |
| 437 | + source_path.c_str(), status.message().c_str()); |
| 438 | + } |
| 439 | + |
| 440 | + std::vector<Registry> extensions; |
| 441 | + RETURN_IF_ERROR(parser.ForEachField([&](std::string_view key) -> FieldResult { |
| 442 | + if (key == "extensions") { |
| 443 | + if (!parser.IsArray()) |
| 444 | + return base::ErrStatus("'extensions' must be an array in '%s'", |
| 445 | + source_path.c_str()); |
| 446 | + RETURN_IF_ERROR(parser.ForEachArrayElement([&]() -> base::Status { |
| 447 | + if (!parser.IsObject()) |
| 448 | + return base::ErrStatus( |
| 449 | + "Each entry in 'extensions' must be an object in '%s'", |
| 450 | + source_path.c_str()); |
| 451 | + ASSIGN_OR_RETURN(auto reg, ParseRegistryObject(parser, source_path)); |
| 452 | + extensions.push_back(std::move(reg)); |
| 453 | + return base::OkStatus(); |
| 454 | + })); |
| 455 | + return FieldResult::Handled{}; |
| 456 | + } |
| 457 | + if (key == "comment") |
| 458 | + return FieldResult::Skip{}; |
| 459 | + return base::ErrStatus("Unknown field '%.*s' in '%s'", |
| 460 | + static_cast<int>(key.size()), key.data(), |
| 461 | + source_path.c_str()); |
| 462 | + })); |
| 463 | + |
| 464 | + return extensions; |
| 465 | +} |
| 466 | + |
465 | 467 | base::Status ValidateRegistry(const Registry& reg) { |
466 | 468 | // Currently only TrackEvent extensions are supported. In the future, this |
467 | 469 | // field could be used to disambiguate TracePacket extensions. |
@@ -554,13 +556,62 @@ base::Status ValidateRegistry(const Registry& reg) { |
554 | 556 | return base::OkStatus(); |
555 | 557 | } |
556 | 558 |
|
| 559 | +namespace { |
| 560 | + |
| 561 | +// Recursively collects all proto files from the registry tree. |
| 562 | +struct ProtoEntry { |
| 563 | + std::string proto_path; |
| 564 | + std::string scope; |
| 565 | + std::vector<Range> ranges; |
| 566 | +}; |
| 567 | + |
| 568 | +// Walks allocations from an already-parsed registry. For sub-registries, |
| 569 | +// reads and parses them using the flat (non-wrapped) format. |
| 570 | +base::Status CollectProtosFromRegistry(const Registry& reg, |
| 571 | + const std::string& root_dir, |
| 572 | + std::vector<ProtoEntry>* out) { |
| 573 | + for (const auto& alloc : reg.allocations) { |
| 574 | + if (!alloc.proto.empty() && alloc.repo.empty()) { |
| 575 | + // Local proto leaf. |
| 576 | + out->push_back({root_dir + "/" + alloc.proto, reg.scope, alloc.ranges}); |
| 577 | + } else if (!alloc.registry.empty() && alloc.repo.empty()) { |
| 578 | + // Local sub-registry (same {"extensions": [...]} format). |
| 579 | + std::string sub_path = root_dir + "/" + alloc.registry; |
| 580 | + std::string contents; |
| 581 | + if (!base::ReadFile(sub_path, &contents)) { |
| 582 | + return base::ErrStatus("Failed to read '%s'", sub_path.c_str()); |
| 583 | + } |
| 584 | + ASSIGN_OR_RETURN(auto sub_regs, ParseRegistryFile(contents, sub_path)); |
| 585 | + for (const auto& sub_reg : sub_regs) { |
| 586 | + RETURN_IF_ERROR(ValidateRegistry(sub_reg)); |
| 587 | + RETURN_IF_ERROR(CollectProtosFromRegistry(sub_reg, root_dir, out)); |
| 588 | + } |
| 589 | + } |
| 590 | + // Remote entries (repo is set) are skipped. |
| 591 | + } |
| 592 | + return base::OkStatus(); |
| 593 | +} |
| 594 | + |
| 595 | +} // namespace |
| 596 | + |
557 | 597 | base::StatusOr<std::vector<uint8_t>> GenerateExtensionDescriptors( |
558 | 598 | const std::string& root_json_path, |
559 | 599 | const std::vector<std::string>& proto_paths, |
560 | 600 | const std::string& root_dir) { |
561 | | - // 1. Recursively collect all local proto entries from the JSON hierarchy. |
| 601 | + // 1. Read and parse the root registry file (uses the "extensions" wrapper). |
| 602 | + std::string root_contents; |
| 603 | + if (!base::ReadFile(root_json_path, &root_contents)) { |
| 604 | + return base::ErrStatus("Failed to read '%s'", root_json_path.c_str()); |
| 605 | + } |
| 606 | + ASSIGN_OR_RETURN(auto extensions, |
| 607 | + ParseRegistryFile(root_contents, root_json_path)); |
| 608 | + |
| 609 | + // 2. Recursively collect all local proto entries from each registry. |
562 | 610 | std::vector<ProtoEntry> entries; |
563 | | - RETURN_IF_ERROR(CollectProtos(root_json_path, root_dir, &entries)); |
| 611 | + for (const auto& reg : extensions) { |
| 612 | + RETURN_IF_ERROR(ValidateRegistry(reg)); |
| 613 | + RETURN_IF_ERROR(CollectProtosFromRegistry(reg, root_dir, &entries)); |
| 614 | + } |
564 | 615 |
|
565 | 616 | if (entries.empty()) { |
566 | 617 | PERFETTO_ILOG("No local proto files found in registry."); |
|
0 commit comments