| #!/usr/bin/env python |
| # Copyright (C) 2018 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| import os |
| import re |
| import sys |
| import argparse |
| import tempfile |
| import subprocess |
| import hashlib |
| import textwrap |
| from compat import iteritems |
| |
| SOURCE_TARGET = [ |
| ( |
| 'protos/perfetto/config/perfetto_config.proto', |
| 'src/perfetto_cmd/perfetto_config.descriptor.h', |
| ), |
| ( |
| 'src/protozero/test/example_proto/test_messages.proto', |
| 'src/protozero/test/example_proto/test_messages.descriptor.h' |
| ), |
| ( |
| 'protos/perfetto/trace/track_event/track_event.proto', |
| 'src/trace_processor/importers/proto/track_event.descriptor.h' |
| ), |
| ( |
| 'protos/perfetto/trace_processor/trace_processor.proto', |
| 'src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor' |
| ), |
| ( |
| 'protos/perfetto/metrics/metrics.proto', |
| 'src/trace_processor/python/perfetto/trace_processor/metrics.descriptor' |
| ), |
| ] |
| |
| ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |
| |
| SCRIPT_PATH = 'tools/gen_binary_descriptors' |
| |
| |
| def hash_path(path): |
| hash = hashlib.sha1() |
| with open(os.path.join(ROOT_DIR, path), 'rb') as f: |
| hash.update(f.read()) |
| return hash.hexdigest() |
| |
| |
| def find_protoc(): |
| for root, _, files in os.walk(os.path.join(ROOT_DIR, 'out')): |
| if 'protoc' in files: |
| return os.path.join(root, 'protoc') |
| return None |
| |
| |
| def check_using_shas(source, target, file_with_shas): |
| with open(file_with_shas, 'rb') as f: |
| s = f.read() |
| |
| hashes = re.findall(r'// SHA1\((.*)\)\n// (.*)\n', s.decode()) |
| assert sorted([SCRIPT_PATH, source]) == sorted([key for key, _ in hashes]) |
| for path, expected_sha1 in hashes: |
| actual_sha1 = hash_path(os.path.join(ROOT_DIR, path)) |
| assert actual_sha1 == expected_sha1, \ |
| 'In {} hash given for {} did not match'.format(target, path) |
| |
| |
| def check_raw_descriptor(source, target): |
| sha1_file = target + '.sha1' |
| assert os.path.exists(sha1_file), \ |
| 'SHA1 file {} does not exist and so cannot be checked'.format(sha1_file) |
| |
| check_using_shas(source, target, sha1_file) |
| |
| |
| def check(source, target): |
| assert os.path.exists(os.path.join(ROOT_DIR, target)), \ |
| 'Output file {} does not exist and so cannot be checked'.format(target) |
| |
| if target.endswith('.descriptor.h'): |
| check_using_shas(source, target, target) |
| elif target.endswith('.descriptor'): |
| check_raw_descriptor(source, target) |
| |
| |
| def write_cpp_header(source, target, descriptor_bytes): |
| _, source_name = os.path.split(source) |
| _, target_name = os.path.split(target) |
| assert source_name.replace('.proto', '.descriptor.h') == target_name |
| |
| proto_name = source_name[:-len('.proto')].title().replace("_", "") |
| try: |
| ord(descriptor_bytes[0]) |
| ordinal = ord |
| except TypeError: |
| ordinal = lambda x: x |
| binary = '{' + ', '.join('{0:#04x}' |
| .format(ordinal(c)) for c in descriptor_bytes) + '}' |
| binary = textwrap.fill( |
| binary, width=80, initial_indent=' ', subsequent_indent=' ') |
| |
| with open(os.path.join(ROOT_DIR, target), 'wb') as f: |
| f.write("""/* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef {include_guard} |
| #define {include_guard} |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <array> |
| |
| // This file was autogenerated by tools/gen_binary_descriptors. Do not edit. |
| |
| // SHA1({script_path}) |
| // {script_hash} |
| // SHA1({source_path}) |
| // {source_hash} |
| |
| // This is the proto {proto_name} encoded as a ProtoFileDescriptor to allow |
| // for reflection without libprotobuf full/non-lite protos. |
| |
| namespace perfetto {{ |
| |
| constexpr std::array<uint8_t, {size}> k{proto_name}Descriptor{{ |
| {binary}}}; |
| |
| }} // namespace perfetto |
| |
| #endif // {include_guard} |
| """.format( |
| proto_name=proto_name, |
| size=len(descriptor_bytes), |
| binary=binary, |
| include_guard=target.replace('/', '_').replace('.', '_').upper() + '_', |
| script_path=SCRIPT_PATH, |
| script_hash=hash_path(__file__), |
| source_path=source, |
| source_hash=hash_path(os.path.join(source)), |
| ).encode()) |
| |
| |
| def write_raw_descriptor(source, target, descriptor_bytes): |
| with open(target, 'wb') as out: |
| out.write(descriptor_bytes) |
| |
| sha1_path = target + '.sha1' |
| with open(sha1_path, 'wb') as c: |
| c.write(""" |
| // SHA1({script_path}) |
| // {script_hash} |
| // SHA1({source_path}) |
| // {source_hash} |
| """.format( |
| script_path=SCRIPT_PATH, |
| script_hash=hash_path(__file__), |
| source_path=source, |
| source_hash=hash_path(os.path.join(source)), |
| ).encode()) |
| |
| |
| |
| def generate(source, target, protoc_path): |
| with tempfile.NamedTemporaryFile() as fdescriptor: |
| subprocess.check_call([ |
| protoc_path, |
| '--include_imports', |
| '--proto_path=.', |
| '--proto_path=' + \ |
| os.path.join(ROOT_DIR, "buildtools", "protobuf", "src"), |
| '--descriptor_set_out={}'.format(fdescriptor.name), |
| source, |
| ], |
| cwd=ROOT_DIR) |
| |
| s = fdescriptor.read() |
| if target.endswith('.descriptor.h'): |
| write_cpp_header(source, target, s) |
| elif target.endswith('.descriptor'): |
| write_raw_descriptor(source, target, s) |
| else: |
| raise Exception('Unsupported target extension for file {}'.format(target)) |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--check-only', action='store_true') |
| parser.add_argument('--protoc') |
| args = parser.parse_args() |
| |
| try: |
| for source, target in SOURCE_TARGET: |
| if args.check_only: |
| check(source, target) |
| else: |
| protoc = args.protoc or find_protoc() |
| assert protoc, 'protoc not found specific (--protoc PROTOC_PATH)' |
| assert os.path.exists(protoc), '{} does not exist'.format(protoc) |
| if protoc is not args.protoc: |
| print('Using protoc: {}'.format(protoc)) |
| generate(source, target, protoc) |
| except AssertionError as e: |
| if not str(e): |
| raise |
| print('Error: {}'.format(e)) |
| return 1 |
| |
| |
| if __name__ == '__main__': |
| exit(main()) |