docs: generate docs for py_common, PyInfoBuilder APIs (#2920)

I wrote up the docs awhile, but didn't fully wire them through to the
doc gen.

Fixes some various issues with the generated docs along the way.
diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel
index 25da682..b3e5f52 100644
--- a/docs/BUILD.bazel
+++ b/docs/BUILD.bazel
@@ -113,6 +113,7 @@
         "//python/private:builders_util_bzl",
         "//python/private:py_binary_rule_bzl",
         "//python/private:py_cc_toolchain_rule_bzl",
+        "//python/private:py_info_bzl",
         "//python/private:py_library_rule_bzl",
         "//python/private:py_runtime_rule_bzl",
         "//python/private:py_test_rule_bzl",
diff --git a/python/api/api.bzl b/python/api/api.bzl
index c8fb921..d41ec73 100644
--- a/python/api/api.bzl
+++ b/python/api/api.bzl
@@ -1,4 +1,23 @@
-"""Public, analysis phase APIs for Python rules."""
+"""Public, analysis phase APIs for Python rules.
+
+To use the analyis-time API, add the attributes to your rule, then
+use `py_common.get()` to get the api object:
+
+```
+load("@rules_python//python/api:api.bzl", "py_common")
+
+def _impl(ctx):
+    py_api = py_common.get(ctx)
+
+myrule = rule(
+    implementation = _impl,
+    attrs = {...} | py_common.API_ATTRS
+)
+```
+
+:::{versionadded} 0.37.0
+:::
+"""
 
 load("//python/private/api:api.bzl", _py_common = "py_common")
 
diff --git a/python/private/api/api.bzl b/python/private/api/api.bzl
index 06fb729..44f9ab4 100644
--- a/python/private/api/api.bzl
+++ b/python/private/api/api.bzl
@@ -27,6 +27,17 @@
     },
 )
 
+def _py_common_typedef():
+    """Typedef for py_common.
+
+    :::{field} API_ATTRS
+    :type: dict[str, Attribute]
+
+    The attributes that rules must have for `py_common.get()` to work.
+    :::
+
+    """
+
 def _py_common_get(ctx):
     """Get the py_common API instance.
 
@@ -45,6 +56,7 @@
     return ctx.attr._py_common_api[ApiImplInfo].impl
 
 py_common = struct(
+    TYPEDEF = _py_common_typedef,
     get = _py_common_get,
     API_ATTRS = {
         "_py_common_api": attr.label(
diff --git a/python/private/api/py_common_api.bzl b/python/private/api/py_common_api.bzl
index 401b359..6fed245 100644
--- a/python/private/api/py_common_api.bzl
+++ b/python/private/api/py_common_api.bzl
@@ -22,17 +22,40 @@
 
 py_common_api = rule(
     implementation = _py_common_api_impl,
-    doc = "Rule implementing py_common API.",
+    doc = "Internal Rule implementing py_common API.",
 )
 
+def _py_common_api_typedef():
+    """The py_common API implementation.
+
+    An instance of this object is obtained using {obj}`py_common.get()`
+    """
+
 def _merge_py_infos(transitive, *, direct = []):
-    builder = PyInfoBuilder()
+    """Merge PyInfo objects into a single PyInfo.
+
+    This is a convenience wrapper around {obj}`PyInfoBuilder.merge_all`. For
+    more control over merging PyInfo objects, use {obj}`PyInfoBuilder`.
+
+    Args:
+        transitive: {type}`list[PyInfo]` The PyInfo objects with info
+            considered indirectly provided by something (e.g. via
+            its deps attribute).
+        direct: {type}`list[PyInfo]` The PyInfo objects that are
+            considered directly provided by something (e.g. via
+            the srcs attribute).
+
+    Returns:
+        {type}`PyInfo` A PyInfo containing the merged values.
+    """
+    builder = PyInfoBuilder.new()
     builder.merge_all(transitive, direct = direct)
     return builder.build()
 
 # Exposed for doc generation, not directly used.
 # buildifier: disable=name-conventions
 PyCommonApi = struct(
+    TYPEDEF = _py_common_api_typedef,
     merge_py_infos = _merge_py_infos,
-    PyInfoBuilder = PyInfoBuilder,
+    PyInfoBuilder = PyInfoBuilder.new,
 )
diff --git a/python/private/common.bzl b/python/private/common.bzl
index 072a1bb..a58a9c0 100644
--- a/python/private/common.bzl
+++ b/python/private/common.bzl
@@ -405,7 +405,7 @@
         transitive sources collected from dependencies (the latter is only
         necessary for deprecated extra actions support).
     """
