Home | History | Annotate | Download | only in protoc_wrapper
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """
      7 A simple wrapper for protoc.
      8 
      9 - Adds includes in generated headers.
     10 - Handles building with system protobuf as an option.
     11 """
     12 
     13 import fnmatch
     14 import optparse
     15 import os.path
     16 import shutil
     17 import subprocess
     18 import sys
     19 import tempfile
     20 
     21 PROTOC_INCLUDE_POINT = '// @@protoc_insertion_point(includes)\n'
     22 
     23 def ModifyHeader(header_file, extra_header):
     24   """Adds |extra_header| to |header_file|. Returns 0 on success.
     25 
     26   |extra_header| is the name of the header file to include.
     27   |header_file| is a generated protobuf cpp header.
     28   """
     29   include_point_found = False
     30   header_contents = []
     31   with open(header_file) as f:
     32     for line in f:
     33       header_contents.append(line)
     34       if line == PROTOC_INCLUDE_POINT:
     35         extra_header_msg = '#include "%s"\n' % extra_header
     36         header_contents.append(extra_header_msg)
     37         include_point_found = True;
     38   if not include_point_found:
     39     return 1
     40 
     41   with open(header_file, 'wb') as f:
     42     f.write(''.join(header_contents))
     43   return 0
     44 
     45 def ScanForBadFiles(scan_root):
     46   """Scan for bad file names, see http://crbug.com/386125 for details.
     47   Returns True if any filenames are bad. Outputs errors to stderr.
     48 
     49   |scan_root| is the path to the directory to be recursively scanned.
     50   """
     51   badname = False
     52   real_scan_root = os.path.realpath(scan_root)
     53   for dirpath, dirnames, filenames in os.walk(real_scan_root):
     54     matches = fnmatch.filter(filenames, '*-*.proto')
     55     if len(matches) > 0:
     56       if not badname:
     57         badname = True
     58         sys.stderr.write('proto files must not have hyphens in their names ('
     59                          'see http://crbug.com/386125 for more information):\n')
     60       for filename in matches:
     61         sys.stderr.write('  ' + os.path.join(real_scan_root,
     62                                              dirpath, filename) + '\n')
     63   return badname
     64 
     65 
     66 def RewriteProtoFilesForSystemProtobuf(path):
     67   wrapper_dir = tempfile.mkdtemp()
     68   try:
     69     for filename in os.listdir(path):
     70       if not filename.endswith('.proto'):
     71         continue
     72       with open(os.path.join(path, filename), 'r') as src_file:
     73         with open(os.path.join(wrapper_dir, filename), 'w') as dst_file:
     74           for line in src_file:
     75             # Remove lines that break build with system protobuf.
     76             # We cannot optimize for lite runtime, because system lite runtime
     77             # does not have a Chromium-specific hack to retain unknown fields.
     78             # Similarly, it does not understand corresponding option to control
     79             # the usage of that hack.
     80             if 'LITE_RUNTIME' in line or 'retain_unknown_fields' in line:
     81               continue
     82             dst_file.write(line)
     83 
     84     return wrapper_dir
     85   except:
     86     shutil.rmtree(wrapper_dir)
     87     raise
     88 
     89 
     90 def main(argv):
     91   parser = optparse.OptionParser()
     92   parser.add_option('--include', dest='extra_header',
     93                     help='The extra header to include. This must be specified '
     94                          'along with --protobuf.')
     95   parser.add_option('--protobuf', dest='generated_header',
     96                     help='The c++ protobuf header to add the extra header to. '
     97                          'This must be specified along with --include.')
     98   parser.add_option('--proto-in-dir',
     99                     help='The directory containing .proto files.')
    100   parser.add_option('--proto-in-file', help='Input file to compile.')
    101   parser.add_option('--use-system-protobuf', type=int, default=0,
    102                     help='Option to use system-installed protobuf '
    103                          'instead of bundled one.')
    104   (options, args) = parser.parse_args(sys.argv)
    105   if len(args) < 2:
    106     return 1
    107 
    108   if ScanForBadFiles(options.proto_in_dir):
    109     return 1
    110 
    111   proto_path = options.proto_in_dir
    112   if options.use_system_protobuf == 1:
    113     proto_path = RewriteProtoFilesForSystemProtobuf(proto_path)
    114   try:
    115     # Run what is hopefully protoc.
    116     protoc_args = args[1:]
    117     protoc_args += ['--proto_path=%s' % proto_path,
    118                     os.path.join(proto_path, options.proto_in_file)]
    119     ret = subprocess.call(protoc_args)
    120     if ret != 0:
    121       return ret
    122   finally:
    123     if options.use_system_protobuf == 1:
    124       # Remove temporary directory holding re-written files.
    125       shutil.rmtree(proto_path)
    126 
    127   # protoc succeeded, check to see if the generated cpp header needs editing.
    128   if not options.extra_header or not options.generated_header:
    129     return 0
    130   return ModifyHeader(options.generated_header, options.extra_header)
    131 
    132 
    133 if __name__ == '__main__':
    134   sys.exit(main(sys.argv))
    135