1#!/usr/bin/env python 2# Copyright 2019 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""" 7A simple wrapper for protoc. 8Script for //third_party/protobuf/proto_library.gni . 9Features: 10- Inserts #include for extra header automatically. 11- Prevents bad proto names. 12""" 13 14from __future__ import print_function 15import argparse 16import os.path 17import subprocess 18import sys 19import tempfile 20 21PROTOC_INCLUDE_POINT = "// @@protoc_insertion_point(includes)" 22 23 24def FormatGeneratorOptions(options): 25 if not options: 26 return "" 27 if options.endswith(":"): 28 return options 29 return options + ":" 30 31 32def VerifyProtoNames(protos): 33 for filename in protos: 34 if "-" in filename: 35 raise RuntimeError("Proto file names must not contain hyphens " 36 "(see http://crbug.com/386125 for more information).") 37 38 39def StripProtoExtension(filename): 40 if not filename.endswith(".proto"): 41 raise RuntimeError("Invalid proto filename extension: " 42 "{0} .".format(filename)) 43 return filename.rsplit(".", 1)[0] 44 45 46def WriteIncludes(headers, include): 47 for filename in headers: 48 include_point_found = False 49 contents = [] 50 with open(filename) as f: 51 for line in f: 52 stripped_line = line.strip() 53 contents.append(stripped_line) 54 if stripped_line == PROTOC_INCLUDE_POINT: 55 if include_point_found: 56 raise RuntimeException("Multiple include points found.") 57 include_point_found = True 58 extra_statement = "#include \"{0}\"".format(include) 59 contents.append(extra_statement) 60 61 if not include_point_found: 62 raise RuntimeError("Include point not found in header: " 63 "{0} .".format(filename)) 64 65 with open(filename, "w") as f: 66 for line in contents: 67 print(line, file=f) 68 69 70def main(argv): 71 parser = argparse.ArgumentParser() 72 parser.add_argument("--protoc", 73 help="Relative path to compiler.") 74 75 parser.add_argument("--proto-in-dir", 76 help="Base directory with source protos.") 77 parser.add_argument("--cc-out-dir", 78 help="Output directory for standard C++ generator.") 79 parser.add_argument("--py-out-dir", 80 help="Output directory for standard Python generator.") 81 parser.add_argument("--plugin-out-dir", 82 help="Output directory for custom generator plugin.") 83 84 parser.add_argument("--cc-options", 85 help="Standard C++ generator options.") 86 parser.add_argument("--import-dir", action="append", default=[], 87 help="Extra import directory for protos, can be repeated." 88 ) 89 parser.add_argument("--include", 90 help="Name of include to insert into generated headers.") 91 parser.add_argument("--plugin", 92 help="Relative path to custom generator plugin.") 93 parser.add_argument("--plugin-options", 94 help="Custom generator plugin options.") 95 parser.add_argument("protos", nargs="+", 96 help="Input protobuf definition file(s).") 97 98 options = parser.parse_args() 99 100 proto_dir = os.path.relpath(options.proto_in_dir) 101 protoc_cmd = [os.path.realpath(options.protoc)] 102 103 protos = options.protos 104 headers = [] 105 VerifyProtoNames(protos) 106 107 if options.py_out_dir: 108 protoc_cmd += ["--python_out", options.py_out_dir] 109 110 if options.cc_out_dir: 111 cc_out_dir = options.cc_out_dir 112 cc_options = FormatGeneratorOptions(options.cc_options) 113 protoc_cmd += ["--cpp_out", cc_options + cc_out_dir] 114 for filename in protos: 115 stripped_name = StripProtoExtension(filename) 116 headers.append(os.path.join(cc_out_dir, stripped_name + ".pb.h")) 117 118 if options.plugin_out_dir: 119 plugin_options = FormatGeneratorOptions(options.plugin_options) 120 protoc_cmd += [ 121 "--plugin", "protoc-gen-plugin=" + os.path.relpath(options.plugin), 122 "--plugin_out", plugin_options + options.plugin_out_dir 123 ] 124 125 protoc_cmd += ["--proto_path", proto_dir] 126 for path in options.import_dir: 127 protoc_cmd += ["--proto_path", path] 128 129 protoc_cmd += [os.path.join(proto_dir, name) for name in protos] 130 131 ret = subprocess.call(protoc_cmd) 132 if ret != 0: 133 if ret <= -100: 134 # Windows error codes such as 0xC0000005 and 0xC0000409 are much easier to 135 # recognize and differentiate in hex. In order to print them as unsigned 136 # hex we need to add 4 Gig to them. 137 error_number = "0x%08X" % (ret + (1 << 32)) 138 else: 139 error_number = "%d" % ret 140 raise RuntimeError("Protoc has returned non-zero status: " 141 "{0}".format(error_number)) 142 143 if options.include: 144 WriteIncludes(headers, options.include) 145 146 147if __name__ == "__main__": 148 try: 149 main(sys.argv) 150 except RuntimeError as e: 151 print(e, file=sys.stderr) 152 sys.exit(1) 153