123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- #!/usr/bin/env python3
- # Copyright (c) 2012 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """
- A simple wrapper for protoc.
- Script for //third_party/protobuf/proto_library.gni .
- Features:
- - Inserts #include for extra header automatically.
- - Prevents bad proto names.
- - Works around protoc's bad descriptor file generation.
- Ninja expects the format:
- target: deps
- But protoc just outputs:
- deps
- This script adds the "target:" part.
- """
- from __future__ import print_function
- import argparse
- import os.path
- import subprocess
- import sys
- import tempfile
- PROTOC_INCLUDE_POINT = "// @@protoc_insertion_point(includes)"
- def FormatGeneratorOptions(options):
- if not options:
- return ""
- if options.endswith(":"):
- return options
- return options + ":"
- def VerifyProtoNames(protos):
- for filename in protos:
- if "-" in filename:
- raise RuntimeError("Proto file names must not contain hyphens "
- "(see http://crbug.com/386125 for more information).")
- def StripProtoExtension(filename):
- if not filename.endswith(".proto"):
- raise RuntimeError("Invalid proto filename extension: "
- "{0} .".format(filename))
- return filename.rsplit(".", 1)[0]
- def WriteIncludes(headers, include):
- for filename in headers:
- include_point_found = False
- contents = []
- with open(filename) as f:
- for line in f:
- stripped_line = line.strip()
- contents.append(stripped_line)
- if stripped_line == PROTOC_INCLUDE_POINT:
- if include_point_found:
- raise RuntimeError("Multiple include points found.")
- include_point_found = True
- extra_statement = "#include \"{0}\"".format(include)
- contents.append(extra_statement)
- if not include_point_found:
- raise RuntimeError("Include point not found in header: "
- "{0} .".format(filename))
- with open(filename, "w") as f:
- for line in contents:
- print(line, file=f)
- def main(argv):
- parser = argparse.ArgumentParser()
- parser.add_argument("--protoc", required=True,
- help="Relative path to compiler.")
- parser.add_argument("--proto-in-dir", required=True,
- help="Base directory with source protos.")
- parser.add_argument("--cc-out-dir",
- help="Output directory for standard C++ generator.")
- parser.add_argument("--py-out-dir",
- help="Output directory for standard Python generator.")
- parser.add_argument("--js-out-dir",
- help="Output directory for standard JS generator.")
- parser.add_argument("--plugin-out-dir",
- help="Output directory for custom generator plugin.")
- parser.add_argument('--enable-kythe-annotations', action='store_true',
- help='Enable generation of Kythe kzip, used for '
- 'codesearch.')
- parser.add_argument("--plugin",
- help="Relative path to custom generator plugin.")
- # TODO(crbug.com/1237958): Remove allow_optional when proto rolls to 3.15.
- parser.add_argument("--allow-optional",
- action='store_true',
- help="Enables experimental_allow_proto3_optional.")
- parser.add_argument("--plugin-options",
- help="Custom generator plugin options.")
- parser.add_argument("--cc-options",
- help="Standard C++ generator options.")
- parser.add_argument("--include",
- help="Name of include to insert into generated headers.")
- parser.add_argument("--import-dir", action="append", default=[],
- help="Extra import directory for protos, can be repeated."
- )
- parser.add_argument("--descriptor-set-out",
- help="Path to write a descriptor.")
- parser.add_argument(
- "--descriptor-set-dependency-file",
- help="Path to write the dependency file for descriptor set.")
- # The meaning of this flag is flipped compared to the corresponding protoc
- # flag due to this script previously passing --include_imports. Removing the
- # --include_imports is likely to have unintended consequences.
- parser.add_argument(
- "--exclude-imports",
- help="Do not include imported files into generated descriptor.",
- action="store_true",
- default=False)
- parser.add_argument("protos", nargs="+",
- help="Input protobuf definition file(s).")
- options = parser.parse_args(argv)
- proto_dir = os.path.relpath(options.proto_in_dir)
- protoc_cmd = [os.path.realpath(options.protoc)]
- protos = options.protos
- headers = []
- VerifyProtoNames(protos)
- if options.py_out_dir:
- protoc_cmd += ["--python_out", options.py_out_dir]
- if options.js_out_dir:
- protoc_cmd += [
- "--js_out",
- "one_output_file_per_input_file,binary:" + options.js_out_dir
- ]
- if options.cc_out_dir:
- cc_out_dir = options.cc_out_dir
- cc_options_list = []
- if options.enable_kythe_annotations:
- cc_options_list.extend([
- 'annotate_headers', 'annotation_pragma_name=kythe_metadata',
- 'annotation_guard_name=KYTHE_IS_RUNNING'
- ])
- # cc_options will likely have trailing colon so needs to be inserted at the
- # end.
- if options.cc_options:
- cc_options_list.append(options.cc_options)
- if options.allow_optional:
- protoc_cmd += ["--experimental_allow_proto3_optional"]
- cc_options = FormatGeneratorOptions(','.join(cc_options_list))
- protoc_cmd += ["--cpp_out", cc_options + cc_out_dir]
- for filename in protos:
- stripped_name = StripProtoExtension(filename)
- headers.append(os.path.join(cc_out_dir, stripped_name + ".pb.h"))
- if options.plugin_out_dir:
- plugin_options = FormatGeneratorOptions(options.plugin_options)
- protoc_cmd += [
- "--plugin", "protoc-gen-plugin=" + os.path.relpath(options.plugin),
- "--plugin_out", plugin_options + options.plugin_out_dir
- ]
- protoc_cmd += ["--proto_path", proto_dir]
- for path in options.import_dir:
- protoc_cmd += ["--proto_path", path]
- protoc_cmd += [os.path.join(proto_dir, name) for name in protos]
- if options.descriptor_set_out:
- protoc_cmd += ["--descriptor_set_out", options.descriptor_set_out]
- if not options.exclude_imports:
- protoc_cmd += ["--include_imports"]
- dependency_file_data = None
- if options.descriptor_set_out and options.descriptor_set_dependency_file:
- protoc_cmd += ['--dependency_out', options.descriptor_set_dependency_file]
- ret = subprocess.call(protoc_cmd)
- with open(options.descriptor_set_dependency_file, 'rb') as f:
- dependency_file_data = f.read().decode('utf-8')
- ret = subprocess.call(protoc_cmd)
- if ret != 0:
- if ret <= -100:
- # Windows error codes such as 0xC0000005 and 0xC0000409 are much easier to
- # recognize and differentiate in hex. In order to print them as unsigned
- # hex we need to add 4 Gig to them.
- error_number = "0x%08X" % (ret + (1 << 32))
- else:
- error_number = "%d" % ret
- raise RuntimeError("Protoc has returned non-zero status: "
- "{0}".format(error_number))
- if dependency_file_data:
- with open(options.descriptor_set_dependency_file, 'w') as f:
- f.write(options.descriptor_set_out + ":")
- f.write(dependency_file_data)
- if options.include:
- WriteIncludes(headers, options.include)
- if __name__ == "__main__":
- try:
- main(sys.argv[1:])
- except RuntimeError as e:
- print(e, file=sys.stderr)
- sys.exit(1)
|