Skip to content
Merged
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
23 changes: 20 additions & 3 deletions bzl/bundle_rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ DocsBundleInfo = provider(
doc = "A documentation bundle with its source and placement metadata.",
fields = {
# Each entry carries the source and placement information needed by
# the runtime manifest, plus the identity and direct-target metadata
# of the bundle that declared it.
"entries": "Ordered entries with source, placement, and direct-target metadata.",
# the runtime manifest, plus the identity, primary Need, and
# direct-target metadata of the bundle that declared it.
"entries": "Ordered entries with source, placement, primary Need, and direct-target metadata.",
"own_source_files": "This bundle's direct source files, excluding nested bundles.",
"source_dir_execroot_path": "Execution-root-relative path of this bundle's direct source root.",
"sourcelinks": "Source-code-link JSON files together with their owning repository.",
Expand Down Expand Up @@ -259,6 +259,7 @@ def _rebase_bundle_entry(entry, mount_at, attach_to, toctree_index):
# the composition graph.
bundle_label = entry.bundle_label,
bundle_name = entry.bundle_name,
primary_need_id = entry.primary_need_id,
code_targets = entry.code_targets,
# This entry is now part of a parent composition. It may have been the
# root of its own standalone bundle, but it is a child entry here and
Expand Down Expand Up @@ -318,6 +319,7 @@ def _docs_bundle_impl(ctx):
own_data = depset(direct = ctx.files.data)
own_bundle_label = str(ctx.label)
own_bundle_name = ctx.label.name
own_primary_need_id = ctx.attr.primary_need_id
own_code_targets = [
struct(
label = str(target.label),
Expand Down Expand Up @@ -355,6 +357,7 @@ def _docs_bundle_impl(ctx):
data = own_data,
bundle_label = own_bundle_label,
bundle_name = own_bundle_name,
primary_need_id = own_primary_need_id,
# This direct entry belongs to the current composition's root
# bundle. _rebase_bundle_entry changes this to false if a parent
# embeds the bundle as a child.
Expand Down Expand Up @@ -397,6 +400,7 @@ def _docs_bundle_impl(ctx):
data = own_data,
bundle_label = own_bundle_label,
bundle_name = own_bundle_name,
primary_need_id = own_primary_need_id,
# This direct entry belongs to the current composition's root
# bundle. _rebase_bundle_entry changes this to false if a parent
# embeds the bundle as a child.
Expand Down Expand Up @@ -428,6 +432,7 @@ def _docs_bundle_impl(ctx):
data = own_data,
bundle_label = own_bundle_label,
bundle_name = own_bundle_name,
primary_need_id = own_primary_need_id,
# This direct entry belongs to the current composition's root
# bundle. _rebase_bundle_entry changes this to false if a parent
# embeds the bundle as a child.
Expand Down Expand Up @@ -494,6 +499,9 @@ _docs_bundle = rule(
# those cases do not call _source_dir_runtime_path().
"source_dir": attr.string(default = ""),
"entry_doc": attr.string(default = "index"),
# Optional explicit association with the Need representing this
# bundle. The value is a Sphinx-Needs ID, not a Bazel label.
"primary_need_id": attr.string(default = ""),
"bundles": attr.label_list(providers = [DocsBundleInfo]),
"bundle_mount_ats": attr.string_list(),
"bundle_attach_tos": attr.string_list(),
Expand All @@ -514,6 +522,7 @@ def create_bundle(
sourcelinks_json = None,
source_dir = None,
entry_doc = "index",
primary_need_id = None,
data = [],
code_targets = [],
visibility = None,
Expand All @@ -524,13 +533,21 @@ def create_bundle(
because they use different runtime path and staging rules.
"""
parsed_bundles = [_parse_bundle_declaration(declaration) for declaration in bundles]
# The public macros use ``None`` to represent an omitted optional ID, but
# the underlying Bazel rule has a string attribute and the manifest schema
# keeps this field string-valued. Normalize at that boundary so callers do
# not need to know about the rule's empty-string sentinel.
normalized_primary_need_id = (
primary_need_id if primary_need_id != None else ""
)
_docs_bundle(
name = name,
source_dir_globbed = source_dir_globbed,
source_targets = source_targets,
sourcelinks_json = sourcelinks_json,
source_dir = source_dir if source_dir != None else "",
entry_doc = entry_doc,
primary_need_id = normalized_primary_need_id,
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],
Expand Down
4 changes: 4 additions & 0 deletions bzl/mount_rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ def _composition_manifest_impl(ctx):
"bundle": {
"label": entry.bundle_label,
"name": entry.bundle_name,
# This explicit ID identifies the primary Need represented by
# the bundle. It is intentionally independent of the Bazel
# target name and of any other Need IDs in the bundle.
"primary_need_id": entry.primary_need_id,
# Each direct target contributes its Bazel label and rule kind
# to the bundle metadata consumed by Python.
"code_targets": [
Expand Down
12 changes: 12 additions & 0 deletions docs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ def _declare_docs_bundle(
entry_doc = "index",
bundles = [],
code_targets = [],
primary_need_id = None,
visibility = None,
**kwargs):
"""Declare the shared bundle target implementation.
Expand Down Expand Up @@ -263,6 +264,9 @@ def _declare_docs_bundle(
code_targets: Implementation targets or filegroups to scan for source-code
links. Implementation target source files and their dependencies
are collected recursively; filegroups expand to their files.
primary_need_id: Sphinx-Needs ID of the primary Need representing this
bundle. Other Needs may remain in the bundle; only this
Need receives bundle-level target metadata.
visibility: Target visibility.
**kwargs: Additional attributes forwarded to the underlying rule.
"""
Expand Down Expand Up @@ -305,6 +309,7 @@ def _declare_docs_bundle(
bundles = bundles,
data = bundle_data,
code_targets = code_targets,
primary_need_id = primary_need_id,
visibility = visibility,
**kwargs
)
Expand Down Expand Up @@ -398,6 +403,7 @@ def docs_bundle(
entry_doc = "index",
bundles = [],
code_targets = [],
primary_need_id = None,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if people are using positional parameters they deserve trouble

visibility = None,
**kwargs):
"""Declare a reusable documentation bundle.
Expand All @@ -415,6 +421,7 @@ def docs_bundle(
entry_doc = entry_doc,
bundles = bundles,
code_targets = code_targets,
primary_need_id = primary_need_id,
visibility = visibility,
**kwargs
)
Expand Down Expand Up @@ -500,6 +507,7 @@ def docs(
deps = [],
external_needs = [],
code_targets = [],
primary_need_id = None,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if people are using positional parameters they deserve trouble

test_sources = [],
known_good = None,
metamodel = None,
Expand All @@ -523,6 +531,9 @@ def docs(
code_targets: Implementation targets or filegroups to scan for source code
links. Implementation targets are scanned recursively; filegroups
expand to their files.
primary_need_id: Sphinx-Needs ID of the primary Need representing this
documentation project and its root bundle. Other Needs
remain unchanged.
test_sources: Optional list of repo-relative directory paths which will be used to filter testcases for documentation generation.
When empty (default), all testcases found in `bazel-testlogs` will be used.
known_good: Optional label to a "known good" JSON file for source links.
Expand Down Expand Up @@ -586,6 +597,7 @@ def docs(
entry_doc = "index",
bundles = bundles,
code_targets = code_targets,
primary_need_id = primary_need_id,
visibility = ["//visibility:public"],
tags = ["manual"]
)
Expand Down
6 changes: 6 additions & 0 deletions docs/how-to/source_to_doc_links.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,10 @@ uses. You may also pass filegroups; their files are scanned directly.
],
source_dir = "docs",
code_targets = [":some_application"],
primary_need_id = "comp__some_component",
)

``primary_need_id`` is optional and identifies the single Need representing
the bundle's primary subject. A bundle can contain many other Needs; target
metadata is attached only to this explicitly selected Need. The ID is never
derived from the Bazel bundle name.
13 changes: 12 additions & 1 deletion docs/reference/bazel_macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ Minimal example (root ``BUILD``)
generated JSON is supplied to ``live_preview`` just like a normal documentation
build.

- ``primary_need_id`` (string, optional)
ID of the Sphinx-Needs item that represents the primary subject of this
documentation project. A project may contain many other Needs; when this
value is set, bundle-level target metadata is written only to this Need.

- ``external_needs`` (list of bazel labels)
External ``:needs_json_file`` targets from other modules/repositories
for referencing their needs.
Expand Down Expand Up @@ -166,7 +171,7 @@ site).
visibility = ["//visibility:public"],
)

Signature: ``docs_bundle(name, source_dir = None, srcs = [], data = [], entry_doc = "index", bundles = [], code_targets = [], visibility = None)``.
Signature: ``docs_bundle(name, source_dir = None, srcs = [], data = [], entry_doc = "index", bundles = [], code_targets = [], primary_need_id = None, visibility = None)``.

- ``source_dir`` (string, optional)
Directory holding the bundle's own doc sources. It is globbed the same way as
Expand Down Expand Up @@ -242,3 +247,9 @@ Signature: ``docs_bundle(name, source_dir = None, srcs = [], data = [], entry_do
recursively from their ``deps``; filegroups expand to their files. The bundle
owns one cached scan result; Bazel only regenerates it when its collected source
inputs change.

- ``primary_need_id`` (string, optional)
ID of the local Sphinx-Needs item representing the primary subject of this
bundle. The bundle may contain many additional Needs; only this explicitly
selected Need receives bundle-level target metadata. The ID is independent
of the Bazel bundle target name.
8 changes: 5 additions & 3 deletions src/extensions/docs/mounts_internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ Each manifest entry contains:
* ``mount_at`` and ``attach_to`` — the already-composed Sphinx placement; and
* ``entry_doc`` — the canonical entry document declared by the source bundle.
* ``external`` — whether the directory belongs to another Bazel module;
* ``bundle`` — the declaring bundle's Bazel label, name, and direct targets;
* ``bundle`` — the declaring bundle's Bazel label, name, explicit primary Need
ID, and direct targets;
* ``root_bundle`` — whether this physical entry belongs to the root bundle of
the current composition. This is composition-specific: the same bundle can
be a root in a standalone manifest and a child in another composition.
Expand Down Expand Up @@ -72,8 +73,9 @@ the mount integration's recorded output, while primary docnames come from
Sphinx's discovery set. Documents discovered through data-only mounts are
intentionally omitted because those mounts provide auxiliary files rather than
bundle-owned source roots. This avoids assigning a bundle to skipped,
conflicting, or data-only mounts. The matcher in ``score_metamodel`` will
consume this mapping without inspecting paths or mount configuration.
conflicting, or data-only mounts. The bundle metadata pass in
``score_metamodel`` consumes this mapping without inspecting paths or mount
configuration.

The rule rejects conflicting final placements before Sphinx starts. A mount
without ``attach_to`` is attached to the ``index`` document beside its
Expand Down
1 change: 1 addition & 0 deletions src/extensions/score_metamodel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ py_library(
# TODO: Figure out if all requirements are needed or if we can break it down a bit
deps = all_requirements + [
"@score_docs_as_code//src/extensions/score_cross_module_compatibility",
"@score_docs_as_code//src/extensions/score_mounts",
"@score_docs_as_code//src/extensions/score_metrics",
"@score_docs_as_code//src/helper_lib",
],
Expand Down
4 changes: 4 additions & 0 deletions src/extensions/score_metamodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from sphinx_needs.data import NeedsView, SphinxNeedsData
from sphinx_needs.need_item import NeedItem

from src.extensions.score_metamodel.bundle_metadata import apply_bundle_metadata
from src.extensions.score_metamodel.external_needs import connect_external_needs
from src.extensions.score_metamodel.log import CheckLogger

Expand Down Expand Up @@ -300,6 +301,9 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
)

_ = app.connect("write-started", lambda app, _builder: _run_checks(app))
# The template extension may purge and reread report pages at priority 600.
# Matching after that pass makes the final Need collection authoritative.
_ = app.connect("env-updated", apply_bundle_metadata, priority=650)

return {
"version": "0.1",
Expand Down
Loading
Loading