# Copyright 2021 The CPP Proto Builder 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
#
#      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.

# READ: https://google.github.io/cpp-proto-builder

# The following rules are defined:
# . cc_proto_builder_library:  Generate a cc_library rule using proto_builder.
# . proto_builder_test:        Compares source files with an expected set of
#                              files.
# . proto_builder:             Generate source files only.

"""Defines 'proto_builder' rules that create Builder pattern for proto types."""

load(":build_oss.bzl", "proto_builder_config")
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
load("@bazel_skylib//lib:dicts.bzl", "dicts")

_VIRTUAL_IMPORTS = "/_virtual_imports/"

ProtoBuilderInfo = provider(
    doc = "A provider containing the source files generated by the proto " +
          "builder. It is created and returned by the proto_builder rule.",
    fields = {
        "source_file": "File: The source file generated by the proto builder " +
                       "executable. It has a '.cc' suffix.",
        "header_file": "File: The header file generated by the proto builder " +
                       "executable. It has a '.h' suffix.",
        "interface_file": "File: The interface header file generated by the " +
                          "proto builder executable. It has a '.h' suffix.",
        "source_tpl_file": "File: The template source file used by the proto " +
                           "builder executable. It has a '.cc.tpl' or " +
                           "'.tpl.cc' suffix.",
        "header_tpl_file": "File: The template header file used by the proto " +
                           "builder executable. It has a '.h.tpl' or " +
                           "'.tpl.h' suffix.",
        "interface_tpl_file": "File: The template interface header file used " +
                              "by the proto builder executable. It has a " +
                              "'.h.tpl' or '.tpl.h' suffix.",
    },
)

ProtoLibraryInfo = provider(
    doc = "A provider containing the proto library dependency. It is created " +
          "and returned by the _get_proto_lib aspect.",
    fields = {
        "proto_library": "Target: The proto library target.",
    },
)

ProtoDepsInfo = provider(
    doc = "A provider that retrieves the transitive proto dependencies.",
    fields = {
        "proto_deps": "Depset of all proto deps",
        "cc_library_deps": "Depset of all related cc_library_proto rules",
    },
)

ProtoValidatorInfo = provider(
    doc = "A provider that retrieves information about the proto validator .",
    fields = {
        "cc_proto_validator_library": "Target: A validator cc_library if found.",
        "proto_header": "C++ proto header.",
        "validator_header": "C++ header for validator code.",
    },
)

def _clang_tidy_impl(ctx, src, output_filename):
    """Clang-tidy a file.

    Creates an action that performs clang-tidy on a file and writes the output
    to a separate file.

    If `src` ends in '.exp', then a temp file with '.exp' removed will be
    created, otherwise the temp file will have the same base filename.

    Args:
      ctx: The current rule's context object.
      src: The source file.
      output_filename: The name of the output file.

    Returns:
      The output file.
    """
    src_no_exp = src.path[:-4] if src.path.endswith(".exp") else src.path
    clang_tidy_src = ctx.actions.declare_file(src_no_exp)
    clang_tidy_file = ctx.actions.declare_file(output_filename)
    ctx.actions.run_shell(
        outputs = [clang_tidy_src, clang_tidy_file],
        inputs = [
            src,
            ctx.executable._stable_clang_format_tool,
        ],
        tools = [ctx.executable._stable_clang_format_tool],
        command = "cat {} > {} && {} --sort-includes={} --style=Google {} > {}".format(
            src.path,
            clang_tidy_src.path,
            ctx.executable._stable_clang_format_tool.path,
            "1" if ctx.attr.sort_includes else "0",
            clang_tidy_src.path,
            clang_tidy_file.path,
        ),
        mnemonic = "ClangTidy",
        progress_message = "Clang Tidy on file: %s." % (src.path),
    )
    return clang_tidy_file

_clang_tidy_common_attrs = {
    "_stable_clang_format_tool": attr.label(
        doc = "The target of the clang-format executable.",
        default = Label("@llvm_toolchain//:bin/clang-format"),
        allow_single_file = True,
        executable = True,
        cfg = "exec",
    ),
    "sort_includes": attr.bool(
        doc = "Passes --sort-includes=<value> to clang-tidy tool.",
        default = True,
    ),
}

def _get_proto_lib_impl(target, ctx):
    """The implementation of the '_get_proto_lib' aspect.

    Retrieves the proto_library target of a cc_proto_library target. The
    cc_proto_library target must contain a single deps entry.

    Args:
      ctx: The current rule's context object.
      target: The current rule's target object.

    Returns:
      ProtoLibraryInfo provider.
    """
    proto_library = None
    rule_name = ctx.rule.attr.name
    if ctx.rule.kind == "cc_proto_library":
        if not hasattr(ctx.rule.attr, "deps"):
            fail("The '%s' target must have a deps attribute!", rule_name)
        if hasattr(ctx.rule.attr, "deps"):
            deps = ctx.rule.attr.deps
            if (len(deps) != 1):
                fail("The deps of '%s' must have a single entry!", rule_name)
            proto_library = deps[0]
    return [ProtoLibraryInfo(proto_library = proto_library)]

_get_proto_lib = aspect(
    attr_aspects = ["deps"],
    doc = """An aspect that returns a ProtoLibraryInfo provider.

    The aspect is used by the 'proto_builder' rule.
    """,
    implementation = _get_proto_lib_impl,
)

def _label_to_dependency(label):
    """Return the Label `label` as a single string."""
    return "//" + label.package + ":" + label.name

def _get_proto_deps_aspect_impl(target, ctx):
    """Compute proto dependencies as well as related cc_proto_library deps."""

    # Get transitive proto dependencies.
    local_proto_targets = []
    transitive_proto_targets = None
    if ctx.rule.kind == "proto_library":
        local_proto_targets.append(target.label)
    if hasattr(ctx.rule.attr, "deps"):
        transitive_proto_targets = [dep[ProtoDepsInfo].proto_deps for dep in ctx.rule.attr.deps]
    proto_deps = depset(local_proto_targets, transitive = transitive_proto_targets)

    # Get all direct cc_proto dependencies as well as all proto_library
    # dependencies translated into cc_proto_library dependencies.
    cc_library_deps = list()
    for dep in proto_deps.to_list():
        if dep.name.endswith("_cc_proto"):
            cc_library_deps.append(dep)
        elif dep.name.endswith("_proto"):
            cc_library_deps.append(Label(
                "//" + dep.package + ":" + dep.name[:-6] + "_cc_proto",
            ))

    return [ProtoDepsInfo(
        cc_library_deps = depset(cc_library_deps),
        proto_deps = proto_deps,
    )]

_get_proto_dep_aspect = aspect(
    attr_aspects = ["deps"],
    doc = "Compute proto dependencies as well as related cc_proto_library deps.",
    implementation = _get_proto_deps_aspect_impl,
)

def _get_proto_validator_aspect_impl(target, ctx):
    """Compute proto validator data."""

    # We require the following:
    # . the rule needs to be a cc_library rule (or entail one).
    # . the rule name must end in '_cc_proto_validator'.
    # . the rule needs to have a matching *.pb.h header.
    # . the rule needs to have a matching *.proto.validator.h header.
    if (ctx.rule.kind != "cc_library" or
        not target.label.name.endswith("_cc_proto_validator")):
        return []

    # Construct relative short_paths for proto header and validator header from
    # the `target`.
    path = target.label.package
    prefix = target.label.name[0:-len("_cc_proto_validator")]
    proto_header = None
    proto_header_short_path = (path + "/" + prefix +
                               proto_builder_config.CC_PROTO_EXTENSION)
    validator_header = None
    validator_header_short_path = path + "/" + prefix + ".proto.validator.h"

    # Search all headers for the necessary `proto_header_short_path` and
    # `validator_header_short_path`.
    for t in target[CcInfo].compilation_context.headers.to_list():
        if t.short_path == proto_header_short_path:
            proto_header = t
        if t.short_path == validator_header_short_path:
            validator_header = t
        if proto_header and validator_header:
            break

    if not proto_header or not validator_header:
        fail('Proto Builder has cc_proto_validator dependency "{dep}" but ' +
             'the related headers "{proto_header}" and "{validator_header}" ' +
             "could not be found.".format(
                 dep = target,
                 proto_header = proto_header_short_path,
                 validator_header = validator_header_short_path,
             ))

    return [ProtoValidatorInfo(
        cc_proto_validator_library = target,
        proto_header = proto_header,
        validator_header = validator_header,
    )]

_get_proto_validator_aspect = aspect(
    attr_aspects = ["deps"],
    doc = "Compute proto validator data.",
    implementation = _get_proto_validator_aspect_impl,
)

def is_non_source_in_virtual_imports_folder(
        file,
        virtual_folder = _VIRTUAL_IMPORTS):
    """Determines if the 'file' is not a source file and located under the virtual_folder

    Args:
      file: A file.
      virtual_folder: The virtual folder name. The default value is
        "_virtual_imports".

    Returns:
      True if the file is not a source file and located under the
      virtual_folder. Otherwise, False.
    """
    return not file.is_source and virtual_folder in file.path

def _get_include_directory_for_proto(proto_file):
    """Returns the include directory path for the proto_file.

    The returned directory path can be used as the "--proto_path=" argument
    value.

    Args:
      proto_file: A proto file.

    Returns:
      The include directory path for the proto_file.
    """
    directory = proto_file.path
    prefix_len = 0

    if is_non_source_in_virtual_imports_folder(proto_file):
        root, relative = proto_file.path.split(_VIRTUAL_IMPORTS, 2)
        return root + _VIRTUAL_IMPORTS + relative.split("/", 1)[0]

    if not proto_file.is_source and directory.startswith(proto_file.root.path):
        prefix_len = len(proto_file.root.path) + 1

    if directory.startswith("external", prefix_len):
        external_separator = directory.find("/", prefix_len)
        repository_separator = directory.find("/", external_separator + 1)
        return directory[:repository_separator]
    else:
        return proto_file.root.path if proto_file.root.path else "."

def _compile_and_link(ctx, srcs, hdrs, textual_hdrs, deps):
    """Compile and link.

    Args:
      ctx: Rule context
      srcs: Source files .cc
      hdrs: Header files .h
      textual_hdrs: Other textual headers
      deps: Dependencies, must be cc_library rules

    Returns:
      CcInfo; It does not return DefaultInfo
    """
    cc_toolchain = find_cc_toolchain(ctx)
    feature_configuration = cc_common.configure_features(
        ctx = ctx,
        cc_toolchain = cc_toolchain,
        requested_features = ctx.features,
        unsupported_features = ctx.disabled_features,
    )
    cc_infos = [lib[CcInfo] for lib in deps if CcInfo in lib]

    compilation_contexts = [cc_info.compilation_context for cc_info in cc_infos]
    linking_contexts = [cc_info.linking_context for cc_info in cc_infos]

    (compilation_context, compilation_outputs) = cc_common.compile(
        name = ctx.label.name,
        actions = ctx.actions,
        feature_configuration = feature_configuration,
        cc_toolchain = cc_toolchain,
        srcs = srcs,
        public_hdrs = hdrs,
        textual_hdrs = textual_hdrs,
        compilation_contexts = compilation_contexts,
        grep_includes = ctx.file._grep_includes,
    )
    (linking_context, linking_outputs) = cc_common.create_linking_context_from_compilation_outputs(
        name = ctx.label.name,
        actions = ctx.actions,
        feature_configuration = feature_configuration,
        cc_toolchain = cc_toolchain,
        compilation_outputs = compilation_outputs,
        linking_contexts = linking_contexts,
    )
    return CcInfo(
        compilation_context = compilation_context,
        linking_context = linking_context,
    )

