blob: 6dac2e69e5482eb202193447224eedd1672af2c4 [file] [edit]
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fcntl.h>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/version.h"
#include "src/tools/tracing_proto_extensions.h"
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
#include <zlib.h>
#endif
namespace perfetto {
namespace gen_proto_extensions {
namespace {
const char kUsage[] =
R"(Usage: tracing_proto_extensions [options]
Reads an extensions.json registry, compiles referenced .proto files,
validates extension field numbers, and generates a merged FileDescriptorSet.
-j, --json: Path to the root extensions.json file.
-I, --proto_path: Proto include directory (can be specified multiple times).
-o, --descriptor-out: Output path for the binary FileDescriptorSet.
--gzip: Gzip-compress the output.
-h, --help: Show this help.
-v, --version: Show version.
Example:
tracing_proto_extensions \
--json protos/perfetto/trace/extensions.json \
-I . \
--descriptor-out /tmp/extensions.descriptor
tracing_proto_extensions \
--json protos/perfetto/trace/extensions.json \
-I . \
--descriptor-out /tmp/extensions.descriptor.gz --gzip
)";
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
std::vector<uint8_t> GzipCompress(const std::vector<uint8_t>& input) {
z_stream zs{};
// windowBits=15 + 16 for gzip encoding.
if (deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, MAX_MEM_LEVEL,
Z_DEFAULT_STRATEGY) != Z_OK) {
PERFETTO_FATAL("deflateInit2 failed");
}
zs.avail_in = static_cast<uInt>(input.size());
zs.next_in = const_cast<Bytef*>(input.data());
std::vector<uint8_t> output;
output.resize(deflateBound(&zs, static_cast<uLong>(input.size())));
zs.avail_out = static_cast<uInt>(output.size());
zs.next_out = output.data();
int ret = deflate(&zs, Z_FINISH);
PERFETTO_CHECK(ret == Z_STREAM_END);
output.resize(zs.total_out);
deflateEnd(&zs);
return output;
}
#endif
int Main(int argc, char** argv) {
static const option long_options[] = {
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, nullptr, 'v'},
{"json", required_argument, nullptr, 'j'},
{"proto_path", required_argument, nullptr, 'I'},
{"descriptor-out", required_argument, nullptr, 'o'},
{"gzip", no_argument, nullptr, 'g'},
{nullptr, 0, nullptr, 0}};
std::string json_path;
std::vector<std::string> proto_paths;
std::string output_path;
bool use_gzip = false;
bool has_args = false;
for (;;) {
int option = getopt_long(argc, argv, "hvj:I:o:", long_options, nullptr);
if (option == -1)
break;
if (option == 'v') {
printf("%s\n", base::GetVersionString());
return 0;
}
if (option == 'h') {
fprintf(stdout, "%s", kUsage);
return 0;
}
has_args = true;
if (option == 'j') {
json_path = optarg;
continue;
}
if (option == 'I') {
proto_paths.emplace_back(optarg);
continue;
}
if (option == 'o') {
output_path = optarg;
continue;
}
if (option == 'g') {
use_gzip = true;
continue;
}
fprintf(stderr, "%s", kUsage);
return 1;
}
if (!has_args) {
fprintf(stdout, "%s", kUsage);
return 1;
}
if (json_path.empty()) {
PERFETTO_ELOG("--json is required");
return 1;
}
if (output_path.empty()) {
PERFETTO_ELOG("--descriptor-out is required");
return 1;
}
if (proto_paths.empty()) {
PERFETTO_ELOG("At least one -I proto_path is required");
return 1;
}
// Derive root_dir from json_path (the directory containing the JSON).
// Actually, we use the first -I path as the root_dir for resolving
// relative paths in the JSON.
const std::string& root_dir = proto_paths[0];
auto result = GenerateExtensionDescriptors(json_path, proto_paths, root_dir);
if (!result.ok()) {
PERFETTO_ELOG("Error: %s", result.status().message().c_str());
return 1;
}
std::vector<uint8_t> output = std::move(*result);
PERFETTO_ILOG("Generated FileDescriptorSet: %zu bytes", output.size());
if (use_gzip) {
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
size_t uncompressed_size = output.size();
output = GzipCompress(output);
PERFETTO_ILOG("Gzip compressed: %zu -> %zu bytes", uncompressed_size,
output.size());
#else
PERFETTO_ELOG("--gzip requested but PERFETTO_ZLIB is not enabled");
return 1;
#endif
}
base::ScopedFile out_fd(
base::OpenFile(output_path, O_CREAT | O_WRONLY | O_TRUNC, 0664));
if (!out_fd) {
PERFETTO_ELOG("Failed to open output file: %s", output_path.c_str());
return 1;
}
base::WriteAll(*out_fd, output.data(), output.size());
return 0;
}
} // namespace
} // namespace gen_proto_extensions
} // namespace perfetto
int main(int argc, char** argv) {
return perfetto::gen_proto_extensions::Main(argc, argv);
}