1#!/usr/bin/env python
2# Copyright (C) 2018 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
16from __future__ import absolute_import
17from __future__ import division
18from __future__ import print_function
19import os
20import re
21import sys
22import argparse
23import tempfile
24import subprocess
25import hashlib
26import textwrap
27from compat import iteritems
28
29SOURCE_TARGET = [
30    (
31      'protos/perfetto/config/perfetto_config.proto',
32      'src/perfetto_cmd/perfetto_config.descriptor.h',
33    ),
34    (
35      'src/protozero/test/example_proto/test_messages.proto',
36      'src/protozero/test/example_proto/test_messages.descriptor.h'
37    ),
38    (
39      'protos/perfetto/trace/track_event/track_event.proto',
40      'src/trace_processor/importers/proto/track_event.descriptor.h'
41    ),
42    (
43      'protos/perfetto/trace_processor/trace_processor.proto',
44      'src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor'
45    ),
46    (
47      'protos/perfetto/metrics/metrics.proto',
48      'src/trace_processor/python/perfetto/trace_processor/metrics.descriptor'
49    ),
50]
51
52ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
53
54SCRIPT_PATH = 'tools/gen_binary_descriptors'
55
56
57def hash_path(path):
58  hash = hashlib.sha1()
59  with open(os.path.join(ROOT_DIR, path), 'rb') as f:
60    hash.update(f.read())
61  return hash.hexdigest()
62
63
64def find_protoc():
65  for root, _, files in os.walk(os.path.join(ROOT_DIR, 'out')):
66    if 'protoc' in files:
67      return os.path.join(root, 'protoc')
68  return None
69
70
71def check_using_shas(source, target, file_with_shas):
72  with open(file_with_shas, 'rb') as f:
73    s = f.read()
74
75  hashes = re.findall(r'// SHA1\((.*)\)\n// (.*)\n', s.decode())
76  assert sorted([SCRIPT_PATH, source]) == sorted([key for key, _ in hashes])
77  for path, expected_sha1 in hashes:
78    actual_sha1 = hash_path(os.path.join(ROOT_DIR, path))
79    assert actual_sha1 == expected_sha1, \
80        'In {} hash given for {} did not match'.format(target, path)
81
82
83def check_raw_descriptor(source, target):
84  sha1_file = target + '.sha1'
85  assert os.path.exists(sha1_file), \
86      'SHA1 file {} does not exist and so cannot be checked'.format(sha1_file)
87
88  check_using_shas(source, target, sha1_file)
89
90
91def check(source, target):
92  assert os.path.exists(os.path.join(ROOT_DIR, target)), \
93      'Output file {} does not exist and so cannot be checked'.format(target)
94
95  if target.endswith('.descriptor.h'):
96    check_using_shas(source, target, target)
97  elif target.endswith('.descriptor'):
98    check_raw_descriptor(source, target)
99
100
101def write_cpp_header(source, target, descriptor_bytes):
102  _, source_name = os.path.split(source)
103  _, target_name = os.path.split(target)
104  assert source_name.replace('.proto', '.descriptor.h') == target_name
105
106  proto_name = source_name[:-len('.proto')].title().replace("_", "")
107  try:
108    ord(descriptor_bytes[0])
109    ordinal = ord
110  except TypeError:
111    ordinal = lambda x: x
112  binary = '{' + ', '.join('{0:#04x}'
113    .format(ordinal(c)) for c in descriptor_bytes) + '}'
114  binary = textwrap.fill(
115      binary, width=80, initial_indent='    ', subsequent_indent='     ')
116
117  with open(os.path.join(ROOT_DIR, target), 'wb') as f:
118    f.write("""/*
119 * Copyright (C) 2019 The Android Open Source Project
120 *
121 * Licensed under the Apache License, Version 2.0 (the "License");
122 * you may not use this file except in compliance with the License.
123 * You may obtain a copy of the License at
124 *
125 *      http://www.apache.org/licenses/LICENSE-2.0
126 *
127 * Unless required by applicable law or agreed to in writing, software
128 * distributed under the License is distributed on an "AS IS" BASIS,
129 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130 * See the License for the specific language governing permissions and
131 * limitations under the License.
132 */
133
134#ifndef {include_guard}
135#define {include_guard}
136
137#include <stddef.h>
138#include <stdint.h>
139
140#include <array>
141
142// This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
143
144// SHA1({script_path})
145// {script_hash}
146// SHA1({source_path})
147// {source_hash}
148
149// This is the proto {proto_name} encoded as a ProtoFileDescriptor to allow
150// for reflection without libprotobuf full/non-lite protos.
151
152namespace perfetto {{
153
154constexpr std::array<uint8_t, {size}> k{proto_name}Descriptor{{
155{binary}}};
156
157}}  // namespace perfetto
158
159#endif  // {include_guard}
160""".format(
161        proto_name=proto_name,
162        size=len(descriptor_bytes),
163        binary=binary,
164        include_guard=target.replace('/', '_').replace('.', '_').upper() + '_',
165        script_path=SCRIPT_PATH,
166        script_hash=hash_path(__file__),
167        source_path=source,
168        source_hash=hash_path(os.path.join(source)),
169    ).encode())
170
171
172def write_raw_descriptor(source, target, descriptor_bytes):
173  with open(target, 'wb') as out:
174    out.write(descriptor_bytes)
175
176  sha1_path = target + '.sha1'
177  with open(sha1_path, 'wb') as c:
178    c.write("""
179// SHA1({script_path})
180// {script_hash}
181// SHA1({source_path})
182// {source_hash}
183""".format(
184        script_path=SCRIPT_PATH,
185        script_hash=hash_path(__file__),
186        source_path=source,
187        source_hash=hash_path(os.path.join(source)),
188    ).encode())
189
190
191
192def generate(source, target, protoc_path):
193  with tempfile.NamedTemporaryFile() as fdescriptor:
194    subprocess.check_call([
195        protoc_path,
196        '--include_imports',
197        '--proto_path=.',
198        '--proto_path=' + \
199            os.path.join(ROOT_DIR, "buildtools", "protobuf", "src"),
200        '--descriptor_set_out={}'.format(fdescriptor.name),
201        source,
202    ],
203                          cwd=ROOT_DIR)
204
205    s = fdescriptor.read()
206    if target.endswith('.descriptor.h'):
207      write_cpp_header(source, target, s)
208    elif target.endswith('.descriptor'):
209      write_raw_descriptor(source, target, s)
210    else:
211      raise Exception('Unsupported target extension for file {}'.format(target))
212
213def main():
214  parser = argparse.ArgumentParser()
215  parser.add_argument('--check-only', action='store_true')
216  parser.add_argument('--protoc')
217  args = parser.parse_args()
218
219  try:
220    for source, target in SOURCE_TARGET:
221      if args.check_only:
222        check(source, target)
223      else:
224        protoc = args.protoc or find_protoc()
225        assert protoc, 'protoc not found specific (--protoc PROTOC_PATH)'
226        assert os.path.exists(protoc), '{} does not exist'.format(protoc)
227        if protoc is not args.protoc:
228          print('Using protoc: {}'.format(protoc))
229        generate(source, target, protoc)
230  except AssertionError as e:
231    if not str(e):
232      raise
233    print('Error: {}'.format(e))
234    return 1
235
236
237if __name__ == '__main__':
238  exit(main())
239