Beispiel #1
0
 def unary_stub(self, method: ProtoServiceMethod,
                output: OutputFile) -> None:
     output.write_line(codegen.STUB_REQUEST_TODO)
     output.write_line('static_cast<void>(request);')
     output.write_line(codegen.STUB_RESPONSE_TODO)
     output.write_line('static_cast<void>(response);')
     output.write_line('return pw::Status::Unimplemented();')
Beispiel #2
0
def _generate_method_descriptor(method: ProtoServiceMethod, method_id: int,
                                output: OutputFile) -> None:
    """Generates a method descriptor for a raw RPC method."""

    impl_method = f'&Implementation::{method.name()}'

    output.write_line(
        f'{RPC_NAMESPACE}::internal::GetRawMethodFor<{impl_method}, '
        f'{method.type().cc_enum()}>(')
    output.write_line(f'    0x{method_id:08x}),  // Hash of "{method.name()}"')
Beispiel #3
0
def process_proto_file(proto_file) -> Iterable[OutputFile]:
    """Generates code for a single .proto file."""

    _, package_root = build_node_tree(proto_file)
    output_filename = _proto_filename_to_generated_header(proto_file.name)
    output_file = OutputFile(output_filename)
    _generate_code_for_package(proto_file, package_root, output_file)

    output_file.write_line()
    codegen.package_stubs(package_root, output_file, StubGenerator())

    return [output_file]
Beispiel #4
0
 def server_streaming_stub(  # pylint: disable=no-self-use
         self, unused_method: ProtoServiceMethod,
         output: OutputFile) -> None:
     """Returns the stub for this server streaming method."""
     output.write_line(STUB_REQUEST_TODO)
     output.write_line('static_cast<void>(request);')
     output.write_line(STUB_WRITER_TODO)
     output.write_line('static_cast<void>(writer);')
Beispiel #5
0
def _generate_method_descriptor(method: ProtoServiceMethod, method_id: int,
                                output: OutputFile) -> None:
    """Generates a nanopb method descriptor for an RPC method."""

    req_fields = f'{method.request_type().nanopb_name()}_fields'
    res_fields = f'{method.response_type().nanopb_name()}_fields'
    impl_method = f'&Implementation::{method.name()}'

    output.write_line(
        f'{RPC_NAMESPACE}::internal::GetNanopbOrRawMethodFor<{impl_method}, '
        f'{method.type().cc_enum()}>(')
    with output.indent(4):
        output.write_line(f'0x{method_id:08x},  // Hash of "{method.name()}"')
        output.write_line(f'{req_fields},')
        output.write_line(f'{res_fields}),')
Beispiel #6
0
def generate_code_for_enum(enum: ProtoEnum, root: ProtoNode,
                           output: OutputFile) -> None:
    """Creates a C++ enum for a proto enum."""
    assert enum.type() == ProtoNode.Type.ENUM

    output.write_line(f'enum class {enum.cpp_namespace(root)} {{')
    with output.indent():
        for name, number in enum.values():
            output.write_line(f'{name} = {number},')
    output.write_line('};')
Beispiel #7
0
def _generate_service_stubs(service: ProtoService, output: OutputFile,
                            stub_generator: StubGenerator) -> None:
    output.write_line(f'// Method definitions for {service.proto_path()}.')

    blank_line = False

    for method in service.methods():
        if blank_line:
            output.write_line()
        else:
            blank_line = True

        signature, stub = _select_stub_methods(stub_generator, method)

        output.write_line(signature(method, f'{service.name()}::') + ' {')
        with output.indent():
            stub(method, output)
        output.write_line('}')
Beispiel #8
0
def _method_lookup_table(service: ProtoService, output: OutputFile) -> None:
    """Generates array of method IDs for looking up methods at compile time."""
    output.write_line('static constexpr std::array<uint32_t, '
                      f'{len(service.methods())}> kMethodIds = {{')

    with output.indent(4):
        for method in service.methods():
            method_id = pw_rpc.ids.calculate(method.name())
            output.write_line(
                f'0x{method_id:08x},  // Hash of "{method.name()}"')

    output.write_line('};\n')