-    py_info = PyInfoBuilder()
+    py_info = PyInfoBuilder.new()
     py_info.site_packages_symlinks.add(site_packages_symlinks)
     py_info.direct_original_sources.add(original_sources)
     py_info.direct_pyc_files.add(required_pyc_files)
diff --git a/python/private/py_info.bzl b/python/private/py_info.bzl
index dc3cb24..d175eef 100644
--- a/python/private/py_info.bzl
+++ b/python/private/py_info.bzl
@@ -82,7 +82,11 @@
     }
 
 PyInfo, _unused_raw_py_info_ctor = define_bazel_6_provider(
-    doc = "Encapsulates information provided by the Python rules.",
+    doc = """Encapsulates information provided by the Python rules.
+
+Instead of creating this object directly, use {obj}`PyInfoBuilder` and
+the {obj}`PyCommonApi` utilities.
+""",
     init = _PyInfo_init,
     fields = {
         "direct_original_sources": """
@@ -265,7 +269,65 @@
 # The "effective" PyInfo is what the canonical //python:py_info.bzl%PyInfo symbol refers to
 _EffectivePyInfo = PyInfo if (config.enable_pystar or BuiltinPyInfo == None) else BuiltinPyInfo
 
-def PyInfoBuilder():
+def _PyInfoBuilder_typedef():
+    """Builder for PyInfo.
+
+    To create an instance, use {obj}`py_common.get()` and call `PyInfoBuilder()`
+
+    :::{field} direct_original_sources
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} direct_pyc_files
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} direct_pyi_files
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} imports
+    :type: DepsetBuilder[str]
+    :::
+
+    :::{field} transitive_implicit_pyc_files
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} transitive_implicit_pyc_source_files
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} transitive_original_sources
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} transitive_pyc_files
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} transitive_pyi_files
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} transitive_sources
+    :type: DepsetBuilder[File]
+    :::
+
+    :::{field} site_packages_symlinks
+    :type: DepsetBuilder[tuple[str | None, str]]
+
+    NOTE: This depset has `topological` order
+    :::
+    """
+
+def _PyInfoBuilder_new():
+    """Creates an instance.
+
+    Returns:
+        {type}`PyInfoBuilder`
+    """
+
     # buildifier: disable=uninitialized
     self = struct(
         _has_py2_only_sources = [False],
@@ -301,35 +363,116 @@
     return self
 
 def _PyInfoBuilder_get_has_py3_only_sources(self):
+    """Get the `has_py3_only_sources` value.
+
+    Args:
+        self: implicitly added.
+
+    Returns:
+        {type}`bool`
+    """
     return self._has_py3_only_sources[0]
 
 def _PyInfoBuilder_get_has_py2_only_sources(self):
+    """Get the `has_py2_only_sources` value.
+
+    Args:
+        self: implicitly added.
+
+    Returns:
+        {type}`bool`
+    """
     return self._has_py2_only_sources[0]
 
 def _PyInfoBuilder_set_has_py2_only_sources(self, value):
+    """Sets `has_py2_only_sources` to `value`.
+
+    Args:
+        self: implicitly added.
+        value: {type}`bool` The value to set.
+
+    Returns:
+        {type}`PyInfoBuilder` self
+    """
     self._has_py2_only_sources[0] = value
     return self
 
 def _PyInfoBuilder_set_has_py3_only_sources(self, value):
+    """Sets `has_py3_only_sources` to `value`.
+
+    Args:
+        self: implicitly added.
+        value: {type}`bool` The value to set.
+
+    Returns:
+        {type}`PyInfoBuilder` self
+    """
     self._has_py3_only_sources[0] = value
     return self
 
 def _PyInfoBuilder_merge_has_py2_only_sources(self, value):
+    """Sets `has_py2_only_sources` based on current and incoming `value`.
+
+    Args:
+        self: implicitly added.
+        value: {type}`bool` Another `has_py2_only_sources` value. It will
+            be merged into this builder's state.
+
+    Returns:
+        {type}`PyInfoBuilder` self
+    """
     self._has_py2_only_sources[0] = self._has_py2_only_sources[0] or value
     return self
 
 def _PyInfoBuilder_merge_has_py3_only_sources(self, value):
+    """Sets `has_py3_only_sources` based on current and incoming `value`.
+
+    Args:
+        self: implicitly added.
+        value: {type}`bool` Another `has_py3_only_sources` value. It will
+            be merged into this builder's state.
+
+    Returns:
+        {type}`PyInfoBuilder` self
+    """
     self._has_py3_only_sources[0] = self._has_py3_only_sources[0] or value
     return self
 
 def _PyInfoBuilder_merge_uses_shared_libraries(self, value):
+    """Sets `uses_shared_libraries` based on current and incoming `value`.
+
+    Args:
+        self: implicitly added.
+        value: {type}`bool` Another `uses_shared_libraries` value. It will
+            be merged into this builder's state.
+
+    Returns:
+        {type}`PyInfoBuilder` self
+    """
     self._uses_shared_libraries[0] = self._uses_shared_libraries[0] or value
     return self
 
 def _PyInfoBuilder_get_uses_shared_libraries(self):
+    """Get the `uses_shared_libraries` value.
+
+    Args:
+        self: implicitly added.
+
+    Returns:
+        {type}`bool`
+    """
     return self._uses_shared_libraries[0]
 
 def _PyInfoBuilder_set_uses_shared_libraries(self, value):
+    """Sets `uses_shared_libraries` to `value`.
+
+    Args:
+        self: implicitly added.
+        value: {type}`bool` The value to set.
+
+    Returns:
+        {type}`PyInfoBuilder` self
+    """
     self._uses_shared_libraries[0] = value
     return self
 
@@ -344,7 +487,7 @@
             direct fields into this object's direct fields.
 
     Returns:
-        {type}`PyInfoBuilder` the current object
+        {type}`PyInfoBuilder` self
     """
     return self.merge_all(list(infos), direct = direct)
 