def _proto_builder_impl(ctx):
    """The implementation of the 'proto_builder' rule.

    Execute the CPP proto builder tool on proto type(s).

    Args:
      ctx: The current rule's context object.

    Returns:
      ProtoBuilderInfo provider
      DefaultInfo provider
    """
    cc_proto_library_deps = ctx.attr.cc_proto_library_deps
    if len(cc_proto_library_deps) != 1:
        fail("The cc_proto_library_deps must contain a single entry.")
    if len(ctx.files.srcs) > 1:
        fail("Attribute srcs must contain at most one source file")
    if len(ctx.files.hdrs) > 1:
        fail("Attribute hdrs must contain at most one header file")
    if len(ctx.files.ifcs) > 1:
        fail("Attribute ifcs must contain at most one interface header file")

    output_files = []

    # Write cc_proto_library dependencies to a file.
    cc_deps_labels = []
    cc_library_deps_file_name = ctx.attr.cc_library_deps_file
    if not cc_library_deps_file_name:
        cc_library_deps_file_name = ctx.attr.name + ".cc_deps.txt"
    cc_deps_file = ctx.actions.declare_file(cc_library_deps_file_name)
    cc_deps_labels = cc_proto_library_deps[0][ProtoDepsInfo].cc_library_deps.to_list()
    cc_deps = [_label_to_dependency(dep) for dep in cc_deps_labels]
    ctx.actions.write(cc_deps_file, "\n".join(sorted(cc_deps)) + "\n")
    output_files.append(cc_deps_file)

    # Get proto file(s).
    proto_library_dep = cc_proto_library_deps[0][ProtoLibraryInfo].proto_library
    proto_files_dep = proto_library_dep[ProtoInfo].transitive_sources
    proto_files = proto_files_dep.to_list()

    # Including the direct proto files in the proto_library rule.
    direct_proto_files = proto_library_dep[ProtoInfo].direct_sources
    direct_proto_paths = ",".join([f.path for f in direct_proto_files])
    direct_proto_short_paths = [
        proto_file.short_path[0:-len(".proto")] +
        proto_builder_config.CC_PROTO_EXTENSION
        for proto_file in direct_proto_files
    ]
    proto_files += direct_proto_files

    # Is there a validator dependency for the proto we are interested?
    use_validator = False
    validator_header = ""
    for dep in ctx.attr.deps:
        if ProtoValidatorInfo in dep:
            if dep[ProtoValidatorInfo].cc_proto_validator_library:
                proto_header = dep[ProtoValidatorInfo].proto_header.short_path
                if proto_header in direct_proto_short_paths:
                    use_validator = True
                    validator_header = dep[ProtoValidatorInfo].validator_header.short_path
                    break

    # We can actually specify all proto files correctly using:
    # protofiles_flag = ",".join([proto.path for proto in proto_files])
    # But it is better for the tool to perform file resolution using proto
    # meta data and proto_paths information.
    protofiles_flag = ""
    proto_paths = ",".join(
        [_get_include_directory_for_proto(proto) for proto in proto_files] + [ctx.configuration.genfiles_dir.path],
    )

    protos = ",".join(ctx.attr.proto)
    template_src = None
    if len(ctx.files.srcs) == 1:
        template_src = ctx.files.srcs[0]
    template_hdr = None
    if len(ctx.files.hdrs) == 1:
        template_hdr = ctx.files.hdrs[0]
    template_ifc = None
    if len(ctx.files.ifcs) == 1:
        template_ifc = ctx.files.ifcs[0]
    template_files = []
    if template_src:
        template_files.append(template_src)
    if template_hdr:
        template_files.append(template_hdr)
    if template_ifc:
        template_files.append(template_ifc)
    src_out_name = ctx.attr.source_out or ctx.attr.name + ".cc"
    hdr_out_name = ctx.attr.header_out or ctx.attr.name + ".h"
    ifc_out_name = ctx.attr.interface_out or ctx.attr.name + ".interface.h"
    header_file = ctx.actions.declare_file(hdr_out_name)
    source_file = ctx.actions.declare_file(src_out_name)
    make_interface = ctx.attr.make_interface
    builder_files = [source_file, header_file]
    header_files = [header_file] + [f for f in ctx.files.extra_hdrs]
    source_files = [source_file] + [f for f in ctx.files.extra_srcs]
    if make_interface:
        interface_file = ctx.actions.declare_file(ifc_out_name)
        interface_filename = interface_file.path
        builder_files.append(interface_file)
        header_files.append(interface_file)
    else:
        interface_file = None
        interface_filename = ""
    output_files += builder_files

    proto_builder_config_files = []
    proto_builder_config_file = ""
    if ctx.attr.proto_builder_config:
        proto_builder_config_files = ctx.attr.proto_builder_config[DefaultInfo].files.to_list()
        proto_builder_config_file = proto_builder_config_files[0].path

    # Write containing all known conversion libraries.
    conv_deps_file = ctx.actions.declare_file(
        ctx.attr.name + ".conversion.deps",
    )
    ctx.actions.write(conv_deps_file, "\n".join(
        [str(f) for f in proto_builder_config.CONVERSION_LIBRARIES],
    ) + "\n")

    # Templates files
    template_hdr_file = ctx.file._default_header_tpl if not (template_hdr) else template_hdr
    template_src_file = ctx.file._default_source_tpl if not (template_src) else template_src
    template_ifc_file = ctx.file._default_interface_header_tpl if not (template_ifc) else template_ifc

    # Run proto_builder tool.
    ctx.actions.run_shell(
        outputs = builder_files,
        tools = [
            ctx.executable._proto_builder_tool,
        ],
        inputs = proto_files + template_files + [
            ctx.executable._proto_builder_tool,
            ctx.executable._stable_clang_format_tool,
            conv_deps_file,
        ] + proto_builder_config_files,
        command = "\n".join([
            # Instantiate proto builder tool (Compute stage)
            """{} \
            --proto="{protos}:{direct_proto_paths}" \
            --header="{header_file}" \
            --source="{source_file}" \
            --interface="{interface_file}" \
            --header_in="{header_tpl}" \
            --source_in="{source_tpl}" \
            --interface_in="{interface_tpl}" \
            --tpl_value_header="{tpl_value_header}" \
            --protofiles="{protofiles}" \
            --proto_paths="{proto_paths}" \
            --make_interface="{make_interface}" \
            --max_field_depth="{max_field_depth}" \
            --use_validator="{use_validator}" \
            --validator_header="{validator_header}" \
            --use_global_db=0 \
            --proto_builder_config="{proto_builder_config}" \
            --conv_deps_file="{conv_deps_file}" \
            --template_builder_strip_prefix_dir="{strip_prefix_dir}";""".format(
                ctx.executable._proto_builder_tool.path,
                protos = protos,
                direct_proto_paths = direct_proto_paths,
                header_file = header_file.path,
                source_file = source_file.path,
                interface_file = interface_filename,
                header_tpl = template_hdr and template_hdr.path or "default",
                source_tpl = template_src and template_src.path or "default",
                interface_tpl = template_ifc and template_ifc.path or "default",
                tpl_value_header = ctx.attr.tpl_value_header or "",
                protofiles = protofiles_flag,
                proto_paths = proto_paths,
                make_interface = ctx.attr.make_interface,
                max_field_depth = ctx.attr.max_field_depth,
                use_validator = use_validator,
                validator_header = validator_header,
                proto_builder_config = proto_builder_config_file,
                conv_deps_file = conv_deps_file.path,
                strip_prefix_dir = ctx.genfiles_dir.path,
            ),
            "if [ $? -ne 0 ]; then",
            "exit -1",
            "fi",
            # Post processing stage
            proto_builder_config.proto_builder_processing(
                template_hdr_file,
                ctx.file._default_header_tpl,
                header_file,
            ),
            proto_builder_config.proto_builder_processing(
                template_src_file,
                ctx.file._default_source_tpl,
                source_file,
            ),
            proto_builder_config.proto_builder_processing(
                template_ifc_file,
                ctx.file._default_interface_header_tpl,
                interface_file,
            ) if ctx.attr.make_interface else "",
            # Clang format stage
            "{} -i --style=Google {}".format(
                ctx.executable._stable_clang_format_tool.path,
                source_file.path,
            ),
            "{} -i --style=Google {}".format(
                ctx.executable._stable_clang_format_tool.path,
                header_file.path,
            ),
            "{} -i --style=Google {}".format(
                ctx.executable._stable_clang_format_tool.path,
                interface_file.path,
            ) if ctx.attr.make_interface else "",
            "exit 0",
        ]),
        mnemonic = "CPPProtoBuilder",
        progress_message = "CPP Proto Builder on files: %s" % (direct_proto_paths),
    )

    # If configured, compile and link generated sources.
    cc_infos = []
    if ctx.attr.cc_compile_and_link:
        # Grep the relevant proto headers.
        hdrs = cc_proto_library_deps[0][CcInfo].compilation_context.headers
        textual_hdrs = [h for h in hdrs.to_list() if h.path.endswith(
            proto_builder_config.CC_PROTO_EXTENSION,
        )]

        # See if the conversion library is present.
        other_cc_deps = ctx.attr.deps + ctx.attr._default_deps
        for cc_dep in other_cc_deps:
            if cc_dep.label in proto_builder_config.CONVERSION_LIBRARIES:
                # If so, save the conversion headers.
                for h in cc_dep[CcInfo].compilation_context.headers.to_list():
                    if h.path in proto_builder_config.CONVERSION_HEADERS:
                        textual_hdrs.append(h)

        # Compile and link.
        cc_infos.append(_compile_and_link(
            srcs = source_files,
            hdrs = header_files,
            ctx = ctx,
            textual_hdrs = textual_hdrs,
            deps = cc_proto_library_deps + other_cc_deps,
        ))

    return [
        ProtoBuilderInfo(
            header_file = header_file,
            interface_file = interface_file,
            source_file = source_file,
            source_tpl_file = template_src_file,
            header_tpl_file = template_hdr_file,
            interface_tpl_file = template_ifc_file,
        ),
        DefaultInfo(files = depset(output_files)),
    ] + cc_infos

_default_tpl_attr = {
    "_default_source_tpl": attr.label(
        doc = "The default source template.",
        default = Label("//proto_builder:default.cc.tpl"),
        allow_single_file = [
            ".cc.tpl",
            ".tpl.cc",
        ],
    ),
    "_default_header_tpl": attr.label(
        doc = "The default header template.",
        default = Label("//proto_builder:default.h.tpl"),
        allow_single_file = [
            ".h.tpl",
            ".tpl.h",
        ],
    ),
    "_default_interface_header_tpl": attr.label(
        doc = "The default interface header template.",
        default = Label("//proto_builder:default_interface.h.tpl"),
        allow_single_file = [
            ".h.tpl",
            ".tpl.h",
        ],
    ),
}