Beispiel #9
0
def process_proto_file(proto_file) -> Iterable[OutputFile]:
    """Generates code for a single .proto file."""

    # Two passes are made through the file. The first builds the tree of all
    # message/enum nodes, then the second creates the fields in each. This is
    # done as non-primitive fields need pointers to their types, which requires
    # the entire tree to have been parsed into memory.
    _, package_root = build_node_tree(proto_file)

    output_filename = _proto_filename_to_generated_header(proto_file.name)
    output_file = OutputFile(output_filename)
    generate_code_for_package(proto_file, package_root, output_file)

    return [output_file]
Beispiel #10
0
def define_not_in_class_methods(message: ProtoMessage, root: ProtoNode,
                                output: OutputFile) -> None:
    """Defines methods for a message class that were previously declared."""
    assert message.type() == ProtoNode.Type.MESSAGE

    for field in message.fields():
        for method_class in PROTO_FIELD_METHODS[field.type()]:
            method = method_class(field, message, root)
            if not method.should_appear() or method.in_class_definition():
                continue

            output.write_line()
            class_name = f'{message.cpp_namespace(root)}::Encoder'
            method_signature = (
                f'inline {method.return_type(from_root=True)} '
                f'{class_name}::{method.name()}({method.param_string()})')
            output.write_line(f'{method_signature} {{')
            with output.indent():
                for line in method.body():
                    output.write_line(line)
            output.write_line('}')
Beispiel #11
0
def generate_code_for_message(message: ProtoMessage, root: ProtoNode,
                              output: OutputFile) -> None:
    """Creates a C++ class for a protobuf message."""
    assert message.type() == ProtoNode.Type.MESSAGE

    # Message classes inherit from the base proto message class in codegen.h
    # and use its constructor.
    base_class = f'{PROTOBUF_NAMESPACE}::{BASE_PROTO_CLASS}'
    output.write_line(
        f'class {message.cpp_namespace(root)}::Encoder : public {base_class} {{'
    )
    output.write_line(' public:')

    with output.indent():
        output.write_line(f'using {BASE_PROTO_CLASS}::{BASE_PROTO_CLASS};')

        # Generate methods for each of the message's fields.
        for field in message.fields():
            for method_class in PROTO_FIELD_METHODS[field.type()]:
                method = method_class(field, message, root)
                if not method.should_appear():
                    continue

                output.write_line()
                method_signature = (
                    f'{method.return_type()} '
                    f'{method.name()}({method.param_string()})')

                if not method.in_class_definition():
                    # Method will be defined outside of the class at the end of
                    # the file.
                    output.write_line(f'{method_signature};')
                    continue

                output.write_line(f'{method_signature} {{')
                with output.indent():
                    for line in method.body():
                        output.write_line(line)
                output.write_line('}')

    output.write_line('};')
Beispiel #12
0
def _server_streaming_stub(method: ProtoServiceMethod,
                           output: OutputFile) -> None:
    output.write_line(f'void {method.name()}(ServerContext&, '
                      'pw::ConstByteSpan request, RawServerWriter& writer) {')

    with output.indent():
        output.write_line(codegen.STUB_REQUEST_TODO)
        output.write_line('static_cast<void>(request);')
        output.write_line(codegen.STUB_WRITER_TODO)
        output.write_line('static_cast<void>(writer);')

    output.write_line('}')
Beispiel #13
0
def _unary_stub(method: ProtoServiceMethod, output: OutputFile) -> None:
    output.write_line(f'pw::StatusWithSize {method.name()}(ServerContext&, '
                      'pw::ConstByteSpan request, pw::ByteSpan response) {')

    with output.indent():
        output.write_line(codegen.STUB_REQUEST_TODO)
        output.write_line('static_cast<void>(request);')
        output.write_line(codegen.STUB_RESPONSE_TODO)
        output.write_line('static_cast<void>(response);')
        output.write_line('return pw::StatusWithSize::Unimplemented();')

    output.write_line('}')
