blob: d6c82860797821a5ee2f9d25bf49608884b30c0a [file] [edit]
#!/usr/bin/env python3
# Copyright (C) 2025 Rivos Inc.
#
# 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.
import argparse
import filecmp
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile
from collections import defaultdict
SOURCE_FILES = [
{
"files": [
"protos/perfetto/common/builtin_clock.proto",
"protos/perfetto/common/semantic_type.proto",
"protos/perfetto/common/track_event_descriptor.proto",
"protos/perfetto/config/priority_boost/priority_boost_config.proto",
"protos/perfetto/config/test_config.proto",
"protos/perfetto/config/trace_config.proto",
"protos/perfetto/config/track_event/track_event_config.proto",
"protos/perfetto/trace/clock_snapshot.proto",
"protos/perfetto/trace/profiling/profile_common.proto",
"protos/perfetto/trace/test_event.proto",
"protos/perfetto/trace/trace.proto",
"protos/perfetto/trace/track_event/chrome_active_processes.proto",
"protos/perfetto/trace/track_event/chrome_application_state_info.proto",
"protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
"protos/perfetto/trace/track_event/chrome_content_settings_event_info.proto",
"protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
"protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
"protos/perfetto/trace/track_event/chrome_keyed_service.proto",
"protos/perfetto/trace/track_event/chrome_latency_info.proto",
"protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
"protos/perfetto/trace/track_event/chrome_message_pump.proto",
"protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
"protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
"protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
"protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
"protos/perfetto/trace/track_event/chrome_user_event.proto",
"protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
"protos/perfetto/trace/track_event/source_location.proto",
"protos/perfetto/trace/track_event/task_execution.proto",
"protos/perfetto/trace/track_event/thread_descriptor.proto",
"protos/perfetto/trace/track_event/track_descriptor.proto",
"protos/perfetto/trace/track_event/track_event.proto",
],
"custom_files": [
"protos/perfetto/common/data_source_descriptor.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/trace/interned_data/interned_data.proto",
"protos/perfetto/trace/trace_packet.proto",
],
"path_strip_prefix": "protos/perfetto",
"path_add_prefix": "contrib/rust-sdk/perfetto/src/protos",
},
{
"files": [
"protos/perfetto/common/gpu_counter_descriptor.proto",
"protos/perfetto/config/gpu/gpu_counter_config.proto",
"protos/perfetto/config/gpu/gpu_renderstages_config.proto",
"protos/perfetto/config/gpu/vulkan_memory_config.proto",
"protos/perfetto/trace/gpu/gpu_counter_event.proto",
"protos/perfetto/trace/gpu/gpu_log.proto",
"protos/perfetto/trace/gpu/gpu_render_stage_event.proto",
"protos/perfetto/trace/gpu/vulkan_api_event.proto",
"protos/perfetto/trace/gpu/vulkan_memory_event.proto",
],
"custom_files": [
"protos/perfetto/common/data_source_descriptor.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/trace/interned_data/interned_data.proto",
"protos/perfetto/trace/trace_packet.proto",
],
"path_strip_prefix": "protos/perfetto",
"path_add_prefix": "contrib/rust-sdk/perfetto-protos-gpu/src/protos",
},
]
ROOT_DIR = os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
IS_WIN = sys.platform.startswith("win")
SCRIPT_PATH = "contrib/rust-sdk/tools/gen_rust_protos"
def protozero_rust_plugin_path(out_directory):
path = os.path.join(out_directory,
"protozero_rust_plugin") + (".exe" if IS_WIN else "")
assert os.path.isfile(path)
return path
def protoc_path(out_directory):
path = os.path.join(out_directory, "protoc") + (".exe" if IS_WIN else "")
assert os.path.isfile(path)
return path
def call(cmd, *args):
path = os.path.join("tools", cmd)
command = ["python3", path] + list(args)
print("Running", " ".join(command))
try:
subprocess.check_call(command, cwd=ROOT_DIR)
except subprocess.CalledProcessError as e:
assert False, "Command: {} failed".format(" ".join(command))
# Transforms filename extension like the ProtoZero Rust plugin
def transform_extension(filename):
old_suffix = ".proto"
new_suffix = ".pz.rs"
if filename.endswith(old_suffix):
return filename[:-len(old_suffix)] + new_suffix
return filename
def generate(
source,
outdir,
protoc_path,
protozero_rust_plugin_path,
path_strip_prefix,
path_add_prefix,
):
options = {
"path_strip_prefix": path_strip_prefix,
"path_add_prefix": path_add_prefix,
"invoker": SCRIPT_PATH,
}
serialized_options = ",".join(
["{}={}".format(name, value) for name, value in options.items()])
subprocess.check_call(
[
protoc_path,
"--proto_path=.",
"--plugin=protoc-gen-plugin={}".format(protozero_rust_plugin_path),
"--plugin_out={}:{}".format(serialized_options, outdir),
source,
],
cwd=ROOT_DIR,
)
def generate_mod(tmpfilename, mods):
with open(tmpfilename, "w") as f:
print(
"""// Copyright (C) 2025 Rivos Inc.
//
// 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.
// Autogenerated by the gen_rust_protos script.
// DO NOT EDIT.""",
file=f,
)
for mod in sorted(mods):
modname = transform_extension(os.path.basename(mod))
if modname.endswith(".pz.rs"):
print(f"\n/// `{modname[:-6]}` protos.", file=f)
print(f'#[path = "{modname}"]', file=f)
print(f"pub mod {modname[:-6]};", file=f)
else:
print(f"\n/// `{modname}` protos.", file=f)
print(f"pub mod {modname};", file=f)
def mods_by_directory(sources, path_strip_prefix):
groups = defaultdict(set)
for path in sources:
if path.startswith(path_strip_prefix):
path = path[len(path_strip_prefix):]
modpath = pathlib.Path(path)
for parent in modpath.parents:
if parent != pathlib.Path("/"):
directory = str(parent)
groups[directory].add(path)
path = directory
return dict(groups)
def rust_path_for(path, path_strip_prefix, path_add_prefix):
if path.startswith(path_strip_prefix):
rust_path = path[len(path_strip_prefix):]
else:
rust_path = path
return path_add_prefix + rust_path
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--check-only", action="store_true")
parser.add_argument("OUT")
args = parser.parse_args()
out = args.OUT
call("ninja", "-C", out, "protoc", "protozero_rust_plugin")
try:
with tempfile.TemporaryDirectory() as tmpdirname:
for sources in SOURCE_FILES:
for source in sources["files"]:
generate(
source,
tmpdirname,
protoc_path(out),
protozero_rust_plugin_path(out),
path_strip_prefix=sources["path_strip_prefix"],
path_add_prefix=sources["path_add_prefix"],
)
tmpfilename = os.path.join(tmpdirname, transform_extension(source))
targetfilename = rust_path_for(
source,
sources["path_strip_prefix"],
sources["path_add_prefix"],
)
targetfilename = transform_extension(targetfilename)
if args.check_only:
if not filecmp.cmp(tmpfilename, targetfilename):
raise AssertionError("Target {} does not match", targetfilename)
else:
os.makedirs(os.path.dirname(targetfilename), exist_ok=True)
shutil.copyfile(tmpfilename, targetfilename)
modsources = sources["files"] + sources["custom_files"]
for directory, mods in mods_by_directory(
modsources, sources["path_strip_prefix"]).items():
tmpfilename = os.path.join(tmpdirname, "mod.rs")
generate_mod(tmpfilename, mods)
targetmoddir = rust_path_for(
directory,
sources["path_strip_prefix"],
sources["path_add_prefix"],
)
targetfilename = os.path.join(targetmoddir, "mod.rs")
if args.check_only:
if not filecmp.cmp(tmpfilename, targetfilename):
raise AssertionError("Target {} does not match", targetfilename)
else:
os.makedirs(os.path.dirname(targetfilename), exist_ok=True)
shutil.copyfile(tmpfilename, targetfilename)
except AssertionError as e:
if not str(e):
raise
print("Error: {}".format(e))
return 1
if __name__ == "__main__":
exit(main())