_proto_builder_attr = {
    "cc_proto_library_deps": attr.label_list(
        doc = "The dependency on a cc_proto_library target that references " +
              "the proto_library target containing the proto type to " +
              "generate the builder. It must contain a single entry.",
        mandatory = True,
        aspects = [
            _get_proto_lib,
            _get_proto_dep_aspect,
        ],
    ),
    "proto": attr.string_list(
        doc = "The proto type to generate the builder.  By default, builders " +
              "for all top-level messages in the proto lirbary dependency " +
              "are generated.",
        default = ["*+"],
    ),
    "srcs": attr.label_list(
        doc = "The template for generating the source. If not specified, the " +
              "default template is used. The files must have a .cc.tpl or " +
              ".tpl.cc extension",
        allow_files = [
            ".cc.tpl",
            ".tpl.cc",
        ],
        default = [],
    ),
    "hdrs": attr.label_list(
        doc = "The template for generating the header. If not specified, the " +
              "default template is used. The files must have a .h.tpl or " +
              ".tpl.h extension",
        allow_files = [
            ".h.tpl",
            ".tpl.h",
        ],
        default = [],
    ),
    "ifcs": attr.label_list(
        doc = "The template for generating the interface. If not specified, the " +
              "default template is used. The files must have a .h.tpl or " +
              ".tpl.h extension",
        allow_files = [
            ".h.tpl",
            ".tpl.h",
        ],
        default = [],
    ),
    "extra_srcs": attr.label_list(
        doc = "Additional sources.",
        allow_files = [
            ".cc",
            ".h",
        ],
        default = [],
    ),
    "extra_hdrs": attr.label_list(
        doc = "Additional headers.",
        allow_files = [
            ".h",
        ],
        default = [],
    ),
    "source_out": attr.string(
        doc = "The name of the generated source file. If not specified, " +
              "the name is <name>.cc, where <name> is the target name.",
        default = "",
    ),
    "header_out": attr.string(
        doc = "The name of the generated header file. If not specified, " +
              "the name is <name>.h, where <name> is the target name.",
        default = "",
    ),
    "interface_out": attr.string(
        doc = "The name of the generated interface file. If not specified, " +
              "the name is <name>.interface.h, where <name> is the target name.",
        default = "",
    ),
    "tpl_value_header": attr.string(
        doc = "Used to override header related template values. This can be " +
              "used to generate files with a different header reference than " +
              "automatically derived from --header. It is helpful to solve " +
              "cyclic dependencies in proto_builder_test rules where it is " +
              "not possible to generate the header with the intended file " +
              "name.",
        default = "",
    ),
    "proto_builder_config": attr.label(
        doc = "Custom ProtoBuilderConfig textproto file.",
        default = None,
        allow_files = [".textproto"],
    ),
    "max_field_depth": attr.int(
        doc = "Do NOT use outside this tool's tests.",
        default = 0,
    ),
    "make_interface": attr.bool(
        doc = "Whether to generate an interface.",
        default = False,
    ),
    "source_file": attr.output(
        doc = "The generated source file.",
    ),
    "header_file": attr.output(
        doc = "The generated header file.",
    ),
    "interface_file": attr.output(
        doc = "The generated interface file - if any.",
    ),
    "outs": attr.output_list(
        doc = "The list of generated files.",
    ),
    "_proto_builder_tool": attr.label(
        doc = "The target of the proto builder executable.",
        default = Label("//proto_builder:proto_builder"),
        allow_single_file = True,
        executable = True,
        cfg = "exec",
    ),
    # Attributes for the compile and link stage.
    "_cc_toolchain": attr.label(default = proto_builder_config.CC_TOOLCHAIN),
    "cc_compile_and_link": attr.bool(
        doc = "Whether to compile and link the builder library.",
        default = False,
    ),
    "cc_library_deps_file": attr.string(
        doc = "The file to save the required cc_library dependencies.",
        default = "",
    ),
    "deps": attr.label_list(
        allow_rules = ["cc_library"],
        providers = [CcInfo],
        aspects = [_get_proto_validator_aspect],
    ),
    "_default_deps": attr.label_list(
        allow_rules = ["cc_library"],
        providers = [CcInfo],
        default = proto_builder_config.DEFAULT_DEPS,
    ),
    "_grep_includes": attr.label(
        allow_single_file = True,
        executable = True,
        cfg = "host",
        default = Label(proto_builder_config.GREP_INCLUDES),
    ),
}

proto_builder = rule(
    attrs = dicts.add(
        _proto_builder_attr,
        _default_tpl_attr,
        _clang_tidy_common_attrs,
    ),
    doc = """A build rule that generates the cpp builders.

        Note: The rule is useful when the generated source files must be part
        of a larger library.

        READ: https://google.github.io/cpp-proto-builder/usage

        Example:

        1) Required fields.

        ```
            proto_library(
                name = "sample_proto",
                srcs = ["sample.proto"],
            )

            cc_proto_library(
                name = "sample_cc_proto",
                deps = ["sample_proto"],
            )

            proto_builder(
                name = "sample_proto_builder",
                cc_proto_library_deps = ["sample_cc_proto"],
            )

        ```

        2) Selecting a proto type.

        ```
            proto_library(
                name = "sample_proto",
                srcs = ["sample.proto"],
            )

            cc_proto_library(
                name = "sample_cc_proto",
                deps = ["sample_proto"],
            )

            proto_builder(
                name = "sample_proto_builder",
                cc_proto_library_deps = ["sample_cc_proto"],
                proto = ["SampleMessage"],
            )
        ```
    """,
    fragments = proto_builder_config.FRAGMENTS,
    output_to_genfiles = True,
    implementation = _proto_builder_impl,
)

def cc_proto_builder_library(
        name,
        cc_proto_library_deps = [],
        proto = ["*+"],
        srcs = None,
        hdrs = None,
        ifcs = None,
        make_interface = False,
        tpl_value_header = None,
        deps = [],
        testonly = None,
        visibility = None,
        **kwargs):
    """Creates a cc_library target named 'name' of the compiled Builder pattern.

    The rule generates the source files and provide them as inputs to a stand
    alone cc_library rule. As a result, the build system will re-generate the
    sources whenever the inputs to this macro changes.

    READ: https://google.github.io/cpp-proto-builder/usage

    Args:
      name: name of the cc_library rule to generate.
      cc_proto_library_deps: The label of a cc_proto_library target. It must
                             reference the proto_library that defines the proto
                             types defined in 'proto'.
      proto: See proto attribute of 'proto_builder' rule.
      srcs: Optional, see 'proto_builder' rule.
      hdrs: Optional, see 'proto_builder' rule.
      ifcs: Optional, see 'proto_builder' rule.
      make_interface: Optional, see 'proto_builder' rule.
      tpl_value_header: See 'proto_builder' rule.
      deps: Dependencies for the cc_library rule. The list must include all
            dependencies for the code in srcs/hdrs.
      testonly: Whether these rules are only for tests.
      visibility: Visibility passed to the generated cc_library rule.
      **kwargs: Other args passed down to the proto_builder and cc_library rule.
    """
    if cc_proto_library_deps == None:
        fail("The cc_proto_library_deps argument must be defined.")
    if type(cc_proto_library_deps) != "list":
        fail("The cc_proto_library_deps argument must be a list.")
    if len(cc_proto_library_deps) != 1:
        fail("The cc_proto_library_deps must contain a single entry.")
    if not proto and type(proto) != "list":
        fail("The proto argument must be a list.")
    if srcs and type(srcs) != "list":
        fail("The srcs argument must be a list.")
    if hdrs and type(hdrs) != "list":
        fail("The hdrs argument must be a list.")
    if ifcs and type(ifcs) != "list":
        fail("The ifcs argument must be a list.")
    if tpl_value_header != None and type(tpl_value_header) != "string":
        fail("The tpl_value_header argument must be a string.")
    if not deps and type(deps) != "list":
        fail("The deps argument must be a list.")
    filename = name
    header_filename = filename + ".h"
    source_filename = filename + ".cc"
    interface_filename = filename + ".interface.h"

    if proto_builder_config.USE_CC_LIBRARY_MACRO:
        proto_builder_name = "__" + name + "_proto_builder"
    else:
        proto_builder_name = name

    proto_builder(
        name = proto_builder_name,
        cc_proto_library_deps = cc_proto_library_deps,
        proto = proto,
        srcs = srcs,
        hdrs = hdrs,
        source_out = source_filename,
        header_out = header_filename,
        interface_out = interface_filename,
        tpl_value_header = tpl_value_header,
        source_file = filename + ".cc",
        header_file = filename + ".h",
        interface_file = interface_filename if make_interface else None,
        make_interface = make_interface,
        testonly = testonly,
        visibility = visibility,
        cc_compile_and_link = not proto_builder_config.USE_CC_LIBRARY_MACRO,
        deps = deps,
        **kwargs
    )
    if proto_builder_config.USE_CC_LIBRARY_MACRO:
        # pop is used to remove unsupported parameters for native.cc_library
        native.cc_library(
            name = name,
            srcs = [":" + source_filename] + kwargs.pop("extra_srcs", []),
            hdrs = [":" + header_filename] + kwargs.pop("extra_hdrs", []) +
                   ([":" + interface_filename] if make_interface else []),
            deps = cc_proto_library_deps + deps + proto_builder_config.DEFAULT_DEPS,
            testonly = testonly,
            visibility = visibility,
            **kwargs
        )

def _proto_builder_test_impl(ctx):
    """The implementation of the 'proto_builder_test' rule.

    Tests the files generated by the 'proto_builder' rule. The test performs a
    textual comparison of the generated source and header with the expected
    source and header respectively.

    Args:
      ctx: The current rule's context object.

    Returns:
      DefaultInfo provider
    """
    header_golden_file = ctx.file.expected_hdr
    source_golden_file = ctx.file.expected_src
    interface_golden_file = ctx.file.expected_ifc
    header_result_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].header_file
    source_result_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].source_file
    interface_result_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].interface_file
    header_tpl_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].header_tpl_file
    source_tpl_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].source_tpl_file
    interface_tpl_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].interface_tpl_file
    make_interface = interface_result_file != None
    interface_processed_file = None
    header_golden_file = _clang_tidy_impl(
        ctx,
        header_golden_file,
        header_golden_file.basename + ".golden.h",
    )
    header_result_file = _clang_tidy_impl(
        ctx,
        header_result_file,
        header_result_file.basename + ".result.h",
    )
    header_processed_file = proto_builder_config.proto_builder_test_processing(
        ctx,
        header_tpl_file,
        ctx.file._default_header_tpl,
        header_golden_file,
        header_golden_file.basename + ".processed.h",
    )
    source_golden_file = _clang_tidy_impl(
        ctx,
        source_golden_file,
        source_golden_file.basename + ".golden.cc",
    )
    source_result_file = _clang_tidy_impl(
        ctx,
        source_result_file,
        source_result_file.basename + ".result.cc",
    )
    source_processed_file = proto_builder_config.proto_builder_test_processing(
        ctx,
        source_tpl_file,
        ctx.file._default_source_tpl,
        source_golden_file,
        source_golden_file.basename + ".processed.cc",
    )
    if make_interface:
        interface_golden_file = _clang_tidy_impl(
            ctx,
            interface_golden_file,
            interface_golden_file.basename + ".golden.h",
        )
        interface_result_file = _clang_tidy_impl(
            ctx,
            interface_result_file,
            interface_result_file.basename + ".result.h",
        )
        interface_processed_file = proto_builder_config.proto_builder_test_processing(
            ctx,
            interface_tpl_file,
            ctx.file._default_interface_header_tpl,
            interface_golden_file,
            interface_golden_file.basename + ".processed.h",
        )

    executable_file = ctx.actions.declare_file(ctx.label.name + ".sh")
    ctx.actions.write(
        output = executable_file,
        content = "\n".join([
            "#!/bin/bash",
            "set -e",
            "diff -du {} {}".format(
                header_processed_file.short_path,
                header_result_file.short_path,
            ),
            "diff -du {} {}".format(
                source_processed_file.short_path,
                source_result_file.short_path,
            ),
            "diff -du {} {}".format(
                interface_processed_file.short_path,
                interface_result_file.short_path,
            ) if make_interface else "",
            "exit 0",
        ]),
        is_executable = True,
    )
    runfiles = [
        header_processed_file,
        source_processed_file,
        header_result_file,
        source_result_file,
        executable_file,
    ] + ([interface_processed_file, interface_result_file] if make_interface else [])
    return [DefaultInfo(
        executable = executable_file,
        files = depset([executable_file]),
        runfiles = ctx.runfiles(files = runfiles),
    )]