Beispiel #14
0
def _server_streaming_stub(method: ProtoServiceMethod,
                           output: OutputFile) -> None:
    output.write_line(
        f'void {method.name()}(ServerContext&, '
        f'const {method.request_type().nanopb_name()}& request, '
        f'ServerWriter<{method.response_type().nanopb_name()}>& writer) {{')

    with output.indent():
        output.write_line(codegen.STUB_REQUEST_TODO)
        output.write_line('static_cast<void>(request);')
        output.write_line(codegen.STUB_WRITER_TODO)
        output.write_line('static_cast<void>(writer);')

    output.write_line('}')
Beispiel #15
0
def package_stubs(proto_package: ProtoNode, output: OutputFile,
                  unary_stub: StubFunction,
                  server_streaming_stub: StubFunction) -> None:

    output.write_line('#ifdef _PW_RPC_COMPILE_GENERATED_SERVICE_STUBS')
    output.write_line(_STUBS_COMMENT)

    output.write_line(f'#include "{output.name()}"\n')

    if proto_package.cpp_namespace():
        file_namespace = proto_package.cpp_namespace()
        if file_namespace.startswith('::'):
            file_namespace = file_namespace[2:]

        output.write_line(f'namespace {file_namespace} {{')

    for node in proto_package:
        if node.type() == ProtoNode.Type.SERVICE:
            _generate_service_stub(cast(ProtoService, node), output,
                                   unary_stub, server_streaming_stub)

    if proto_package.cpp_namespace():
        output.write_line(f'}}  // namespace {file_namespace}')

    output.write_line('\n#endif  // _PW_RPC_COMPILE_GENERATED_SERVICE_STUBS')
Beispiel #16
0
def _generate_code_for_client_method(method: ProtoServiceMethod,
                                     output: OutputFile) -> None:
    """Outputs client code for a single RPC method."""

    req = method.request_type().nanopb_name()
    res = method.response_type().nanopb_name()
    method_id = pw_rpc.ids.calculate(method.name())

    if method.type() == ProtoServiceMethod.Type.UNARY:
        callback = f'{RPC_NAMESPACE}::UnaryResponseHandler<{res}>'
    elif method.type() == ProtoServiceMethod.Type.SERVER_STREAMING:
        callback = f'{RPC_NAMESPACE}::ServerStreamingResponseHandler<{res}>'
    else:
        raise NotImplementedError(
            'Only unary and server streaming RPCs are currently supported')

    output.write_line()
    output.write_line(f'static NanopbClientCall<\n    {callback}>')
    output.write_line(f'{method.name()}({RPC_NAMESPACE}::Channel& channel,')
    with output.indent(len(method.name()) + 1):
        output.write_line(f'const {req}& request,')
        output.write_line(f'{callback}& callback) {{')

    with output.indent():
        output.write_line(f'NanopbClientCall<{callback}>')
        output.write_line('    call(&channel,')
        with output.indent(9):
            output.write_line('kServiceId,')
            output.write_line(
                f'0x{method_id:08x},  // Hash of "{method.name()}"')
            output.write_line('callback,')
            output.write_line(f'{req}_fields,')
            output.write_line(f'{res}_fields);')
        output.write_line('call.SendRequest(&request);')
        output.write_line('return call;')

    output.write_line('}')
Beispiel #17
0
def _generate_server_writer_alias(output: OutputFile) -> None:
    output.write_line('template <typename T>')
    output.write_line('using ServerWriter = ::pw::rpc::ServerWriter<T>;')
