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