@@ -359,7 +502,7 @@
             direct fields into this object's direct fields.
 
     Returns:
-        {type}`PyInfoBuilder` the current object
+        {type}`PyInfoBuilder` self
     """
     for info in direct:
         # BuiltinPyInfo doesn't have this field
@@ -392,11 +535,11 @@
     Args:
         self: implicitly added.
         target: {type}`Target` targets that provide PyInfo, or other relevant
-        providers, will be merged into this object. If a target doesn't provide
-        any relevant providers, it is ignored.
+            providers, will be merged into this object. If a target doesn't provide
+            any relevant providers, it is ignored.
 
     Returns:
-        {type}`PyInfoBuilder` the current object.
+        {type}`PyInfoBuilder` self.
     """
     if PyInfo in target:
         self.merge(target[PyInfo])
@@ -410,18 +553,26 @@
     Args:
         self: implicitly added.
         targets: {type}`list[Target]`
-        targets that provide PyInfo, or other relevant
-        providers, will be merged into this object. If a target doesn't provide
-        any relevant providers, it is ignored.
+            targets that provide PyInfo, or other relevant
+            providers, will be merged into this object. If a target doesn't provide
+            any relevant providers, it is ignored.
 
     Returns:
-        {type}`PyInfoBuilder` the current object.
+        {type}`PyInfoBuilder` self.
     """
     for t in targets:
         self.merge_target(t)
     return self
 
 def _PyInfoBuilder_build(self):
+    """Builds into a {obj}`PyInfo` object.
+
+    Args:
+        self: implicitly added.
+
+    Returns:
+        {type}`PyInfo`
+    """
     if config.enable_pystar:
         kwargs = dict(
             direct_original_sources = self.direct_original_sources.build(),
@@ -447,6 +598,15 @@
     )
 
 def _PyInfoBuilder_build_builtin_py_info(self):
+    """Builds into a Bazel-builtin PyInfo object, if available.
+
+    Args:
+        self: implicitly added.
+
+    Returns:
+        {type}`BuiltinPyInfo | None` None is returned if Bazel's
+        builtin PyInfo object is disabled.
+    """
     if BuiltinPyInfo == None:
         return None
 
@@ -457,3 +617,25 @@
         transitive_sources = self.transitive_sources.build(),
         uses_shared_libraries = self._uses_shared_libraries[0],
     )
+
+# Provided for documentation purposes
+# buildifier: disable=name-conventions
+PyInfoBuilder = struct(
+    TYPEDEF = _PyInfoBuilder_typedef,
+    new = _PyInfoBuilder_new,
+    build = _PyInfoBuilder_build,
+    build_builtin_py_info = _PyInfoBuilder_build_builtin_py_info,
+    get_has_py2_only_sources = _PyInfoBuilder_get_has_py2_only_sources,
+    get_has_py3_only_sources = _PyInfoBuilder_get_has_py3_only_sources,
+    get_uses_shared_libraries = _PyInfoBuilder_get_uses_shared_libraries,
+    merge = _PyInfoBuilder_merge,
+    merge_all = _PyInfoBuilder_merge_all,
+    merge_has_py2_only_sources = _PyInfoBuilder_merge_has_py2_only_sources,
+    merge_has_py3_only_sources = _PyInfoBuilder_merge_has_py3_only_sources,
+    merge_target = _PyInfoBuilder_merge_target,
+    merge_targets = _PyInfoBuilder_merge_targets,
+    merge_uses_shared_libraries = _PyInfoBuilder_merge_uses_shared_libraries,
+    set_has_py2_only_sources = _PyInfoBuilder_set_has_py2_only_sources,
+    set_has_py3_only_sources = _PyInfoBuilder_set_has_py3_only_sources,
+    set_uses_shared_libraries = _PyInfoBuilder_set_uses_shared_libraries,
+)
diff --git a/python/private/py_package.bzl b/python/private/py_package.bzl
index 1d866a9..adf2b6d 100644
--- a/python/private/py_package.bzl
+++ b/python/private/py_package.bzl
@@ -34,7 +34,7 @@
 
 def _py_package_impl(ctx):
     inputs = builders.DepsetBuilder()
-    py_info = PyInfoBuilder()
+    py_info = PyInfoBuilder.new()
     for dep in ctx.attr.deps:
         inputs.add(dep[DefaultInfo].data_runfiles.files)
         inputs.add(dep[DefaultInfo].default_runfiles.files)
diff --git a/tests/base_rules/py_info/py_info_tests.bzl b/tests/base_rules/py_info/py_info_tests.bzl
index e160e70..aa252a2 100644
--- a/tests/base_rules/py_info/py_info_tests.bzl
+++ b/tests/base_rules/py_info/py_info_tests.bzl
@@ -162,7 +162,7 @@
         direct_pyi,
         trans_pyi,
     ) = targets.misc[DefaultInfo].files.to_list()
-    builder = PyInfoBuilder()
+    builder = PyInfoBuilder.new()
     builder.direct_pyc_files.add(direct_pyc)
     builder.direct_original_sources.add(original_py)
     builder.direct_pyi_files.add(direct_pyi)