Beispiel #18
0
def _generate_service_class(service: ProtoService, output: OutputFile,
                            stub_generator: StubGenerator) -> None:
    output.write_line(f'// Implementation class for {service.proto_path()}.')
    output.write_line(
        f'class {service.name()} '
        f': public generated::{service.name()}<{service.name()}> {{')

    output.write_line(' public:')

    with output.indent():
        blank_line = False

        for method in service.methods():
            if blank_line:
                output.write_line()
            else:
                blank_line = True

            signature, _ = _select_stub_methods(stub_generator, method)

            output.write_line(signature(method, '') + ';')

    output.write_line('};\n')
Beispiel #19
0
def _generate_code_for_client(service: ProtoService, root: ProtoNode,
                              output: OutputFile) -> None:
    """Outputs client code for an RPC service."""

    output.write_line('namespace nanopb {')

    class_name = f'{service.cpp_namespace(root)}Client'
    output.write_line(f'\nclass {class_name} {{')
    output.write_line(' public:')

    with output.indent():
        output.write_line('template <typename T>')
        output.write_line(
            f'using NanopbClientCall = {RPC_NAMESPACE}::NanopbClientCall<T>;')

        output.write_line('')
        output.write_line(f'{class_name}() = delete;')

        for method in service.methods():
            _generate_code_for_client_method(method, output)

    service_name_hash = pw_rpc.ids.calculate(service.proto_path())
    output.write_line('\n private:')

    with output.indent():
        output.write_line(f'// Hash of "{service.proto_path()}".')
        output.write_line(
            f'static constexpr uint32_t kServiceId = 0x{service_name_hash:08x};'
        )

    output.write_line('};')

    output.write_line('\n}  // namespace nanopb\n')
Beispiel #20
0
def forward_declare(node: ProtoMessage, root: ProtoNode,
                    output: OutputFile) -> None:
    """Generates code forward-declaring entities in a message's namespace."""
    namespace = node.cpp_namespace(root)
    output.write_line()
    output.write_line(f'namespace {namespace} {{')

    # Define an enum defining each of the message's fields and their numbers.
    output.write_line('enum class Fields {')
    with output.indent():
        for field in node.fields():
            output.write_line(f'{field.enum_name()} = {field.number()},')
    output.write_line('};')

    # Declare the message's encoder class and all of its enums.
    output.write_line()
    output.write_line('class Encoder;')
    for child in node.children():
        if child.type() == ProtoNode.Type.ENUM:
            output.write_line()
            generate_code_for_enum(cast(ProtoEnum, child), node, output)

    output.write_line(f'}}  // namespace {namespace}')
Beispiel #21
0
def generate_code_for_package(file_descriptor_proto, package: ProtoNode,
                              output: OutputFile) -> None:
    """Generates code for a single .pb.h file corresponding to a .proto file."""

    assert package.type() == ProtoNode.Type.PACKAGE

    output.write_line(f'// {os.path.basename(output.name())} automatically '
                      f'generated by {PLUGIN_NAME} {PLUGIN_VERSION}')
    output.write_line(f'// on {datetime.now()}')
    output.write_line('#pragma once\n')
    output.write_line('#include <cstddef>')
    output.write_line('#include <cstdint>')
    output.write_line('#include <span>\n')
    output.write_line('#include "pw_protobuf/codegen.h"')

    for imported_file in file_descriptor_proto.dependency:
        generated_header = _proto_filename_to_generated_header(imported_file)
        output.write_line(f'#include "{generated_header}"')

    if package.cpp_namespace():
        file_namespace = package.cpp_namespace()
        if file_namespace.startswith('::'):
            file_namespace = file_namespace[2:]

        output.write_line(f'\nnamespace {file_namespace} {{')

    for node in package:
        if node.type() == ProtoNode.Type.MESSAGE:
            forward_declare(cast(ProtoMessage, node), package, output)

    # Define all top-level enums.
    for node in package.children():
        if node.type() == ProtoNode.Type.ENUM:
            output.write_line()
            generate_code_for_enum(cast(ProtoEnum, node), package, output)

    # Run through all messages in the file, generating a class for each.
    for node in package:
        if node.type() == ProtoNode.Type.MESSAGE:
            output.write_line()
            generate_code_for_message(cast(ProtoMessage, node), package,
                                      output)

    # Run a second pass through the classes, this time defining all of the
    # methods which were previously only declared.
    for node in package:
        if node.type() == ProtoNode.Type.MESSAGE:
            define_not_in_class_methods(cast(ProtoMessage, node), package,
                                        output)

    if package.cpp_namespace():
        output.write_line(f'\n}}  // namespace {package.cpp_namespace()}')