_proto_builder_test_attrs = {
    "proto_builder_dep": attr.label(
        doc = "The proto_builder target to test.",
        mandatory = True,
        providers = [ProtoBuilderInfo],
    ),
    "expected_src": attr.label(
        doc = "The expected result for the source file (golden result).",
        allow_single_file = [
            ".cc",
            ".cc.exp",
        ],
        mandatory = True,
    ),
    "expected_hdr": attr.label(
        doc = "The expected result for the header file (golden result).",
        allow_single_file = [
            ".h",
            ".h.exp",
        ],
        mandatory = True,
    ),
    "expected_ifc": attr.label(
        doc = "The expected result for the interface header file (golden result).",
        allow_single_file = [
            ".h",
            ".h.exp",
        ],
        mandatory = False,
    ),
}

proto_builder_test = rule(
    attrs = dicts.add(
        _proto_builder_test_attrs,
        _default_tpl_attr,
        _clang_tidy_common_attrs,
    ),
    doc = """A test rule that compares 'proto_builder' against golden files.

        Use this rule **only** if the direct use of the `proto_builder` rule
        would cause a cyclic dependency.

        READ: https://google.github.io/cpp-proto-builder/usage

        Example:

        ```
            proto_library(
                name = "sample_proto",
                srcs = ["sample.proto"],
            )

            cc_proto_library(
                name = "sample_cc_proto",
                deps = ["sample_proto"],
            )

            proto_builder(
                name = "sample_proto_builder",
                testonly = 1,
                cc_proto_library_deps = ["sample_cc_proto"],
            )

            proto_builder_test(
                name = "sample_proto_builder_test",
                proto_builder_dep = "sample_proto_builder",
                expected_src = "sample_proto_builder.cc.exp",
                expected_hdr = "sample_proto_builder.cc.exp",
            )

        ```
    """,
    test = True,
    implementation = _proto_builder_test_impl,
)

def cc_proto_builder_manual_library(
        name,
        cc_proto_library_deps = [],
        proto = ["*+"],
        srcs = None,
        hdrs = None,
        deps = [],
        template_srcs = [],
        template_hdrs = [],
        testonly = None,
        visibility = None,
        **kwargs):
    """Creates a cc_library 'name' and verifies src and hdr as proto_builder.

    This macro simplifies cases where cyclic dependencies have to be broken.
    Instead of the macro depending directly on a builder the macro assumes that
    there is exactly one source and one header file which are manually created
    builder source and header files. The macro generates a `cc_library` from
    these sources.

    The macro then creates a builder for `cc_proto_library_deps` and the
    specified proto types. It then compares the manual sources against the
    generated ones.

    This allows to break dependency cycles and can ensure that the sources for
    the `cc_library` are up to date.

    The deps must be managed manually, however, the macro produces an output
    file (name + 'cc_proto_builder.cc_deps.txt') that contains the necessary
    dependencies.

    If the generated test (name + '_golden_test') fails, then there are
    automatically created versions of source and header in the bazel output
    that can be copied as new input source and header. If the test fails to
    run, then the generator (name + '_golden') must be run explicitly.

    READ: https://google.github.io/cpp-proto-builder/usage

    Args:
      name: name of the `cc_library` rule to generate.
      cc_proto_library_deps: The label of a `cc_proto_library` target. It must
                             reference the `proto_library` that defines the
                             proto types defined in `proto`.
      proto: See proto attribute of 'proto_builder' rule.
      srcs: A single manual generated source file that must matche the
            automatically generated source file.
      hdrs: A single manual generated header file that must matche the
            automatically generated header file.
      deps: Dependencies for the `cc_library` rule. The list must include all
            dependencies for the code in srcs/hdrs.
      template_srcs: Optional template srcs, see `proto_builder` rule.
      template_hdrs: Optional template hdrs, see `proto_builder` rule.

      testonly: Whether these rules are only for tests.
      visibility: Visibility passed to the generated `cc_library` rule.
      **kwargs: Other args passed down to the proto_builder and `cc_library`
                rule.
    """
    if len(srcs) != 1:
        fail("There must be exactly one source, the manual generated builder.")
    if srcs[0] != name + ".cc":
        fail("The source must be named: " + name + ".cc.")
    if len(hdrs) != 1:
        fail("There must be exactly one header, the manual generated builder.")
    if hdrs[0] != name + ".h":
        fail("The header must be named: " + name + ".h.")
    if len(cc_proto_library_deps) != 1:
        fail("There must be exactly one cc_proto_library rule in attribut " +
             "cc_proto_library_deps.")

    cc_deps = deps
    for dep in proto_builder_config.DEFAULT_DEPS:
        if dep not in cc_deps:
            cc_deps.append(dep)
    pb_deps = cc_deps
    if cc_proto_library_deps[0] not in cc_deps:
        cc_deps += cc_proto_library_deps

    native.cc_library(
        name = name,
        srcs = srcs,
        hdrs = hdrs,
        deps = cc_deps,
        testonly = testonly,
        visibility = visibility,
        **kwargs
    )
    proto_builder(
        name = name + "_golden",
        testonly = True,
        srcs = template_srcs,
        hdrs = template_hdrs,
        cc_proto_library_deps = cc_proto_library_deps,
        header_out = name + ".h",
        proto = proto,
        source_out = name + ".cc",
        visibility = ["//visibility:private"],
        deps = pb_deps,
    )
    proto_builder_test(
        name = name + "_golden_test",
        expected_hdr = ":" + name + ".h",
        expected_src = ":" + name + ".cc",
        proto_builder_dep = ":" + name + "_golden",
        visibility = ["//visibility:private"],
    )

# Copyright 2021 The CPP Proto Builder 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
#
#      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.

# READ: https://google.github.io/cpp-proto-builder

# The following rules are defined:
# . cc_proto_builder_library:  Generate a cc_library rule using proto_builder.
# . proto_builder_test:        Compares source files with an expected set of
#                              files.
# . proto_builder:             Generate source files only.

"""Defines 'proto_builder' rules that create Builder pattern for proto types."""

load(":build_oss.bzl", "proto_builder_config")
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
load("@bazel_skylib//lib:dicts.bzl", "dicts")

_VIRTUAL_IMPORTS = "/_virtual_imports/"

ProtoBuilderInfo = provider(
    doc = "A provider containing the source files generated by the proto " +
          "builder. It is created and returned by the proto_builder rule.",
    fields = {
        "source_file": "File: The source file generated by the proto builder " +
                       "executable. It has a '.cc' suffix.",
        "header_file": "File: The header file generated by the proto builder " +
                       "executable. It has a '.h' suffix.",
        "interface_file": "File: The interface header file generated by the " +
                          "proto builder executable. It has a '.h' suffix.",
        "source_tpl_file": "File: The template source file used by the proto " +
                           "builder executable. It has a '.cc.tpl' or " +
                           "'.tpl.cc' suffix.",
        "header_tpl_file": "File: The template header file used by the proto " +
                           "builder executable. It has a '.h.tpl' or " +
                           "'.tpl.h' suffix.",
        "interface_tpl_file": "File: The template interface header file used " +
                              "by the proto builder executable. It has a " +
                              "'.h.tpl' or '.tpl.h' suffix.",
    },
)

ProtoLibraryInfo = provider(
    doc = "A provider containing the proto library dependency. It is created " +
          "and returned by the _get_proto_lib aspect.",
    fields = {
        "proto_library": "Target: The proto library target.",
    },
)

ProtoDepsInfo = provider(
    doc = "A provider that retrieves the transitive proto dependencies.",
    fields = {
        "proto_deps": "Depset of all proto deps",
        "cc_library_deps": "Depset of all related cc_library_proto rules",
    },
)

ProtoValidatorInfo = provider(
    doc = "A provider that retrieves information about the proto validator .",
    fields = {
        "cc_proto_validator_library": "Target: A validator cc_library if found.",
        "proto_header": "C++ proto header.",
        "validator_header": "C++ header for validator code.",
    },
)

def _clang_tidy_impl(ctx, src, output_filename):
    """Clang-tidy a file.

    Creates an action that performs clang-tidy on a file and writes the output
    to a separate file.

    If `src` ends in '.exp', then a temp file with '.exp' removed will be
    created, otherwise the temp file will have the same base filename.

    Args:
      ctx: The current rule's context object.
      src: The source file.
      output_filename: The name of the output file.

    Returns:
      The output file.
    """
    src_no_exp = src.path[:-4] if src.path.endswith(".exp") else src.path
    clang_tidy_src = ctx.actions.declare_file(src_no_exp)
    clang_tidy_file = ctx.actions.declare_file(output_filename)
    ctx.actions.run_shell(
        outputs = [clang_tidy_src, clang_tidy_file],
        inputs = [
            src,
            ctx.executable._stable_clang_format_tool,
        ],
        tools = [ctx.executable._stable_clang_format_tool],
        command = "cat {} > {} && {} --sort-includes={} --style=Google {} > {}".format(
            src.path,
            clang_tidy_src.path,
            ctx.executable._stable_clang_format_tool.path,
            "1" if ctx.attr.sort_includes else "0",
            clang_tidy_src.path,
            clang_tidy_file.path,
        ),
        mnemonic = "ClangTidy",
        progress_message = "Clang Tidy on file: %s." % (src.path),
    )
    return clang_tidy_file

_clang_tidy_common_attrs = {
    "_stable_clang_format_tool": attr.label(
        doc = "The target of the clang-format executable.",
        default = Label("@llvm_toolchain//:bin/clang-format"),
        allow_single_file = True,
        executable = True,
        cfg = "exec",
    ),
    "sort_includes": attr.bool(
        doc = "Passes --sort-includes=<value> to clang-tidy tool.",
        default = True,
    ),
}

