Skip to content
Open
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
20 changes: 19 additions & 1 deletion docs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -478,11 +478,12 @@ def _declare_bundle_local_needs(
sphinx_build_deps = _sphinx_runtime_deps(deps)

needs_local = _bundle_internal_target(name, "needs_local")
needs_local_builder = _bundle_internal_target(name, "needs_local_builder")
# The source provider and local manifest describe the same owner-only
# document set. Pass both to Sphinx so mounted descendants are neither
# loaded as local sources nor assigned to this bundle's Needs export.
_needs_sphinx_docs(
name = needs_local,
name = needs_local_builder,
bundle = source_bundle,
config = config,
sphinx_build_deps = sphinx_build_deps,
Expand All @@ -496,7 +497,24 @@ def _declare_bundle_local_needs(
score_sourcelinks_json = sourcelinks_json,
score_source_code_linker_plain_links = "1",
mounts_manifest = mounts_manifest,
# Sphinx writes a directory containing several builder outputs. Keep
# that implementation output private; external consumers need only
# the extracted needs.json file below.
visibility = ["//visibility:private"],
)

# A Bazel directory artifact cannot also expose one of its children as a
# separate output of the same action. Keep Sphinx's directory output as an
# intermediate and publish just needs.json through the stable
# needs_local target. External-Needs loading then depends on a file artifact
# instead of reconstructing Sphinx's private output layout.
native.genrule(
name = needs_local,
srcs = [":" + needs_local_builder],
outs = [needs_local + "/needs.json"],
cmd = "cp $(execpath :" + needs_local_builder + ")/needs.json $@",
visibility = visibility,
tags = ["manual"],
)

def docs_bundle(
Expand Down
10 changes: 5 additions & 5 deletions docs/reference/bazel_macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,11 @@ Signature: ``docs_bundle(name, source_dir = None, srcs = [], data = [], entry_do

- ``needs_local`` (internal target)
A source-bearing bundle creates ``<name>.__internal__.needs_local`` with the
Needs declared by its own sources. The standalone build is intentionally
self-contained in this version: references to Needs defined outside the
bundle remain unresolved and fail strict builds. Cross-bundle imports and
merged exports are planned for a later change. Data-only bundles do not
create a Needs target.
``needs.json`` file for Needs declared by its own sources. The standalone
build is intentionally self-contained in this version: references to Needs
defined outside the bundle remain unresolved and fail strict builds.
Cross-bundle imports and merged exports are planned for a later change.
Data-only bundles do not create a Needs target.

.. note::

Expand Down
10 changes: 6 additions & 4 deletions src/helper_lib/external_needs.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ def external_needs_source_path(
) -> Path:
"""Find the inventory JSON emitted by the selected Bazel target.

Public `needs_json` and private bundle-local exports are directory outputs
containing `_build/needs/needs.json`. The `needs_json_file` target points
directly at the inventory file.
The public `needs_json` target is a directory output containing
`_build/needs/needs.json`. The `needs_json_file` target and private
bundle-local exports point directly at JSON file artifacts.
"""
if runfiles_dir is None:
raise ValueError("An external needs source has no runfiles root.")
Expand All @@ -105,7 +105,9 @@ def external_needs_source_path(
elif source.target == "needs_json_file":
suffix = ("needs.json",)
elif source.target.endswith(".__internal__.needs_local"):
suffix = (source.target, "_build", "needs", "needs.json")
# The genrule exposes `<target>/needs.json`. This target-scoped path is
# independent of the private Sphinx builder's output directory layout.
suffix = (source.target, "needs.json")
else:
raise ValueError(f"Unsupported external needs target: {source.target}")
return external_needs_runfiles_path(runfiles_dir, source, *suffix)
12 changes: 6 additions & 6 deletions src/tests/docs_bzl/expected_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,25 @@ class ExpectedTarget:
label=":docs_bundle.__internal__.needs_local",
command="build",
output_kind="file",
output_path="docs_bundle.__internal__.needs_local/_build/needs/needs.json",
output_path="docs_bundle.__internal__.needs_local/needs.json",
),
"root_needs_local": ExpectedTarget(
label=":docs.__internal__.needs_local",
command="build",
output_kind="file",
output_path="docs.__internal__.needs_local/_build/needs/needs.json",
output_path="docs.__internal__.needs_local/needs.json",
),
"data_bundle_needs": ExpectedTarget(
label=":data_bundle.__internal__.needs_local",
command="build",
output_kind="directory",
output_path="data_bundle.__internal__.needs_local/_build/needs",
output_kind="file",
output_path="data_bundle.__internal__.needs_local/needs.json",
),
"isolated_source_bundle_needs": ExpectedTarget(
label=":isolated_source_bundle.__internal__.needs_local",
command="build",
output_kind="directory",
output_path="isolated_source_bundle.__internal__.needs_local/_build/needs",
output_kind="file",
output_path="isolated_source_bundle.__internal__.needs_local/needs.json",
),
"sourcelinks_json": ExpectedTarget(
label=":sourcelinks_json",
Expand Down
2 changes: 1 addition & 1 deletion src/tests/docs_bzl/test_root_docs_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_child_bundle_uses_root_docs_config_without_a_child_conf_py():

needs_json = built_output(
"scenarios/root_docs_config",
"component.__internal__.needs_local/_build/needs/needs.json",
"component.__internal__.needs_local/needs.json",
)
needs = load_needs(needs_json)
metadata = load_needs_json(needs_json)
Expand Down
Loading