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