def _get_proto_lib_impl(target, ctx):
    """The implementation of the '_get_proto_lib' aspect.

    Retrieves the proto_library target of a cc_proto_library target. The
    cc_proto_library target must contain a single deps entry.

    Args:
      ctx: The current rule's context object.
      target: The current rule's target object.

    Returns:
      ProtoLibraryInfo provider.
    """
    proto_library = None
    rule_name = ctx.rule.attr.name
    if ctx.rule.kind == "cc_proto_library":
        if not hasattr(ctx.rule.attr, "deps"):
            fail("The '%s' target must have a deps attribute!", rule_name)
        if hasattr(ctx.rule.attr, "deps"):
            deps = ctx.rule.attr.deps
            if (len(deps) != 1):
                fail("The deps of '%s' must have a single entry!", rule_name)
            proto_library = deps[0]
    return [ProtoLibraryInfo(proto_library = proto_library)]

_get_proto_lib = aspect(
    attr_aspects = ["deps"],
    doc = """An aspect that returns a ProtoLibraryInfo provider.

    The aspect is used by the 'proto_builder' rule.
    """,
    implementation = _get_proto_lib_impl,
)

def _label_to_dependency(label):
    """Return the Label `label` as a single string."""
    return "//" + label.package + ":" + label.name

def _get_proto_deps_aspect_impl(target, ctx):
    """Compute proto dependencies as well as related cc_proto_library deps."""

    # Get transitive proto dependencies.
    local_proto_targets = []
    transitive_proto_targets = None
    if ctx.rule.kind == "proto_library":
        local_proto_targets.append(target.label)
    if hasattr(ctx.rule.attr, "deps"):
        transitive_proto_targets = [dep[ProtoDepsInfo].proto_deps for dep in ctx.rule.attr.deps]
    proto_deps = depset(local_proto_targets, transitive = transitive_proto_targets)

    # Get all direct cc_proto dependencies as well as all proto_library
    # dependencies translated into cc_proto_library dependencies.
    cc_library_deps = list()
    for dep in proto_deps.to_list():
        if dep.name.endswith("_cc_proto"):
            cc_library_deps.append(dep)
        elif dep.name.endswith("_proto"):
            cc_library_deps.append(Label(
                "//" + dep.package + ":" + dep.name[:-6] + "_cc_proto",
            ))

    return [ProtoDepsInfo(
        cc_library_deps = depset(cc_library_deps),
        proto_deps = proto_deps,
    )]

_get_proto_dep_aspect = aspect(
    attr_aspects = ["deps"],
    doc = "Compute proto dependencies as well as related cc_proto_library deps.",
    implementation = _get_proto_deps_aspect_impl,
)

def _get_proto_validator_aspect_impl(target, ctx):
    """Compute proto validator data."""

    # We require the following:
    # . the rule needs to be a cc_library rule (or entail one).
    # . the rule name must end in '_cc_proto_validator'.
    # . the rule needs to have a matching *.pb.h header.
    # . the rule needs to have a matching *.proto.validator.h header.
    if (ctx.rule.kind != "cc_library" or
        not target.label.name.endswith("_cc_proto_validator")):
        return []

    # Construct relative short_paths for proto header and validator header from
    # the `target`.
    path = target.label.package
    prefix = target.label.name[0:-len("_cc_proto_validator")]
    proto_header = None
    proto_header_short_path = (path + "/" + prefix +
                               proto_builder_config.CC_PROTO_EXTENSION)
    validator_header = None
    validator_header_short_path = path + "/" + prefix + ".proto.validator.h"

    # Search all headers for the necessary `proto_header_short_path` and
    # `validator_header_short_path`.
    for t in target[CcInfo].compilation_context.headers.to_list():
        if t.short_path == proto_header_short_path:
            proto_header = t
        if t.short_path == validator_header_short_path:
            validator_header = t
        if proto_header and validator_header:
            break

    if not proto_header or not validator_header:
        fail('Proto Builder has cc_proto_validator dependency "{dep}" but ' +
             'the related headers "{proto_header}" and "{validator_header}" ' +
             "could not be found.".format(
                 dep = target,
                 proto_header = proto_header_short_path,
                 validator_header = validator_header_short_path,
             ))

    return [ProtoValidatorInfo(
        cc_proto_validator_library = target,
        proto_header = proto_header,
        validator_header = validator_header,
    )]

_get_proto_validator_aspect = aspect(
    attr_aspects = ["deps"],
    doc = "Compute proto validator data.",
    implementation = _get_proto_validator_aspect_impl,
)

def is_non_source_in_virtual_imports_folder(
        file,
        virtual_folder = _VIRTUAL_IMPORTS):
    """Determines if the 'file' is not a source file and located under the virtual_folder

    Args:
      file: A file.
      virtual_folder: The virtual folder name. The default value is
        "_virtual_imports".

    Returns:
      True if the file is not a source file and located under the
      virtual_folder. Otherwise, False.
    """
    return not file.is_source and virtual_folder in file.path

def _get_include_directory_for_proto(proto_file):
    """Returns the include directory path for the proto_file.

    The returned directory path can be used as the "--proto_path=" argument
    value.

    Args:
      proto_file: A proto file.

    Returns:
      The include directory path for the proto_file.
    """
    directory = proto_file.path
    prefix_len = 0

    if is_non_source_in_virtual_imports_folder(proto_file):
        root, relative = proto_file.path.split(_VIRTUAL_IMPORTS, 2)
        return root + _VIRTUAL_IMPORTS + relative.split("/", 1)[0]

    if not proto_file.is_source and directory.startswith(proto_file.root.path):
        prefix_len = len(proto_file.root.path) + 1

    if directory.startswith("external", prefix_len):
        external_separator = directory.find("/", prefix_len)
        repository_separator = directory.find("/", external_separator + 1)
        return directory[:repository_separator]
    else:
        return proto_file.root.path if proto_file.root.path else "."

def _compile_and_link(ctx, srcs, hdrs, textual_hdrs, deps):
    """Compile and link.

    Args:
      ctx: Rule context
      srcs: Source files .cc
      hdrs: Header files .h
      textual_hdrs: Other textual headers
      deps: Dependencies, must be cc_library rules

    Returns:
      CcInfo; It does not return DefaultInfo
    """
    cc_toolchain = find_cc_toolchain(ctx)
    feature_configuration = cc_common.configure_features(
        ctx = ctx,
        cc_toolchain = cc_toolchain,
        requested_features = ctx.features,
        unsupported_features = ctx.disabled_features,
    )
    cc_infos = [lib[CcInfo] for lib in deps if CcInfo in lib]

    compilation_contexts = [cc_info.compilation_context for cc_info in cc_infos]
    linking_contexts = [cc_info.linking_context for cc_info in cc_infos]

    (compilation_context, compilation_outputs) = cc_common.compile(
        name = ctx.label.name,
        actions = ctx.actions,
        feature_configuration = feature_configuration,
        cc_toolchain = cc_toolchain,
        srcs = srcs,
        public_hdrs = hdrs,
        textual_hdrs = textual_hdrs,
        compilation_contexts = compilation_contexts,
        grep_includes = ctx.file._grep_includes,
    )
    (linking_context, linking_outputs) = cc_common.create_linking_context_from_compilation_outputs(
        name = ctx.label.name,
        actions = ctx.actions,
        feature_configuration = feature_configuration,
        cc_toolchain = cc_toolchain,
        compilation_outputs = compilation_outputs,
        linking_contexts = linking_contexts,
    )
    return CcInfo(
        compilation_context = compilation_context,
        linking_context = linking_context,
    )

