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