Beispiel #22
0
def package(file_descriptor_proto, proto_package: ProtoNode,
            output: OutputFile, includes: IncludesGenerator,
            service: ServiceGenerator, client: ServiceGenerator) -> None:
    """Generates service and client code for a package."""
    assert proto_package.type() == ProtoNode.Type.PACKAGE

    output.write_line(f'// {os.path.basename(output.name())} automatically '
                      f'generated by {PLUGIN_NAME} {PLUGIN_VERSION}')
    output.write_line(f'// on {datetime.now().isoformat()}')
    output.write_line('// clang-format off')
    output.write_line('#pragma once\n')

    output.write_line('#include <array>')
    output.write_line('#include <cstdint>')
    output.write_line('#include <type_traits>\n')

    include_lines = [
        '#include "pw_rpc/internal/method_lookup.h"',
        '#include "pw_rpc/server_context.h"',
        '#include "pw_rpc/service.h"',
    ]
    include_lines += includes(file_descriptor_proto, proto_package)

    for include_line in sorted(include_lines):
        output.write_line(include_line)

    output.write_line()

    if proto_package.cpp_namespace():
        file_namespace = proto_package.cpp_namespace()
        if file_namespace.startswith('::'):
            file_namespace = file_namespace[2:]

        output.write_line(f'namespace {file_namespace} {{')

    for node in proto_package:
        if node.type() == ProtoNode.Type.SERVICE:
            service(cast(ProtoService, node), proto_package, output)
            client(cast(ProtoService, node), proto_package, output)

    if proto_package.cpp_namespace():
        output.write_line(f'}}  // namespace {file_namespace}')
Beispiel #23
0
def package_stubs(proto_package: ProtoNode, output: OutputFile,
                  stub_generator: StubGenerator) -> None:
    """Generates the RPC stubs for a package."""
    if proto_package.cpp_namespace():
        file_ns = proto_package.cpp_namespace()
        if file_ns.startswith('::'):
            file_ns = file_ns[2:]

        start_ns = lambda: output.write_line(f'namespace {file_ns} {{\n')
        finish_ns = lambda: output.write_line(f'}}  // namespace {file_ns}\n')
    else:
        start_ns = finish_ns = lambda: None

    services = [
        cast(ProtoService, node) for node in proto_package
        if node.type() == ProtoNode.Type.SERVICE
    ]

    output.write_line('#ifdef _PW_RPC_COMPILE_GENERATED_SERVICE_STUBS')
    output.write_line(_STUBS_COMMENT)

    output.write_line(f'#include "{output.name()}"\n')

    start_ns()

    for node in services:
        _generate_service_class(node, output, stub_generator)

    output.write_line()

    finish_ns()

    start_ns()

    for node in services:
        _generate_service_stubs(node, output, stub_generator)
        output.write_line()

    finish_ns()

    output.write_line('#endif  // _PW_RPC_COMPILE_GENERATED_SERVICE_STUBS')
Beispiel #24
0
def _unary_stub(method: ProtoServiceMethod, output: OutputFile) -> None:
    output.write_line(f'pw::Status {method.name()}(ServerContext&, '
                      f'const {method.request_type().nanopb_name()}& request, '
                      f'{method.response_type().nanopb_name()}& response) {{')

    with output.indent():
        output.write_line(codegen.STUB_REQUEST_TODO)
        output.write_line('static_cast<void>(request);')
        output.write_line(codegen.STUB_RESPONSE_TODO)
        output.write_line('static_cast<void>(response);')
        output.write_line('return pw::Status::Unimplemented();')

    output.write_line('}')
