mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
262 lines
8.0 KiB
Python
Executable File
262 lines
8.0 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Copyright 2018 Google
|
|
#
|
|
# 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
|
|
#
|
|
# http://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.
|
|
|
|
"""Generates and massages protocol buffer outputs.
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import sys
|
|
|
|
import io
|
|
import nanopb_generator as nanopb
|
|
import os
|
|
import os.path
|
|
import shlex
|
|
|
|
from google.protobuf.descriptor_pb2 import FieldDescriptorProto
|
|
|
|
if sys.platform == 'win32':
|
|
import msvcrt # pylint: disable=g-import-not-at-top
|
|
|
|
# The plugin_pb2 package loads descriptors on import, but doesn't defend
|
|
# against multiple imports. Reuse the plugin package as loaded by the
|
|
# nanopb_generator.
|
|
plugin_pb2 = nanopb.plugin_pb2
|
|
nanopb_pb2 = nanopb.nanopb_pb2
|
|
|
|
|
|
def main():
|
|
# Parse request
|
|
if sys.platform == 'win32':
|
|
# Set stdin and stdout to binary mode
|
|
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
|
|
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
|
|
|
data = io.open(sys.stdin.fileno(), 'rb').read()
|
|
request = plugin_pb2.CodeGeneratorRequest.FromString(data)
|
|
|
|
# Preprocess inputs, changing types and nanopb defaults
|
|
use_anonymous_oneof(request)
|
|
use_bytes_for_strings(request)
|
|
use_malloc(request)
|
|
|
|
# Generate code
|
|
options = nanopb_parse_options(request)
|
|
parsed_files = nanopb_parse_files(request, options)
|
|
results = nanopb_generate(request, options, parsed_files)
|
|
response = nanopb_write(results)
|
|
|
|
# Write to stdout
|
|
io.open(sys.stdout.fileno(), 'wb').write(response.SerializeToString())
|
|
|
|
|
|
def use_malloc(request):
|
|
"""Mark all variable length items as requiring malloc.
|
|
|
|
By default nanopb renders string, bytes, and repeated fields (dynamic fields)
|
|
as having the C type pb_callback_t. Unfortunately this type is incompatible
|
|
with nanopb's union support.
|
|
|
|
The function performs the equivalent of adding the following annotation to
|
|
each dynamic field in all the protos.
|
|
|
|
string name = 1 [(nanopb).type = FT_POINTER];
|
|
|
|
Args:
|
|
request: A CodeGeneratorRequest from protoc. The descriptors are modified
|
|
in place.
|
|
"""
|
|
dynamic_types = [
|
|
FieldDescriptorProto.TYPE_STRING,
|
|
FieldDescriptorProto.TYPE_BYTES,
|
|
]
|
|
|
|
for _, message_type in iterate_messages(request):
|
|
for field in message_type.field:
|
|
dynamic_type = field.type in dynamic_types
|
|
repeated = field.label == FieldDescriptorProto.LABEL_REPEATED
|
|
|
|
if dynamic_type or repeated:
|
|
ext = field.options.Extensions[nanopb_pb2.nanopb]
|
|
ext.type = nanopb_pb2.FT_POINTER
|
|
|
|
|
|
def use_anonymous_oneof(request):
|
|
"""Use anonymous unions for oneofs if they're the only one in a message.
|
|
|
|
Equivalent to setting this option on messages where it applies:
|
|
|
|
option (nanopb).anonymous_oneof = true;
|
|
|
|
Args:
|
|
request: A CodeGeneratorRequest from protoc. The descriptors are modified
|
|
in place.
|
|
"""
|
|
for _, message_type in iterate_messages(request):
|
|
if len(message_type.oneof_decl) == 1:
|
|
ext = message_type.options.Extensions[nanopb_pb2.nanopb_msgopt]
|
|
ext.anonymous_oneof = True
|
|
|
|
|
|
def use_bytes_for_strings(request):
|
|
"""Always use the bytes type instead of string.
|
|
|
|
By default, nanopb renders proto strings as having the C type char* and does
|
|
not include a separate size field, getting the length of the string via
|
|
strlen(). Unfortunately this prevents using strings with embedded nulls,
|
|
which is something the wire format supports.
|
|
|
|
Fortunately, string and bytes proto fields are identical on the wire and
|
|
nanopb's bytes representation does have an explicit length, so this function
|
|
changes the types of all string fields to bytes. The generated code will now
|
|
contain pb_bytes_array_t.
|
|
|
|
There's no nanopb or proto option to control this behavior. The equivalent
|
|
would be to hand edit all the .proto files :-(.
|
|
|
|
Args:
|
|
request: A CodeGeneratorRequest from protoc. The descriptors are modified
|
|
in place.
|
|
"""
|
|
for names, message_type in iterate_messages(request):
|
|
for field in message_type.field:
|
|
if field.type == FieldDescriptorProto.TYPE_STRING:
|
|
field.type = FieldDescriptorProto.TYPE_BYTES
|
|
|
|
|
|
def iterate_messages(request):
|
|
"""Iterates over all messages in all files in the request.
|
|
|
|
Args:
|
|
request: A CodeGeneratorRequest passed by protoc.
|
|
|
|
Yields:
|
|
names: a nanopb.Names object giving a qualified name for the message
|
|
message_type: a DescriptorProto for the message.
|
|
"""
|
|
for fdesc in request.proto_file:
|
|
for names, message_type in nanopb.iterate_messages(fdesc):
|
|
yield names, message_type
|
|
|
|
|
|
def nanopb_parse_options(request):
|
|
"""Parses nanopb_generator command-line options from the given request.
|
|
|
|
Args:
|
|
request: A CodeGeneratorRequest passed by protoc.
|
|
|
|
Returns:
|
|
Nanopb's options object, obtained via optparser.
|
|
"""
|
|
# Parse options the same as nanopb_generator.main_plugin() does.
|
|
args = shlex.split(request.parameter)
|
|
options, _ = nanopb.optparser.parse_args(args)
|
|
|
|
# Force certain options
|
|
options.extension = '.nanopb'
|
|
|
|
# Replicate options setup from nanopb_generator.main_plugin.
|
|
nanopb.Globals.verbose_options = options.verbose
|
|
|
|
# Google's protoc does not currently indicate the full path of proto files.
|
|
# Instead always add the main file path to the search dirs, that works for
|
|
# the common case.
|
|
options.options_path.append(os.path.dirname(request.file_to_generate[0]))
|
|
|
|
return options
|
|
|
|
|
|
def nanopb_parse_files(request, options):
|
|
"""Parses the files in the given request into nanopb ProtoFile objects.
|
|
|
|
Args:
|
|
request: A CodeGeneratorRequest, as passed by protoc.
|
|
options: The command-line options from nanopb_parse_options.
|
|
|
|
Returns:
|
|
A dictionary of filename to nanopb.ProtoFile objects, each one representing
|
|
the parsed form of a FileDescriptor in the request.
|
|
"""
|
|
# Process any include files first, in order to have them
|
|
# available as dependencies
|
|
parsed_files = {}
|
|
for fdesc in request.proto_file:
|
|
parsed_files[fdesc.name] = nanopb.parse_file(fdesc.name, fdesc, options)
|
|
|
|
return parsed_files
|
|
|
|
|
|
def nanopb_generate(request, options, parsed_files):
|
|
"""Generates C sources from the given parsed files.
|
|
|
|
Args:
|
|
request: A CodeGeneratorRequest, as passed by protoc.
|
|
options: The command-line options from nanopb_parse_options.
|
|
parsed_files: A dictionary of filename to nanopb.ProtoFile, as returned by
|
|
nanopb_parse_files().
|
|
|
|
Returns:
|
|
A list of nanopb output dictionaries, each one representing the code
|
|
generation result for each file to generate. The output dictionaries have
|
|
the following form:
|
|
|
|
{
|
|
'headername': Name of header file, ending in .h,
|
|
'headerdata': Contents of the header file,
|
|
'sourcename': Name of the source code file, ending in .c,
|
|
'sourcedata': Contents of the source code file
|
|
}
|
|
"""
|
|
output = []
|
|
|
|
for filename in request.file_to_generate:
|
|
for fdesc in request.proto_file:
|
|
if fdesc.name == filename:
|
|
results = nanopb.process_file(filename, fdesc, options, parsed_files)
|
|
output.append(results)
|
|
|
|
return output
|
|
|
|
|
|
def nanopb_write(results):
|
|
"""Translates nanopb output dictionaries to a CodeGeneratorResponse.
|
|
|
|
Args:
|
|
results: A list of generated source dictionaries, as returned by
|
|
nanopb_generate().
|
|
|
|
Returns:
|
|
A CodeGeneratorResponse describing the result of the code generation
|
|
process to protoc.
|
|
"""
|
|
response = plugin_pb2.CodeGeneratorResponse()
|
|
|
|
for result in results:
|
|
f = response.file.add()
|
|
f.name = result['headername']
|
|
f.content = result['headerdata']
|
|
|
|
f = response.file.add()
|
|
f.name = result['sourcename']
|
|
f.content = result['sourcedata']
|
|
|
|
return response
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|