Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions bzl/bundle_rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ DocsBundleInfo = provider(
# these are resolved at this bundle's mount (for example, a generated
# index.rst).
"data": "Bundle-owned generated/supporting files resolved at the bundle's mount.",
# Keep the direct declarations separate from the closure. The macro
# needs the direct labels to create the immediate Sphinx inputs, while
# consumers of the provider need the complete ancestor set.
"direct_upward_bundles": "The bundle targets declared directly in upward_bundles.",
"upward_bundles": "Depset containing the direct upward dependencies and their transitive closure.",
},
)

Expand Down Expand Up @@ -233,12 +238,37 @@ def _parse_bundle_declaration(bundle):
)

def _docs_bundle_impl(ctx):
"""Compose source files and nested bundles into a reusable bundle."""
"""Compose a bundle and propagate both content and hierarchy metadata.

``bundles`` and ``upward_bundles`` describe two different graphs:

* ``bundles`` is the content graph. Its entries are mounted into this
bundle and therefore contribute source files and data.
* ``upward_bundles`` is the Needs dependency graph. It does not mount any
files; it only makes the ancestors' merged Needs exports available to
the bundle's own Needs build.

Keeping these graphs separate is intentional. A bundle may contain a
nested documentation subtree without depending on that subtree's Needs,
and a bundle may depend on an ancestor's Needs without mounting the
ancestor's sources.
"""
entries = []
own_source_files = []
own_external_runfiles = []
own_data = depset(direct = ctx.files.data)

# Propagate the complete ancestor closure. ``docs.bzl`` uses the direct
# labels for the current Sphinx invocation, while the closure makes the
# hierarchy available transitively to future bundle consumers.
upward_bundles = depset(
direct = ctx.attr.upward_bundles,
transitive = [
upward_bundle[DocsBundleInfo].upward_bundles
for upward_bundle in ctx.attr.upward_bundles
],
)

if ctx.files.srcs:
runtime_path = _bundle_runtime_path(ctx)
external = runtime_path.startswith("../")
Expand Down Expand Up @@ -317,6 +347,8 @@ def _docs_bundle_impl(ctx):
sourcelinks = sourcelinks,
external_runfiles = external_runfiles,
data = all_data,
direct_upward_bundles = ctx.attr.upward_bundles,
upward_bundles = upward_bundles,
),
]

Expand All @@ -330,13 +362,26 @@ _docs_bundle = rule(
"bundles": attr.label_list(providers = [DocsBundleInfo]),
"bundle_mount_ats": attr.string_list(),
"bundle_attach_tos": attr.string_list(),
"upward_bundles": attr.label_list(
providers = [DocsBundleInfo],
doc = "Ancestor bundles whose merged Needs exports are available to this bundle.",
),
"data": attr.label_list(allow_files = True),
},
doc = "Internal rule that carries bundle files and their documentation-tree locations.",
)

def create_bundle(name, bundles, srcs = [], sourcelinks = [], strip_prefix = "", entry_doc = "index", data = [], visibility = None, **kwargs):
"""Create a reusable documentation bundle from files and child declarations."""
def create_bundle(name, bundles, srcs = [], sourcelinks = [], strip_prefix = "", entry_doc = "index", data = [], visibility = None, upward_bundles = [], **kwargs):
"""Create a reusable bundle from files, child bundles, and Needs ancestors.

``bundles`` are content composition: their sources and data become part of
this bundle and are rebased below their declared mount points.
``upward_bundles`` are deliberately not content composition. They describe
which already-built Needs exports are available when this bundle's own
sources are processed. The distinction prevents an ancestor's source tree
from being mounted or exported a second time just because its Needs are
needed for link resolution.
"""
parsed_bundles = [_parse_bundle_declaration(declaration) for declaration in bundles]
_docs_bundle(
name = name,
Expand All @@ -347,6 +392,7 @@ def create_bundle(name, bundles, srcs = [], sourcelinks = [], strip_prefix = "",
bundles = [bundle.bundle for bundle in parsed_bundles],
bundle_mount_ats = [bundle.mount_at for bundle in parsed_bundles],
bundle_attach_tos = [bundle.attach_to for bundle in parsed_bundles],
upward_bundles = upward_bundles,
data = data,
visibility = visibility,
**kwargs
Expand Down
6 changes: 5 additions & 1 deletion default_conf.py.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
# Default Sphinx configuration emitted by the ``docs()`` macro.
# Default Sphinx configuration emitted by the ``docs()`` and
# ``docs_bundle()`` macros.
# SCORE Docs-as-Code owns these baseline settings. Projects needing further
# Sphinx configuration can provide their own conf.py instead.

project = {PROJECT}
project_url = {PROJECT_URL}
version = "0.0.0"
# ``docs_bundle(entry_doc = ...)`` may use a non-index entry page. The regular
# project-level docs() build uses the default value, ``index``.
master_doc = {ENTRY_DOC}

# Allow feature IDs that use the Bazel module name without its first
# underscore-separated prefix (for example, ``score_docs_as_code`` becomes
Expand Down
Loading
Loading