def _proto_builder_impl(ctx):
    """The implementation of the 'proto_builder' rule.

    Execute the CPP proto builder tool on proto type(s).

    Args:
      ctx: The current rule's context object.

    Returns:
      ProtoBuilderInfo provider
      DefaultInfo provider
    """
    cc_proto_library_deps = ctx.attr.cc_proto_library_deps
    if len(cc_proto_library_deps) != 1:
        fail("The cc_proto_library_deps must contain a single entry.")
    if len(ctx.files.srcs) > 1:
        fail("Attribute srcs must contain at most one source file")
    if len(ctx.files.hdrs) > 1:
        fail("Attribute hdrs must contain at most one header file")
    if len(ctx.files.ifcs) > 1:
        fail("Attribute ifcs must contain at most one interface header file")

    output_files = []

    # Write cc_proto_library dependencies to a file.
    cc_deps_labels = []
    cc_library_deps_file_name = ctx.attr.cc_library_deps_file
    if not cc_library_deps_file_name:
        cc_library_deps_file_name = ctx.attr.name + ".cc_deps.txt"
    cc_deps_file = ctx.actions.declare_file(cc_library_deps_file_name)
    cc_deps_labels = cc_proto_library_deps[0][ProtoDepsInfo].cc_library_deps.to_list()
    cc_deps = [_label_to_dependency(dep) for dep in cc_deps_labels]
    ctx.actions.write(cc_deps_file, "\n".join(sorted(cc_deps)) + "\n")
    output_files.append(cc_deps_file)

    # Get proto file(s).
    proto_library_dep = cc_proto_library_deps[0][ProtoLibraryInfo].proto_library
    proto_files_dep = proto_library_dep[ProtoInfo].transitive_sources
    proto_files = proto_files_dep.to_list()

    # Including the direct proto files in the proto_library rule.
    direct_proto_files = proto_library_dep[ProtoInfo].direct_sources
    direct_proto_paths = ",".join([f.path for f in direct_proto_files])
    direct_proto_short_paths = [
        proto_file.short_path[0:-len(".proto")] +
        proto_builder_config.CC_PROTO_EXTENSION
        for proto_file in direct_proto_files
    ]
    proto_files += direct_proto_files

    # Is there a validator dependency for the proto we are interested?
    use_validator = False
    validator_header = ""
    for dep in ctx.attr.deps:
        if ProtoValidatorInfo in dep:
            if dep[ProtoValidatorInfo].cc_proto_validator_library:
                proto_header = dep[ProtoValidatorInfo].proto_header.short_path
                if proto_header in direct_proto_short_paths:
                    use_validator = True
                    validator_header = dep[ProtoValidatorInfo].validator_header.short_path
                    break

    # We can actually specify all proto files correctly using:
    # protofiles_flag = ",".join([proto.path for proto in proto_files])
    # But it is better for the tool to perform file resolution using proto
    # meta data and proto_paths information.
    protofiles_flag = ""
    proto_paths = ",".join(
        [_get_include_directory_for_proto(proto) for proto in proto_files] + [ctx.configuration.genfiles_dir.path],
    )

    protos = ",".join(ctx.attr.proto)
    template_src = None
    if len(ctx.files.srcs) == 1:
        template_src = ctx.files.srcs[0]
    template_hdr = None
    if len(ctx.files.hdrs) == 1:
        template_hdr = ctx.files.hdrs[0]
    template_ifc = None
    if len(ctx.files.ifcs) == 1:
        template_ifc = ctx.files.ifcs[0]
    template_files = []
    if template_src:
        template_files.append(template_src)
    if template_hdr:
        template_files.append(template_hdr)
    if template_ifc:
        template_files.append(template_ifc)
    src_out_name = ctx.attr.source_out or ctx.attr.name + ".cc"
    hdr_out_name = ctx.attr.header_out or ctx.attr.name + ".h"
    ifc_out_name = ctx.attr.interface_out or ctx.attr.name + ".interface.h"
    header_file = ctx.actions.declare_file(hdr_out_name)
    source_file = ctx.actions.declare_file(src_out_name)
    make_interface = ctx.attr.make_interface
    builder_files = [source_file, header_file]
    header_files = [header_file] + [f for f in ctx.files.extra_hdrs]
    source_files = [source_file] + [f for f in ctx.files.extra_srcs]
    if make_interface:
        interface_file = ctx.actions.declare_file(ifc_out_name)
        interface_filename = interface_file.path
        builder_files.append(interface_file)
        header_files.append(interface_file)
    else:
        interface_file = None
        interface_filename = ""
    output_files += builder_files

    proto_builder_config_files = []
    proto_builder_config_file = ""
    if ctx.attr.proto_builder_config:
        proto_builder_config_files = ctx.attr.proto_builder_config[DefaultInfo].files.to_list()
        proto_builder_config_file = proto_builder_config_files[0].path

    # Write containing all known conversion libraries.
    conv_deps_file = ctx.actions.declare_file(
        ctx.attr.name + ".conversion.deps",
    )
    ctx.actions.write(conv_deps_file, "\n".join(
        [str(f) for f in proto_builder_config.CONVERSION_LIBRARIES],
    ) + "\n")

    # Templates files
    template_hdr_file = ctx.file._default_header_tpl if not (template_hdr) else template_hdr
    template_src_file = ctx.file._default_source_tpl if not (template_src) else template_src
    template_ifc_file = ctx.file._default_interface_header_tpl if not (template_ifc) else template_ifc

    # Run proto_builder tool.
    ctx.actions.run_shell(
        outputs = builder_files,
        tools = [
            ctx.executable._proto_builder_tool,
        ],
        inputs = proto_files + template_files + [
            ctx.executable._proto_builder_tool,
            ctx.executable._stable_clang_format_tool,
            conv_deps_file,
        ] + proto_builder_config_files,
        command = "\n".join([
            # Instantiate proto builder tool (Compute stage)
            """{} \
            --proto="{protos}:{direct_proto_paths}" \
            --header="{header_file}" \
            --source="{source_file}" \
            --interface="{interface_file}" \
            --header_in="{header_tpl}" \
            --source_in="{source_tpl}" \
            --interface_in="{interface_tpl}" \
            --tpl_value_header="{tpl_value_header}" \
            --protofiles="{protofiles}" \
            --proto_paths="{proto_paths}" \
            --make_interface="{make_interface}" \
            --max_field_depth="{max_field_depth}" \
            --use_validator="{use_validator}" \
            --validator_header="{validator_header}" \
            --use_global_db=0 \
            --proto_builder_config="{proto_builder_config}" \
            --conv_deps_file="{conv_deps_file}" \
            --template_builder_strip_prefix_dir="{strip_prefix_dir}";""".format(
                ctx.executable._proto_builder_tool.path,
                protos = protos,
                direct_proto_paths = direct_proto_paths,
                header_file = header_file.path,
                source_file = source_file.path,
                interface_file = interface_filename,
                header_tpl = template_hdr and template_hdr.path or "default",
                source_tpl = template_src and template_src.path or "default",
                interface_tpl = template_ifc and template_ifc.path or "default",
                tpl_value_header = ctx.attr.tpl_value_header or "",
                protofiles = protofiles_flag,
                proto_paths = proto_paths,
                make_interface = ctx.attr.make_interface,
                max_field_depth = ctx.attr.max_field_depth,
                use_validator = use_validator,
                validator_header = validator_header,
                proto_builder_config = proto_builder_config_file,
                conv_deps_file = conv_deps_file.path,
                strip_prefix_dir = ctx.genfiles_dir.path,
            ),
            "if [ $? -ne 0 ]; then",
            "exit -1",
            "fi",
            # Post processing stage
            proto_builder_config.proto_builder_processing(
                template_hdr_file,
                ctx.file._default_header_tpl,
                header_file,
            ),
            proto_builder_config.proto_builder_processing(
                template_src_file,
                ctx.file._default_source_tpl,
                source_file,
            ),
            proto_builder_config.proto_builder_processing(
                template_ifc_file,
                ctx.file._default_interface_header_tpl,
                interface_file,
            ) if ctx.attr.make_interface else "",
            # Clang format stage
            "{} -i --style=Google {}".format(
                ctx.executable._stable_clang_format_tool.path,
                source_file.path,
            ),
            "{} -i --style=Google {}".format(
                ctx.executable._stable_clang_format_tool.path,
                header_file.path,
            ),
            "{} -i --style=Google {}".format(
                ctx.executable._stable_clang_format_tool.path,
                interface_file.path,
            ) if ctx.attr.make_interface else "",
            "exit 0",
        ]),
        mnemonic = "CPPProtoBuilder",
        progress_message = "CPP Proto Builder on files: %s" % (direct_proto_paths),
    )

    # If configured, compile and link generated sources.
    cc_infos = []
    if ctx.attr.cc_compile_and_link:
        # Grep the relevant proto headers.
        hdrs = cc_proto_library_deps[0][CcInfo].compilation_context.headers
        textual_hdrs = [h for h in hdrs.to_list() if h.path.endswith(
            proto_builder_config.CC_PROTO_EXTENSION,
        )]

        # See if the conversion library is present.
        other_cc_deps = ctx.attr.deps + ctx.attr._default_deps
        for cc_dep in other_cc_deps:
            if cc_dep.label in proto_builder_config.CONVERSION_LIBRARIES:
                # If so, save the conversion headers.
                for h in cc_dep[CcInfo].compilation_context.headers.to_list():
                    if h.path in proto_builder_config.CONVERSION_HEADERS:
                        textual_hdrs.append(h)

        # Compile and link.
        cc_infos.append(_compile_and_link(
            srcs = source_files,
            hdrs = header_files,
            ctx = ctx,
            textual_hdrs = textual_hdrs,
            deps = cc_proto_library_deps + other_cc_deps,
        ))

    return [
        ProtoBuilderInfo(
            header_file = header_file,
            interface_file = interface_file,
            source_file = source_file,
            source_tpl_file = template_src_file,
            header_tpl_file = template_hdr_file,
            interface_tpl_file = template_ifc_file,
        ),
        DefaultInfo(files = depset(output_files)),
    ] + cc_infos

_default_tpl_attr = {
    "_default_source_tpl": attr.label(
        doc = "The default source template.",
        default = Label("//proto_builder:default.cc.tpl"),
        allow_single_file = [
            ".cc.tpl",
            ".tpl.cc",
        ],
    ),
    "_default_header_tpl": attr.label(
        doc = "The default header template.",
        default = Label("//proto_builder:default.h.tpl"),
        allow_single_file = [
            ".h.tpl",
            ".tpl.h",
        ],
    ),
    "_default_interface_header_tpl": attr.label(
        doc = "The default interface header template.",
        default = Label("//proto_builder:default_interface.h.tpl"),
        allow_single_file = [
            ".h.tpl",
            ".tpl.h",
        ],
    ),
}

_proto_builder_attr = {
    "cc_proto_library_deps": attr.label_list(
        doc = "The dependency on a cc_proto_library target that references " +
              "the proto_library target containing the proto type to " +
              "generate the builder. It must contain a single entry.",
        mandatory = True,
        aspects = [
            _get_proto_lib,
            _get_proto_dep_aspect,
        ],
    ),
    "proto": attr.string_list(
        doc = "The proto type to generate the builder.  By default, builders " +
              "for all top-level messages in the proto lirbary dependency " +
              "are generated.",
        default = ["*+"],
    ),
    "srcs": attr.label_list(
        doc = "The template for generating the source. If not specified, the " +
              "default template is used. The files must have a .cc.tpl or " +
              ".tpl.cc extension",
        allow_files = [
            ".cc.tpl",
            ".tpl.cc",
        ],
        default = [],
    ),
    "hdrs": attr.label_list(
        doc = "The template for generating the header. If not specified, the " +
              "default template is used. The files must have a .h.tpl or " +
              ".tpl.h extension",
        allow_files = [
            ".h.tpl",
            ".tpl.h",
        ],
        default = [],
    ),
    "ifcs": attr.label_list(
        doc = "The template for generating the interface. If not specified, the " +
              "default template is used. The files must have a .h.tpl or " +
              ".tpl.h extension",
        allow_files = [
            ".h.tpl",
            ".tpl.h",
        ],
        default = [],
    ),
    "extra_srcs": attr.label_list(
        doc = "Additional sources.",
        allow_files = [
            ".cc",
            ".h",
        ],
        default = [],
    ),
    "extra_hdrs": attr.label_list(
        doc = "Additional headers.",
        allow_files = [
            ".h",
        ],
        default = [],
    ),
    "source_out": attr.string(
        doc = "The name of the generated source file. If not specified, " +
              "the name is <name>.cc, where <name> is the target name.",
        default = "",
    ),
    "header_out": attr.string(
        doc = "The name of the generated header file. If not specified, " +
              "the name is <name>.h, where <name> is the target name.",
        default = "",
    ),
    "interface_out": attr.string(
        doc = "The name of the generated interface file. If not specified, " +
              "the name is <name>.interface.h, where <name> is the target name.",
        default = "",
    ),
    "tpl_value_header": attr.string(
        doc = "Used to override header related template values. This can be " +
              "used to generate files with a different header reference than " +
              "automatically derived from --header. It is helpful to solve " +
              "cyclic dependencies in proto_builder_test rules where it is " +
              "not possible to generate the header with the intended file " +
              "name.",
        default = "",
    ),
    "proto_builder_config": attr.label(
        doc = "Custom ProtoBuilderConfig textproto file.",
        default = None,
        allow_files = [".textproto"],
    ),
    "max_field_depth": attr.int(
        doc = "Do NOT use outside this tool's tests.",
        default = 0,
    ),
    "make_interface": attr.bool(
        doc = "Whether to generate an interface.",
        default = False,
    ),
    "source_file": attr.output(
        doc = "The generated source file.",
    ),
    "header_file": attr.output(
        doc = "The generated header file.",
    ),
    "interface_file": attr.output(
        doc = "The generated interface file - if any.",
    ),
    "outs": attr.output_list(
        doc = "The list of generated files.",
    ),
    "_proto_builder_tool": attr.label(
        doc = "The target of the proto builder executable.",
        default = Label("//proto_builder:proto_builder"),
        allow_single_file = True,
        executable = True,
        cfg = "exec",
    ),
    # Attributes for the compile and link stage.
    "_cc_toolchain": attr.label(default = proto_builder_config.CC_TOOLCHAIN),
    "cc_compile_and_link": attr.bool(
        doc = "Whether to compile and link the builder library.",
        default = False,
    ),
    "cc_library_deps_file": attr.string(
        doc = "The file to save the required cc_library dependencies.",
        default = "",
    ),
    "deps": attr.label_list(
        allow_rules = ["cc_library"],
        providers = [CcInfo],
        aspects = [_get_proto_validator_aspect],
    ),
    "_default_deps": attr.label_list(
        allow_rules = ["cc_library"],
        providers = [CcInfo],
        default = proto_builder_config.DEFAULT_DEPS,
    ),
    "_grep_includes": attr.label(
        allow_single_file = True,
        executable = True,
        cfg = "host",
        default = Label(proto_builder_config.GREP_INCLUDES),
    ),
}

