[Bazel/C++] Augment `cc_dist_library` to generate lists of source files (#10026)

This change largely moves internal logic that creates the the `CcFileList` provider from `build_systems.bzl` to `cc_dist_library.bzl`.

There are also some associated changes to the particular `cc_dist_library` targets, since the output didn't really make sense after previous BUILD.bazel refactoring. There is also a target now for `libprotoc`.
diff --git a/pkg/cc_dist_library.bzl b/pkg/cc_dist_library.bzl
index d48e8be..383979e 100644
--- a/pkg/cc_dist_library.bzl
+++ b/pkg/cc_dist_library.bzl
@@ -3,12 +3,32 @@
 load("@rules_cc//cc:action_names.bzl", cc_action_names = "ACTION_NAMES")
 load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
 
+################################################################################
+# Archive/linking support
+################################################################################
+
+def _collect_linker_input_objects(dep_label, cc_info, objs, pic_objs):
+    """Accumulate .o and .pic.o files into `objs` and `pic_objs`."""
+    link_ctx = cc_info.linking_context
+    if link_ctx == None:
+        return
+
+    linker_inputs = link_ctx.linker_inputs.to_list()
+    for link_input in linker_inputs:
+        if link_input.owner != dep_label:
+            # This is a transitive dep: skip it.
+            continue
+
+        for lib in link_input.libraries:
+            objs.extend(lib.objects or [])
+            pic_objs.extend(lib.pic_objects or [])
+
 # Creates an action to build the `output_file` static library (archive)
 # using `object_files`.
 def _create_archive_action(
         ctx,
         feature_configuration,
-        cc_toolchain,
+        cc_toolchain_info,
         output_file,
         object_files):
     # Based on Bazel's src/main/starlark/builtins_bzl/common/cc/cc_import.bzl:
@@ -16,7 +36,7 @@
     # Build the command line and add args for all of the input files:
     archiver_variables = cc_common.create_link_variables(
         feature_configuration = feature_configuration,
-        cc_toolchain = cc_toolchain,
+        cc_toolchain = cc_toolchain_info,
         output_file = output_file.path,
         is_using_linker = False,
     )
@@ -48,7 +68,7 @@
         inputs = depset(
             direct = object_files,
             transitive = [
-                cc_toolchain.all_files,
+                cc_toolchain_info.all_files,
             ],
         ),
         use_default_shell_env = False,
@@ -56,72 +76,15 @@
         mnemonic = "CppArchiveDist",
     )
 
-# Implementation for cc_dist_library rule.
-def _cc_dist_library_impl(ctx):
-    cc_toolchain_info = find_cc_toolchain(ctx)
-    if cc_toolchain_info.ar_executable == None:
-        return []
-
-    feature_configuration = cc_common.configure_features(
-        ctx = ctx,
-        cc_toolchain = cc_toolchain_info,
-    )
-
-    # Collect the set of object files from the immediate deps.
-
-    objs = []
-    pic_objs = []
-    for dep in ctx.attr.deps:
-        if CcInfo not in dep:
-            continue
-
-        link_ctx = dep[CcInfo].linking_context
-        if link_ctx == None:
-            continue
-
-        linker_inputs = link_ctx.linker_inputs.to_list()
-        for link_input in linker_inputs:
-            if link_input.owner != dep.label:
-                # This is a transitive dep: skip it.
-                continue
-
-            for lib in link_input.libraries:
-                objs.extend(lib.objects or [])
-                pic_objs.extend(lib.pic_objects or [])
-
-    # For static libraries, build separately with and without pic.
-
-    stemname = "lib" + ctx.label.name
-    outputs = []
-
-    if len(objs) > 0:
-        archive_out = ctx.actions.declare_file(stemname + ".a")
-        _create_archive_action(
-            ctx,
-            feature_configuration,
-            cc_toolchain_info,
-            archive_out,
-            objs,
-        )
-        outputs.append(archive_out)
-
-    if len(pic_objs) > 0:
-        pic_archive_out = ctx.actions.declare_file(stemname + ".pic.a")
-        _create_archive_action(
-            ctx,
-            feature_configuration,
-            cc_toolchain_info,
-            pic_archive_out,
-            pic_objs,
-        )
-        outputs.append(pic_archive_out)
-
-    # For dynamic libraries, use the `cc_common.link` command to ensure
-    # everything gets built correctly according to toolchain definitions.
-
+def _create_dso_link_action(
+        ctx,
+        feature_configuration,
+        cc_toolchain_info,
+        object_files,
+        pic_object_files):
     compilation_outputs = cc_common.create_compilation_outputs(
-        objects = depset(objs),
-        pic_objects = depset(pic_objs),
+        objects = depset(object_files),
+        pic_objects = depset(pic_object_files),
     )
     link_output = cc_common.link(
         actions = ctx.actions,
@@ -134,6 +97,8 @@
     )
     library_to_link = link_output.library_to_link
 