Beispiel #25
0
def _generate_service_stub(service: ProtoService, output: OutputFile,
                           unary_stub: StubFunction,
                           server_streaming_stub: StubFunction) -> None:
    output.write_line()
    output.write_line(
        f'class {service.name()} '
        f': public generated::{service.name()}<{service.name()}> {{')

    output.write_line(' public:')

    with output.indent():
        blank_line = False

        for method in service.methods():
            if blank_line:
                output.write_line()
            else:
                blank_line = True

            if method.type() is ProtoServiceMethod.Type.UNARY:
                unary_stub(method, output)
            elif method.type() is ProtoServiceMethod.Type.SERVER_STREAMING:
                server_streaming_stub(method, output)
            else:
                raise NotImplementedError(
                    'Client and bidirectional streaming not yet implemented')

    output.write_line('};\n')
Beispiel #26
0
def _generate_server_writer_alias(output: OutputFile) -> None:
    output.write_line(
        f'using RawServerWriter = {RPC_NAMESPACE}::RawServerWriter;')
Beispiel #27
0
def service_class(service: ProtoService, root: ProtoNode, output: OutputFile,
                  server_writer_alias: ServerWriterGenerator,
                  method_union: str,
                  method_descriptor: MethodGenerator) -> None:
    """Generates a C++ derived class for a nanopb RPC service."""

    output.write_line('namespace generated {')

    base_class = f'{RPC_NAMESPACE}::Service'
    output.write_line('\ntemplate <typename Implementation>')
    output.write_line(
        f'class {service.cpp_namespace(root)} : public {base_class} {{')
    output.write_line(' public:')

    with output.indent():
        output.write_line(
            f'using ServerContext = {RPC_NAMESPACE}::ServerContext;')
        server_writer_alias(output)
        output.write_line()

        output.write_line(f'constexpr {service.name()}()')
        output.write_line(f'    : {base_class}(kServiceId, kMethods) {{}}')

        output.write_line()
        output.write_line(
            f'{service.name()}(const {service.name()}&) = delete;')
        output.write_line(f'{service.name()}& operator='
                          f'(const {service.name()}&) = delete;')

        output.write_line()
        output.write_line(f'static constexpr const char* name() '
                          f'{{ return "{service.name()}"; }}')

        output.write_line()
        output.write_line(
            '// Used by MethodLookup to identify the generated service base.')
        output.write_line(
            'constexpr void _PwRpcInternalGeneratedBase() const {}')

    service_name_hash = pw_rpc.ids.calculate(service.proto_path())
    output.write_line('\n private:')

    with output.indent():
        output.write_line('friend class ::pw::rpc::internal::MethodLookup;\n')
        output.write_line(f'// Hash of "{service.proto_path()}".')
        output.write_line(
            f'static constexpr uint32_t kServiceId = 0x{service_name_hash:08x};'
        )

        output.write_line()

        # Generate the method table
        output.write_line('static constexpr std::array<'
                          f'{RPC_NAMESPACE}::internal::{method_union},'
                          f' {len(service.methods())}> kMethods = {{')

        with output.indent(4):
            for method in service.methods():
                method_descriptor(method, pw_rpc.ids.calculate(method.name()),
                                  output)

        output.write_line('};\n')

        # Generate the method lookup table
        _method_lookup_table(service, output)

    output.write_line('};')

    output.write_line('\n}  // namespace generated\n')
Beispiel #28
0
def _generate_code_for_client(unused_service: ProtoService,
                              unused_root: ProtoNode,
                              output: OutputFile) -> None:
    """Outputs client code for an RPC service."""
    output.write_line('// Raw RPC clients are not yet implemented.\n')