xref: /aosp_15_r20/external/openscreen/third_party/protobuf/protoc_wrapper.py (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
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