xref: /aosp_15_r20/external/perfetto/tools/gen_c_protos (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1#!/usr/bin/env python3
2# Copyright (C) 2023 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
16import argparse
17import filecmp
18import os
19import pathlib
20import shutil
21import subprocess
22import sys
23import tempfile
24
25SOURCE_FILES = [
26  {
27    'files': [
28      'protos/perfetto/common/builtin_clock.proto',
29      'protos/perfetto/common/data_source_descriptor.proto',
30      'protos/perfetto/config/data_source_config.proto',
31      'protos/perfetto/config/trace_config.proto',
32      'protos/perfetto/config/track_event/track_event_config.proto',
33      'protos/perfetto/trace/android/android_track_event.proto',
34      'protos/perfetto/trace/clock_snapshot.proto',
35      'protos/perfetto/trace/interned_data/interned_data.proto',
36      'protos/perfetto/trace/test_event.proto',
37      'protos/perfetto/trace/trace.proto',
38      'protos/perfetto/trace/trace_packet.proto',
39      'protos/perfetto/trace/track_event/counter_descriptor.proto',
40      'protos/perfetto/trace/track_event/debug_annotation.proto',
41      'protos/perfetto/trace/track_event/track_descriptor.proto',
42      'protos/perfetto/trace/track_event/track_event.proto',
43    ],
44    'guard_strip_prefix': 'PROTOS_PERFETTO_',
45    'guard_add_prefix':'INCLUDE_PERFETTO_PUBLIC_PROTOS_',
46    'path_strip_prefix': 'protos/perfetto',
47    'path_add_prefix': 'perfetto/public/protos',
48    'include_prefix': 'include/',
49  },
50  {
51    'files': [
52      'src/protozero/test/example_proto/extensions.proto',
53      'src/protozero/test/example_proto/library.proto',
54      'src/protozero/test/example_proto/library_internals/galaxies.proto',
55      'src/protozero/test/example_proto/other_package/test_messages.proto',
56      'src/protozero/test/example_proto/subpackage/test_messages.proto',
57      'src/protozero/test/example_proto/test_messages.proto',
58      'src/protozero/test/example_proto/upper_import.proto',
59    ],
60    'guard_strip_prefix': 'SRC_PROTOZERO_TEST_EXAMPLE_PROTO_',
61    'guard_add_prefix':'SRC_SHARED_LIB_TEST_PROTOS_',
62    'path_strip_prefix': 'src/protozero/test/example_proto',
63    'path_add_prefix': 'src/shared_lib/test/protos',
64  },
65]
66
67ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
68IS_WIN = sys.platform.startswith('win')
69
70SCRIPT_PATH = 'tools/gen_c_protos'
71
72
73def protozero_c_plugin_path(out_directory):
74  path = os.path.join(out_directory,
75                      'protozero_c_plugin') + ('.exe' if IS_WIN else '')
76  assert os.path.isfile(path)
77  return path
78
79
80def protoc_path(out_directory):
81  path = os.path.join(out_directory, 'protoc') + ('.exe' if IS_WIN else '')
82  assert os.path.isfile(path)
83  return path
84
85
86def call(cmd, *args):
87  path = os.path.join('tools', cmd)
88  command = ['python3', path] + list(args)
89  print('Running', ' '.join(command))
90  try:
91    subprocess.check_call(command, cwd=ROOT_DIR)
92  except subprocess.CalledProcessError as e:
93    assert False, 'Command: {} failed'.format(' '.join(command))
94
95
96# Reformats filename
97def clang_format(filename):
98  path = os.path.join(ROOT_DIR, 'third_party', 'clang-format',
99                      'clang-format') + ('.exe' if IS_WIN else '')
100  assert os.path.isfile(
101      path), "clang-format not found. Run tools/install-build-deps"
102  subprocess.check_call([
103      path, '--style=file:{}'.format(os.path.join(ROOT_DIR, '.clang-format')),
104      '-i', filename
105  ],
106                        cwd=ROOT_DIR)
107
108
109# Transforms filename extension like the ProtoZero C plugin
110def transform_extension(filename):
111  old_suffix = ".proto"
112  new_suffix = ".pzc.h"
113  if filename.endswith(old_suffix):
114    return filename[:-len(old_suffix)] + new_suffix
115  return filename
116
117
118def generate(source, outdir, protoc_path, protozero_c_plugin_path, guard_strip_prefix, guard_add_prefix, path_strip_prefix, path_add_prefix):
119  options = {
120      'guard_strip_prefix': guard_strip_prefix,
121      'guard_add_prefix': guard_add_prefix,
122      'path_strip_prefix': path_strip_prefix,
123      'path_add_prefix': path_add_prefix,
124      'invoker': SCRIPT_PATH,
125  }
126  serialized_options = ','.join(
127      ['{}={}'.format(name, value) for name, value in options.items()])
128  subprocess.check_call([
129      protoc_path,
130      '--proto_path=.',
131      '--plugin=protoc-gen-plugin={}'.format(protozero_c_plugin_path),
132      '--plugin_out={}:{}'.format(serialized_options, outdir),
133      source,
134  ],
135                        cwd=ROOT_DIR)
136
137
138# Given filename, the path of a header generated by the ProtoZero C plugin,
139# returns the path where the header should go in the public include directory.
140# Example
141#
142# include_path_for("protos/perfetto/trace/trace.pzc.h") ==
143# "include/perfetto/public/protos/trace/trace.pzc.h"
144def include_path_for(filename):
145  return os.path.join('include', 'perfetto', 'public', 'protos',
146                      *pathlib.Path(transform_extension(filename)).parts[2:])
147
148
149def main():
150  parser = argparse.ArgumentParser()
151  parser.add_argument('--check-only', action='store_true')
152  parser.add_argument('OUT')
153  args = parser.parse_args()
154  out = args.OUT
155
156  call('ninja', '-C', out, 'protoc', 'protozero_c_plugin')
157
158  try:
159    with tempfile.TemporaryDirectory() as tmpdirname:
160      for sources in SOURCE_FILES:
161        for source in sources['files']:
162          generate(source, tmpdirname, protoc_path(out), protozero_c_plugin_path(out),
163                   guard_strip_prefix=sources['guard_strip_prefix'],
164                   guard_add_prefix=sources['guard_add_prefix'],
165                   path_strip_prefix=sources['path_strip_prefix'],
166                   path_add_prefix=sources['path_add_prefix'],
167                   )
168
169          tmpfilename = os.path.join(tmpdirname, transform_extension(source))
170          clang_format(tmpfilename)
171          if source.startswith(sources['path_strip_prefix']):
172            targetfilename = source[len(sources['path_strip_prefix']):]
173          else:
174            targetfilename = source
175
176          targetfilename = sources['path_add_prefix'] + targetfilename
177
178          if 'include_prefix' in sources:
179            targetfilename = os.path.join(sources['include_prefix'], targetfilename)
180          targetfilename = transform_extension(targetfilename)
181
182          if args.check_only:
183            if not filecmp.cmp(tmpfilename, targetfilename):
184              raise AssertionError('Target {} does not match', targetfilename)
185          else:
186            os.makedirs(os.path.dirname(targetfilename), exist_ok=True)
187            shutil.copyfile(tmpfilename, targetfilename)
188
189  except AssertionError as e:
190    if not str(e):
191      raise
192    print('Error: {}'.format(e))
193    return 1
194
195
196if __name__ == '__main__':
197  exit(main())
198