blob: bc3c405ec783dde63d47eabd1a6669f753cae811 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""pw_protobuf compiler plugin.
This file implements a protobuf compiler plugin which generates C++ headers for
protobuf messages in the pw_protobuf format.
"""
import sys
from argparse import ArgumentParser, Namespace
from pathlib import Path
from shlex import shlex
from google.protobuf.compiler import plugin_pb2
from pw_protobuf import codegen_pwpb, options
def parse_parameter_options(parameter: str) -> Namespace:
"""Parses parameters passed through from protoc.
These parameters come in via passing `--${NAME}_out` parameters to protoc,
where protoc-gen-${NAME} is the supplied name of the plugin. At time of
writing, Blaze uses --pwpb_opt, whereas the script for GN uses --custom_opt.
"""
parser = ArgumentParser()
parser.add_argument(
'-I',
'--include-path',
dest='include_paths',
metavar='DIR',
action='append',
default=[],
type=Path,
help='Append DIR to options file search path',
)
parser.add_argument(
'--no-legacy-namespace',
dest='no_legacy_namespace',
action='store_true',
help='If set, suppresses `using namespace` declarations, which '
'disallows use of the legacy non-prefixed namespace',
)
parser.add_argument(
'--exclude-legacy-snake-case-field-name-enums',
dest='exclude_legacy_snake_case_field_name_enums',
action='store_true',
help='Do not generate legacy SNAKE_CASE names for field name enums.',
)
# protoc passes the custom arguments in shell quoted form, separated by
# commas. Use shlex to split them, correctly handling quoted sections, with
# equivalent options to IFS=","
lex = shlex(parameter)
lex.whitespace_split = True
lex.whitespace = ','
lex.commenters = ''
args = list(lex)
return parser.parse_args(args)
def process_proto_request(
req: plugin_pb2.CodeGeneratorRequest, res: plugin_pb2.CodeGeneratorResponse
) -> None:
"""Handles a protoc CodeGeneratorRequest message.
Generates code for the files in the request and writes the output to the
specified CodeGeneratorResponse message.
Args:
req: A CodeGeneratorRequest for a proto compilation.
res: A CodeGeneratorResponse to populate with the plugin's output.
"""
args = parse_parameter_options(req.parameter)
for proto_file in req.proto_file:
proto_options = options.load_options(
args.include_paths, Path(proto_file.name)
)
output_files = codegen_pwpb.process_proto_file(
proto_file,
proto_options,
suppress_legacy_namespace=args.no_legacy_namespace,
exclude_legacy_snake_case_field_name_enums=(
args.exclude_legacy_snake_case_field_name_enums
),
)
for output_file in output_files:
fd = res.file.add()
fd.name = output_file.name()
fd.content = output_file.content()
def main() -> int:
"""Protobuf compiler plugin entrypoint.
Reads a CodeGeneratorRequest proto from stdin and writes a
CodeGeneratorResponse to stdout.
"""
data = sys.stdin.buffer.read()
request = plugin_pb2.CodeGeneratorRequest.FromString(data)
response = plugin_pb2.CodeGeneratorResponse()
process_proto_request(request, response)
# Declare that this plugin supports optional fields in proto3.
response.supported_features |= ( # type: ignore[attr-defined]
response.FEATURE_PROTO3_OPTIONAL
) # type: ignore[attr-defined]
sys.stdout.buffer.write(response.SerializeToString())
return 0
if __name__ == '__main__':
sys.exit(main())