proto_builder = rule(
    attrs = dicts.add(
        _proto_builder_attr,
        _default_tpl_attr,
        _clang_tidy_common_attrs,
    ),
    doc = """A build rule that generates the cpp builders.

        Note: The rule is useful when the generated source files must be part
        of a larger library.

        READ: https://google.github.io/cpp-proto-builder/usage

        Example:

        1) Required fields.

        ```
            proto_library(
                name = "sample_proto",
                srcs = ["sample.proto"],
            )

            cc_proto_library(
                name = "sample_cc_proto",
                deps = ["sample_proto"],
            )

            proto_builder(
                name = "sample_proto_builder",
                cc_proto_library_deps = ["sample_cc_proto"],
            )

        ```

        2) Selecting a proto type.

        ```
            proto_library(
                name = "sample_proto",
                srcs = ["sample.proto"],
            )

            cc_proto_library(
                name = "sample_cc_proto",
                deps = ["sample_proto"],
            )

            proto_builder(
                name = "sample_proto_builder",
                cc_proto_library_deps = ["sample_cc_proto"],
                proto = ["SampleMessage"],
            )
        ```
    """,
    fragments = proto_builder_config.FRAGMENTS,
    output_to_genfiles = True,
    implementation = _proto_builder_impl,
)

def cc_proto_builder_library(
        name,
        cc_proto_library_deps = [],
        proto = ["*+"],
        srcs = None,
        hdrs = None,
        ifcs = None,
        make_interface = False,
        tpl_value_header = None,
        deps = [],
        testonly = None,
        visibility = None,
        **kwargs):
    """Creates a cc_library target named 'name' of the compiled Builder pattern.

    The rule generates the source files and provide them as inputs to a stand
    alone cc_library rule. As a result, the build system will re-generate the
    sources whenever the inputs to this macro changes.

    READ: https://google.github.io/cpp-proto-builder/usage

    Args:
      name: name of the cc_library rule to generate.
      cc_proto_library_deps: The label of a cc_proto_library target. It must
                             reference the proto_library that defines the proto
                             types defined in 'proto'.
      proto: See proto attribute of 'proto_builder' rule.
      srcs: Optional, see 'proto_builder' rule.
      hdrs: Optional, see 'proto_builder' rule.
      ifcs: Optional, see 'proto_builder' rule.
      make_interface: Optional, see 'proto_builder' rule.
      tpl_value_header: See 'proto_builder' rule.
      deps: Dependencies for the cc_library rule. The list must include all
            dependencies for the code in srcs/hdrs.
      testonly: Whether these rules are only for tests.
      visibility: Visibility passed to the generated cc_library rule.
      **kwargs: Other args passed down to the proto_builder and cc_library rule.
    """
    if cc_proto_library_deps == None:
        fail("The cc_proto_library_deps argument must be defined.")
    if type(cc_proto_library_deps) != "list":
        fail("The cc_proto_library_deps argument must be a list.")
    if len(cc_proto_library_deps) != 1:
        fail("The cc_proto_library_deps must contain a single entry.")
    if not proto and type(proto) != "list":
        fail("The proto argument must be a list.")
    if srcs and type(srcs) != "list":
        fail("The srcs argument must be a list.")
    if hdrs and type(hdrs) != "list":
        fail("The hdrs argument must be a list.")
    if ifcs and type(ifcs) != "list":
        fail("The ifcs argument must be a list.")
    if tpl_value_header != None and type(tpl_value_header) != "string":
        fail("The tpl_value_header argument must be a string.")
    if not deps and type(deps) != "list":
        fail("The deps argument must be a list.")
    filename = name
    header_filename = filename + ".h"
    source_filename = filename + ".cc"
    interface_filename = filename + ".interface.h"

    if proto_builder_config.USE_CC_LIBRARY_MACRO:
        proto_builder_name = "__" + name + "_proto_builder"
    else:
        proto_builder_name = name

    proto_builder(
        name = proto_builder_name,
        cc_proto_library_deps = cc_proto_library_deps,
        proto = proto,
        srcs = srcs,
        hdrs = hdrs,
        source_out = source_filename,
        header_out = header_filename,
        interface_out = interface_filename,
        tpl_value_header = tpl_value_header,
        source_file = filename + ".cc",
        header_file = filename + ".h",
        interface_file = interface_filename if make_interface else None,
        make_interface = make_interface,
        testonly = testonly,
        visibility = visibility,
        cc_compile_and_link = not proto_builder_config.USE_CC_LIBRARY_MACRO,
        deps = deps,
        **kwargs
    )
    if proto_builder_config.USE_CC_LIBRARY_MACRO:
        # pop is used to remove unsupported parameters for native.cc_library
        native.cc_library(
            name = name,
            srcs = [":" + source_filename] + kwargs.pop("extra_srcs", []),
            hdrs = [":" + header_filename] + kwargs.pop("extra_hdrs", []) +
                   ([":" + interface_filename] if make_interface else []),
            deps = cc_proto_library_deps + deps + proto_builder_config.DEFAULT_DEPS,
            testonly = testonly,
            visibility = visibility,
            **kwargs
        )

def _proto_builder_test_impl(ctx):
    """The implementation of the 'proto_builder_test' rule.

    Tests the files generated by the 'proto_builder' rule. The test performs a
    textual comparison of the generated source and header with the expected
    source and header respectively.

    Args:
      ctx: The current rule's context object.

    Returns:
      DefaultInfo provider
    """
    header_golden_file = ctx.file.expected_hdr
    source_golden_file = ctx.file.expected_src
    interface_golden_file = ctx.file.expected_ifc
    header_result_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].header_file
    source_result_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].source_file
    interface_result_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].interface_file
    header_tpl_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].header_tpl_file
    source_tpl_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].source_tpl_file
    interface_tpl_file = ctx.attr.proto_builder_dep[ProtoBuilderInfo].interface_tpl_file
    make_interface = interface_result_file != None
    interface_processed_file = None
    header_golden_file = _clang_tidy_impl(
        ctx,
        header_golden_file,
        header_golden_file.basename + ".golden.h",
    )
    header_result_file = _clang_tidy_impl(
        ctx,
        header_result_file,
        header_result_file.basename + ".result.h",
    )
    header_processed_file = proto_builder_config.proto_builder_test_processing(
        ctx,
        header_tpl_file,
        ctx.file._default_header_tpl,
        header_golden_file,
        header_golden_file.basename + ".processed.h",
    )
    source_golden_file = _clang_tidy_impl(
        ctx,
        source_golden_file,
        source_golden_file.basename + ".golden.cc",
    )
    source_result_file = _clang_tidy_impl(
        ctx,
        source_result_file,
        source_result_file.basename + ".result.cc",
    )
    source_processed_file = proto_builder_config.proto_builder_test_processing(
        ctx,
        source_tpl_file,
        ctx.file._default_source_tpl,
        source_golden_file,
        source_golden_file.basename + ".processed.cc",
    )
    if make_interface:
        interface_golden_file = _clang_tidy_impl(
            ctx,
            interface_golden_file,
            interface_golden_file.basename + ".golden.h",
        )
        interface_result_file = _clang_tidy_impl(
            ctx,
            interface_result_file,
            interface_result_file.basename + ".result.h",
        )
        interface_processed_file = proto_builder_config.proto_builder_test_processing(
            ctx,
            interface_tpl_file,
            ctx.file._default_interface_header_tpl,
            interface_golden_file,
            interface_golden_file.basename + ".processed.h",
        )

    executable_file = ctx.actions.declare_file(ctx.label.name + ".sh")
    ctx.actions.write(
        output = executable_file,
        content = "\n".join([
            "#!/bin/bash",
            "set -e",
            "diff -du {} {}".format(
                header_processed_file.short_path,
                header_result_file.short_path,
            ),
            "diff -du {} {}".format(
                source_processed_file.short_path,
                source_result_file.short_path,
            ),
            "diff -du {} {}".format(
                interface_processed_file.short_path,
                interface_result_file.short_path,
            ) if make_interface else "",
            "exit 0",
        ]),
        is_executable = True,
    )
    runfiles = [
        header_processed_file,
        source_processed_file,
        header_result_file,
        source_result_file,
        executable_file,
    ] + ([interface_processed_file, interface_result_file] if make_interface else [])
    return [DefaultInfo(
        executable = executable_file,
        files = depset([executable_file]),
        runfiles = ctx.runfiles(files = runfiles),
    )]

_proto_builder_test_attrs = {
    "proto_builder_dep": attr.label(
        doc = "The proto_builder target to test.",
        mandatory = True,
        providers = [ProtoBuilderInfo],
    ),
    "expected_src": attr.label(
        doc = "The expected result for the source file (golden result).",
        allow_single_file = [
            ".cc",
            ".cc.exp",
        ],
        mandatory = True,
    ),
    "expected_hdr": attr.label(
        doc = "The expected result for the header file (golden result).",
        allow_single_file = [
            ".h",
            ".h.exp",
        ],
        mandatory = True,
    ),
    "expected_ifc": attr.label(
        doc = "The expected result for the interface header file (golden result).",
        allow_single_file = [
            ".h",
            ".h.exp",
        ],
        mandatory = False,
    ),
}

proto_builder_test = rule(
    attrs = dicts.add(
        _proto_builder_test_attrs,
        _default_tpl_attr,
        _clang_tidy_common_attrs,
    ),
    doc = """A test rule that compares 'proto_builder' against golden files.

        Use this rule **only** if the direct use of the `proto_builder` rule
        would cause a cyclic dependency.

        READ: https://google.github.io/cpp-proto-builder/usage

        Example:

        ```
            proto_library(
                name = "sample_proto",
                srcs = ["sample.proto"],
            )

            cc_proto_library(
                name = "sample_cc_proto",
                deps = ["sample_proto"],
            )

            proto_builder(
                name = "sample_proto_builder",
                testonly = 1,
                cc_proto_library_deps = ["sample_cc_proto"],
            )

            proto_builder_test(
                name = "sample_proto_builder_test",
                proto_builder_dep = "sample_proto_builder",
                expected_src = "sample_proto_builder.cc.exp",
                expected_hdr = "sample_proto_builder.cc.exp",
            )

        ```
    """,
    test = True,
    implementation = _proto_builder_test_impl,
)

