Home | History | Annotate | Download | only in tools
      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 
     16 from __future__ import absolute_import
     17 from __future__ import division
     18 from __future__ import print_function
     19 import os
     20 import re
     21 import sys
     22 import argparse
     23 import tempfile
     24 import subprocess
     25 import hashlib
     26 import textwrap
     27 
     28 SOURCE_TARGET = {
     29     'protos/perfetto/config/perfetto_config.proto':
     30             'src/perfetto_cmd/perfetto_config.descriptor.h',
     31 }
     32 
     33 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
     34 
     35 SCRIPT_PATH = 'tools/gen_binary_descriptors'
     36 
     37 
     38 def hash_path(path):
     39   hash = hashlib.sha1()
     40   with open(os.path.join(ROOT_DIR, path)) as f:
     41     hash.update(f.read())
     42   return hash.hexdigest()
     43 
     44 
     45 def find_protoc():
     46   for root, dirs, files in os.walk(ROOT_DIR):
     47     if 'protoc' in files:
     48       return os.path.join(root, 'protoc')
     49     for name in ('src', 'buildtools'):
     50       if name in dirs:
     51         dirs.remove(name)
     52   return None
     53 
     54 
     55 def check(source, target):
     56   assert os.path.exists(os.path.join(ROOT_DIR, target)), \
     57       'Output file {} does not exist and so cannot be checked'.format(target)
     58 
     59   with open(target, 'rb') as f:
     60     s = f.read()
     61 
     62   hashes = re.findall(r'// SHA1\((.*)\)\n// (.*)\n', s)
     63   assert sorted([SCRIPT_PATH, source]) == sorted([key for key, _ in hashes])
     64   for path, expected_sha1 in hashes:
     65     actual_sha1 = hash_path(os.path.join(ROOT_DIR, path))
     66     assert actual_sha1 == expected_sha1, \
     67         'In {} hash given for {} did not match'.format(target, path)
     68 
     69 
     70 def generate(source, target, protoc_path):
     71   _, source_name = os.path.split(source)
     72   _, target_name = os.path.split(target)
     73   assert source_name.replace('.proto', '.descriptor.h') == target_name
     74 
     75   with tempfile.NamedTemporaryFile() as fdescriptor:
     76     subprocess.check_call([
     77         protoc_path,
     78         '--proto_path=protos',
     79         '-o{}'.format(fdescriptor.name),
     80         source,
     81     ], cwd=ROOT_DIR)
     82 
     83     s = fdescriptor.read()
     84     proto_name = source_name[:-len('.proto')].title().replace("_", "")
     85     constant_name = 'k' + proto_name + 'Descriptor'
     86     binary = '{' + ', '.join('{0:#04x}'.format(ord(c)) for c in s) + '}'
     87     binary = textwrap.fill(binary,
     88         width=80,
     89         initial_indent='    ',
     90         subsequent_indent='     ')
     91     include_guard = target.replace('/', '_').replace('.', '_').upper() + '_'
     92 
     93     with open(os.path.join(ROOT_DIR, target), 'wb') as f:
     94       f.write("""
     95 #ifndef {include_guard}
     96 #define {include_guard}
     97 
     98 #include <stddef.h>
     99 #include <stdint.h>
    100 
    101 #include <array>
    102 
    103 // This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
    104 
    105 // SHA1({script_path})
    106 // {script_hash}
    107 // SHA1({source_path})
    108 // {source_hash}
    109 
    110 // This is the proto {proto_name} encoded as a ProtoFileDescriptor to allow
    111 // for reflection without libprotobuf full/non-lite protos.
    112 
    113 namespace perfetto {{
    114 
    115 constexpr std::array<uint8_t, {size}> {constant_name}{{
    116 {binary}}};
    117 
    118 }}  // namespace perfetto
    119 
    120 #endif  // {include_guard}
    121 """.format(**{
    122         'proto_name': proto_name,
    123         'size': len(s),
    124         'constant_name': constant_name,
    125         'binary': binary,
    126         'include_guard': include_guard,
    127         'script_path': SCRIPT_PATH,
    128         'script_hash': hash_path(__file__),
    129         'source_path': source,
    130         'source_hash': hash_path(os.path.join(source)),
    131       }))
    132 
    133 
    134 def main():
    135   parser = argparse.ArgumentParser()
    136   parser.add_argument('--check-only', action='store_true')
    137   parser.add_argument('--protoc')
    138   args = parser.parse_args()
    139 
    140   try:
    141     for source, target in SOURCE_TARGET.iteritems():
    142       if args.check_only:
    143           check(source, target)
    144       else:
    145         protoc = args.protoc or find_protoc()
    146         assert protoc, 'protoc not found specific (--protoc PROTOC_PATH)'
    147         assert os.path.exists(protoc), '{} does not exist'.format(protoc)
    148         if protoc is not args.protoc:
    149           print('Using protoc: {}'.format(protoc))
    150         generate(source, target, protoc)
    151   except AssertionError as e:
    152     if not str(e):
    153       raise
    154     print('Error: {}'.format(e))
    155     return 1
    156 
    157 if __name__ == '__main__':
    158   exit(main())
    159