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