def cc_proto_builder_manual_library(
        name,
        cc_proto_library_deps = [],
        proto = ["*+"],
        srcs = None,
        hdrs = None,
        deps = [],
        template_srcs = [],
        template_hdrs = [],
        testonly = None,
        visibility = None,
        **kwargs):
    """Creates a cc_library 'name' and verifies src and hdr as proto_builder.

    This macro simplifies cases where cyclic dependencies have to be broken.
    Instead of the macro depending directly on a builder the macro assumes that
    there is exactly one source and one header file which are manually created
    builder source and header files. The macro generates a `cc_library` from
    these sources.

    The macro then creates a builder for `cc_proto_library_deps` and the
    specified proto types. It then compares the manual sources against the
    generated ones.

    This allows to break dependency cycles and can ensure that the sources for
    the `cc_library` are up to date.

    The deps must be managed manually, however, the macro produces an output
    file (name + 'cc_proto_builder.cc_deps.txt') that contains the necessary
    dependencies.

    If the generated test (name + '_golden_test') fails, then there are
    automatically created versions of source and header in the bazel output
    that can be copied as new input source and header. If the test fails to
    run, then the generator (name + '_golden') must be run explicitly.

    READ: https://google.github.io/cpp-proto-builder/usage

    Args:
      name: name of the `cc_library` rule to generate.
      cc_proto_library_deps: The label of a `cc_proto_library` target. It must
                             reference the `proto_library` that defines the
                             proto types defined in `proto`.
      proto: See proto attribute of 'proto_builder' rule.
      srcs: A single manual generated source file that must matche the
            automatically generated source file.
      hdrs: A single manual generated header file that must matche the
            automatically generated header file.
      deps: Dependencies for the `cc_library` rule. The list must include all
            dependencies for the code in srcs/hdrs.
      template_srcs: Optional template srcs, see `proto_builder` rule.
      template_hdrs: Optional template hdrs, see `proto_builder` rule.

      testonly: Whether these rules are only for tests.
      visibility: Visibility passed to the generated `cc_library` rule.
      **kwargs: Other args passed down to the proto_builder and `cc_library`
                rule.
    """
    if len(srcs) != 1:
        fail("There must be exactly one source, the manual generated builder.")
    if srcs[0] != name + ".cc":
        fail("The source must be named: " + name + ".cc.")
    if len(hdrs) != 1:
        fail("There must be exactly one header, the manual generated builder.")
    if hdrs[0] != name + ".h":
        fail("The header must be named: " + name + ".h.")
    if len(cc_proto_library_deps) != 1:
        fail("There must be exactly one cc_proto_library rule in attribut " +
             "cc_proto_library_deps.")

    cc_deps = deps
    for dep in proto_builder_config.DEFAULT_DEPS:
        if dep not in cc_deps:
            cc_deps.append(dep)
    pb_deps = cc_deps
    if cc_proto_library_deps[0] not in cc_deps:
        cc_deps += cc_proto_library_deps

    native.cc_library(
        name = name,
        srcs = srcs,
        hdrs = hdrs,
        deps = cc_deps,
        testonly = testonly,
        visibility = visibility,
        **kwargs
    )
    proto_builder(
        name = name + "_golden",
        testonly = True,
        srcs = template_srcs,
        hdrs = template_hdrs,
        cc_proto_library_deps = cc_proto_library_deps,
        header_out = name + ".h",
        proto = proto,
        source_out = name + ".cc",
        visibility = ["//visibility:private"],
        deps = pb_deps,
    )
    proto_builder_test(
        name = name + "_golden_test",
        expected_hdr = ":" + name + ".h",
        expected_src = ":" + name + ".cc",
        proto_builder_dep = ":" + name + "_golden",
        visibility = ["//visibility:private"],
    )

# Copyright 2021 The CPP Proto Builder 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 # #      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. # READ: https://google.github.io/cpp-proto-builder # The following rules are defined: # . cc_proto_builder_library:  Generate a cc_library rule using proto_builder. # . proto_builder_test:        Compares source files with an expected set of #                              files. # . proto_builder:             Generate source files only. Defines 'proto_builder' rules that create Builder pattern for proto types. Clang-tidy a file.      Creates an action that performs clang-tidy on a file and writes the output     to a separate file.      If `src` ends in '.exp', then a temp file with '.exp' removed will be     created, otherwise the temp file will have the same base filename.      Args:       ctx: The current rule's context object.       src: The source file.       output_filename: The name of the output file.      Returns:       The output file. The implementation of the '_get_proto_lib' aspect.      Retrieves the proto_library target of a cc_proto_library target. The     cc_proto_library target must contain a single deps entry.      Args:       ctx: The current rule's context object.       target: The current rule's target object.      Returns:       ProtoLibraryInfo provider. An aspect that returns a ProtoLibraryInfo provider.      The aspect is used by the 'proto_builder' rule. Return the Label `label` as a single string. Compute proto dependencies as well as related cc_proto_library deps. # Get transitive proto dependencies. # Get all direct cc_proto dependencies as well as all proto_library # dependencies translated into cc_proto_library dependencies. Compute proto validator data. # We require the following: # . the rule needs to be a cc_library rule (or entail one). # . the rule name must end in '_cc_proto_validator'. # . the rule needs to have a matching *.pb.h header. # . the rule needs to have a matching *.proto.validator.h header. # Construct relative short_paths for proto header and validator header from # the `target`. # Search all headers for the necessary `proto_header_short_path` and # `validator_header_short_path`. Determines if the 'file' is not a source file and located under the virtual_folder      Args:       file: A file.       virtual_folder: The virtual folder name. The default value is         "_virtual_imports".      Returns:       True if the file is not a source file and located under the       virtual_folder. Otherwise, False. Returns the include directory path for the proto_file.      The returned directory path can be used as the "--proto_path=" argument     value.      Args:       proto_file: A proto file.      Returns:       The include directory path for the proto_file. Compile and link.      Args:       ctx: Rule context       srcs: Source files .cc       hdrs: Header files .h       textual_hdrs: Other textual headers       deps: Dependencies, must be cc_library rules      Returns:       CcInfo; It does not return DefaultInfo The implementation of the 'proto_builder' rule.      Execute the CPP proto builder tool on proto type(s).      Args:       ctx: The current rule's context object.      Returns:       ProtoBuilderInfo provider       DefaultInfo provider # Write cc_proto_library dependencies to a file. # Get proto file(s). # Including the direct proto files in the proto_library rule. # Is there a validator dependency for the proto we are interested? # We can actually specify all proto files correctly using: # protofiles_flag = ",".join([proto.path for proto in proto_files]) # But it is better for the tool to perform file resolution using proto # meta data and proto_paths information. # Write containing all known conversion libraries. # Templates files # Run proto_builder tool. # Instantiate proto builder tool (Compute stage) {} \             --proto="{protos}:{direct_proto_paths}" \             --header="{header_file}" \             --source="{source_file}" \             --interface="{interface_file}" \             --header_in="{header_tpl}" \             --source_in="{source_tpl}" \             --interface_in="{interface_tpl}" \             --tpl_value_header="{tpl_value_header}" \             --protofiles="{protofiles}" \             --proto_paths="{proto_paths}" \             --make_interface="{make_interface}" \             --max_field_depth="{max_field_depth}" \             --use_validator="{use_validator}" \             --validator_header="{validator_header}" \             --use_global_db=0 \             --proto_builder_config="{proto_builder_config}" \             --conv_deps_file="{conv_deps_file}" \             --template_builder_strip_prefix_dir="{strip_prefix_dir}"; # Post processing stage # Clang format stage # If configured, compile and link generated sources. # Grep the relevant proto headers. # See if the conversion library is present. # If so, save the conversion headers. # Compile and link. # Attributes for the compile and link stage. A build rule that generates the cpp builders.          Note: The rule is useful when the generated source files must be part         of a larger library.          READ: https://google.github.io/cpp-proto-builder/usage          Example:          1) Required fields.          ```             proto_library(                 name = "sample_proto",                 srcs = ["sample.proto"],             )              cc_proto_library(                 name = "sample_cc_proto",                 deps = ["sample_proto"],             )              proto_builder(                 name = "sample_proto_builder",                 cc_proto_library_deps = ["sample_cc_proto"],             )          ```          2) Selecting a proto type.          ```             proto_library(                 name = "sample_proto",                 srcs = ["sample.proto"],             )              cc_proto_library(                 name = "sample_cc_proto",                 deps = ["sample_proto"],             )              proto_builder(                 name = "sample_proto_builder",                 cc_proto_library_deps = ["sample_cc_proto"],                 proto = ["SampleMessage"],             )         ``` Creates a cc_library target named 'name' of the compiled Builder pattern.      The rule generates the source files and provide them as inputs to a stand     alone cc_library rule. As a result, the build system will re-generate the     sources whenever the inputs to this macro changes.      READ: https://google.github.io/cpp-proto-builder/usage      Args:       name: name of the cc_library rule to generate.       cc_proto_library_deps: The label of a cc_proto_library target. It must                              reference the proto_library that defines the proto                              types defined in 'proto'.       proto: See proto attribute of 'proto_builder' rule.       srcs: Optional, see 'proto_builder' rule.       hdrs: Optional, see 'proto_builder' rule.       ifcs: Optional, see 'proto_builder' rule.       make_interface: Optional, see 'proto_builder' rule.       tpl_value_header: See 'proto_builder' rule.       deps: Dependencies for the cc_library rule. The list must include all             dependencies for the code in srcs/hdrs.       testonly: Whether these rules are only for tests.       visibility: Visibility passed to the generated cc_library rule.       **kwargs: Other args passed down to the proto_builder and cc_library rule. # pop is used to remove unsupported parameters for native.cc_library The implementation of the 'proto_builder_test' rule.      Tests the files generated by the 'proto_builder' rule. The test performs a     textual comparison of the generated source and header with the expected     source and header respectively.      Args:       ctx: The current rule's context object.      Returns:       DefaultInfo provider A test rule that compares 'proto_builder' against golden files.          Use this rule **only** if the direct use of the `proto_builder` rule         would cause a cyclic dependency.          READ: https://google.github.io/cpp-proto-builder/usage          Example:          ```             proto_library(                 name = "sample_proto",                 srcs = ["sample.proto"],             )              cc_proto_library(                 name = "sample_cc_proto",                 deps = ["sample_proto"],             )              proto_builder(                 name = "sample_proto_builder",                 testonly = 1,                 cc_proto_library_deps = ["sample_cc_proto"],             )              proto_builder_test(                 name = "sample_proto_builder_test",                 proto_builder_dep = "sample_proto_builder",                 expected_src = "sample_proto_builder.cc.exp",                 expected_hdr = "sample_proto_builder.cc.exp",             )          ``` Creates a cc_library 'name' and verifies src and hdr as proto_builder.      This macro simplifies cases where cyclic dependencies have to be broken.     Instead of the macro depending directly on a builder the macro assumes that     there is exactly one source and one header file which are manually created     builder source and header files. The macro generates a `cc_library` from     these sources.      The macro then creates a builder for `cc_proto_library_deps` and the     specified proto types. It then compares the manual sources against the     generated ones.      This allows to break dependency cycles and can ensure that the sources for     the `cc_library` are up to date.      The deps must be managed manually, however, the macro produces an output     file (name + 'cc_proto_builder.cc_deps.txt') that contains the necessary     dependencies.      If the generated test (name + '_golden_test') fails, then there are     automatically created versions of source and header in the bazel output     that can be copied as new input source and header. If the test fails to     run, then the generator (name + '_golden') must be run explicitly.      READ: https://google.github.io/cpp-proto-builder/usage      Args:       name: name of the `cc_library` rule to generate.       cc_proto_library_deps: The label of a `cc_proto_library` target. It must                              reference the `proto_library` that defines the                              proto types defined in `proto`.       proto: See proto attribute of 'proto_builder' rule.       srcs: A single manual generated source file that must matche the             automatically generated source file.       hdrs: A single manual generated header file that must matche the             automatically generated header file.       deps: Dependencies for the `cc_library` rule. The list must include all             dependencies for the code in srcs/hdrs.       template_srcs: Optional template srcs, see `proto_builder` rule.       template_hdrs: Optional template hdrs, see `proto_builder` rule.        testonly: Whether these rules are only for tests.       visibility: Visibility passed to the generated `cc_library` rule.       **kwargs: Other args passed down to the proto_builder and `cc_library`                 rule.