+    outputs = []
+
     # Note: library_to_link.dynamic_library and interface_library are often
     # symlinks in the solib directory. For DefaultInfo, prefer reporting
     # the resolved artifact paths.
@@ -147,6 +112,207 @@
     elif library_to_link.interface_library != None:
         outputs.append(library_to_link.interface_library)
 
+    return outputs
+
+################################################################################
+# Source file/header support
+################################################################################
+
+CcFileList = provider(
+    doc = "List of files to be built into a library.",
+    fields = {
+        # As a rule of thumb, `hdrs` and `textual_hdrs` are the files that
+        # would be installed along with a prebuilt library.
+        "hdrs": "public header files, including those used by generated code",
+        "textual_hdrs": "files which are included but are not self-contained",
+
+        # The `internal_hdrs` are header files which appear in `srcs`.
+        # These are only used when compiling the library.
+        "internal_hdrs": "internal header files (only used to build .cc files)",
+        "srcs": "source files",
+    },
+)
+
+def _flatten_target_files(targets):
+    return depset(transitive = [target.files for target in targets])
+
+    files = []
+    for target in targets:
+        files.extend(target.files.to_list())
+    return files
+
+def _cc_file_list_aspect_impl(target, ctx):
+    # Extract sources from a `cc_library` (or similar):
+    if CcInfo not in target:
+        return []
+
+    # We're going to reach directly into the attrs on the traversed rule.
+    rule_attr = ctx.rule.attr
+
+    # CcInfo is a proxy for what we expect this rule to look like.
+    # However, some deps may expose `CcInfo` without having `srcs`,
+    # `hdrs`, etc., so we use `getattr` to handle that gracefully.
+
+    internal_hdrs = []
+    srcs = []
+
+    # Filter `srcs` so it only contains source files. Headers will go
+    # into `internal_headers`.
+    for src in _flatten_target_files(getattr(rule_attr, "srcs", [])).to_list():
+        if src.extension.lower() in ["c", "cc", "cpp", "cxx"]:
+            srcs.append(src)
+        else:
+            internal_hdrs.append(src)
+
+    return [CcFileList(
+        hdrs = _flatten_target_files(getattr(rule_attr, "hdrs", depset())),
+        textual_hdrs = _flatten_target_files(getattr(
+            rule_attr,
+            "textual_hdrs",
+            depset(),
+        )),
+        internal_hdrs = depset(internal_hdrs),
+        srcs = depset(srcs),
+    )]
+
+cc_file_list_aspect = aspect(
+    doc = """
+Aspect to provide the list of sources and headers from a rule.
+
+Output is CcFileList. Example:
+
+  cc_library(
+      name = "foo",
+      srcs = [
+          "foo.cc",
+          "foo_internal.h",
+      ],
+      hdrs = ["foo.h"],
+      textual_hdrs = ["foo_inl.inc"],
+  )
+  # produces:
+  # CcFileList(
+  #     hdrs = depset([File("foo.h")]),
+  #     textual_hdrs = depset([File("foo_inl.inc")]),
+  #     internal_hdrs = depset([File("foo_internal.h")]),
+  #     srcs = depset([File("foo.cc")]),
+  # )
+""",
+    implementation = _cc_file_list_aspect_impl,
+)
+
+################################################################################
+# Rule impl
+################################################################################
+
+def _collect_inputs(deps):
+    """Collects files from a list of immediate deps.
+
+    This rule collects source files and linker inputs for C++ deps. Only
+    these immediate deps are considered, not transitive deps.
+
+    The return value is a struct with object files (linker inputs),
+    partitioned by PIC and non-pic, and the rules' source and header files:
+
+        struct(
+            objects = ...,       # non-PIC object files
+            pic_objects = ...,   # PIC objects
+            cc_file_list = ...,  # a CcFileList
+        )
+
+    Args:
+      deps: Iterable of immediate deps. These will be treated as the "inputs,"
+          but not the transitive deps.
+
+    Returns:
+      A struct with linker inputs, source files, and header files.
+    """
+
+    objs = []
+    pic_objs = []
+
+    # The returned CcFileList will contain depsets of the deps' file lists.
+    # These lists hold `depset()`s from each of `deps`.
+    srcs = []
+    hdrs = []
+    internal_hdrs = []
+    textual_hdrs = []
+
+    for dep in deps:
+        if CcInfo in dep:
+            _collect_linker_input_objects(
+                dep.label,
+                dep[CcInfo],
+                objs,
+                pic_objs,
+            )
+
+        if CcFileList in dep:
+            cfl = dep[CcFileList]
+            srcs.append(cfl.srcs)
+            hdrs.append(cfl.hdrs)
+            internal_hdrs.append(cfl.internal_hdrs)
+            textual_hdrs.append(cfl.textual_hdrs)
+
+    return struct(
+        objects = objs,
+        pic_objects = pic_objs,
+        cc_file_list = CcFileList(
+            srcs = depset(transitive = srcs),
+            hdrs = depset(transitive = hdrs),
+            internal_hdrs = depset(transitive = internal_hdrs),
+            textual_hdrs = depset(transitive = textual_hdrs),
+        ),
+    )
+
+# Implementation for cc_dist_library rule.
+def _cc_dist_library_impl(ctx):
+    cc_toolchain_info = find_cc_toolchain(ctx)
+
+    feature_configuration = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = cc_toolchain_info,
+    )
+
+    inputs = _collect_inputs(ctx.attr.deps)
+
+    # For static libraries, build separately with and without pic.
+
+    stemname = "lib" + ctx.label.name
+    outputs = []
+
+    if len(inputs.objects) > 0:
+        archive_out = ctx.actions.declare_file(stemname + ".a")
+        _create_archive_action(
+            ctx,
+            feature_configuration,
+            cc_toolchain_info,
+            archive_out,
+            inputs.objects,
+        )
+        outputs.append(archive_out)
+
+    if len(inputs.pic_objects) > 0:
+        pic_archive_out = ctx.actions.declare_file(stemname + ".pic.a")
+        _create_archive_action(
+            ctx,
+            feature_configuration,
+            cc_toolchain_info,
+            pic_archive_out,
+            inputs.pic_objects,
+        )
+        outputs.append(pic_archive_out)
+
+    # For dynamic libraries, use the `cc_common.link` command to ensure
+    # everything gets built correctly according to toolchain definitions.
+    outputs.extend(_create_dso_link_action(
+        ctx,
+        feature_configuration,
+        cc_toolchain_info,
+        inputs.objects,
+        inputs.pic_objects,
+    ))
+
     # We could expose the libraries for use from cc rules:
     #
     # linking_context = cc_common.create_linking_context(
@@ -169,6 +335,7 @@
 
     return [
         DefaultInfo(files = depset(outputs)),
+        inputs.cc_file_list,
     ]
 
 cc_dist_library = rule(
@@ -214,6 +381,7 @@
                    "Only these targets' compilation outputs will be " +
                    "included (i.e., the transitive dependencies are not " +
                    "included in the output)."),
+            aspects = [cc_file_list_aspect],
         ),
         "linkopts": attr.string_list(
             doc = ("Add these flags to the C++ linker command when creating " +