diff --git a/bazel/rules/rules_score/private/component.bzl b/bazel/rules/rules_score/private/component.bzl index a542a313..1d38e02f 100644 --- a/bazel/rules/rules_score/private/component.bzl +++ b/bazel/rules/rules_score/private/component.bzl @@ -42,11 +42,19 @@ def _component_impl(ctx): # ------------------------------------------------------------------------- # Sphinx Docs: collect RST sources from component_requirements targets - # and bubble up SphinxSourcesInfo from sub-components/units + # and bubble up SphinxSourcesInfo from sub-components/units. + # + # Only ComponentRequirementsInfo-providing targets contribute their own + # rendered RST here. feature_requirements targets are commonly also listed + # in `requirements` (see below) purely so their .lobster file can resolve + # derived_from references from the component requirements; they are + # already rendered once at the dependable_element level, so re-rendering + # them per component would duplicate the feature requirements page across + # every component that references the same feature. # ------------------------------------------------------------------------- req_sphinx_files = [] for req_target in ctx.attr.requirements: - if SphinxSourcesInfo in req_target: + if ComponentRequirementsInfo in req_target and SphinxSourcesInfo in req_target: req_sphinx_files.append(req_target[SphinxSourcesInfo].srcs) component_sphinx_files = [] @@ -275,7 +283,13 @@ def component( Args: name: The name of the component. Used as the target name. requirements: List of labels to component_requirements targets - that define the requirements for this component. + that define the requirements for this component. A + feature_requirements target may also be listed here so its + .lobster file is available to resolve derived_from references + from the component requirements; only component_requirements + targets contribute rendered RST to the component's Sphinx docs, + so listing a feature_requirements target does not duplicate it + in the generated documentation. components: List of labels to nested component targets (for hierarchical component structures). tests: List of labels to Bazel test targets that verify the component diff --git a/bazel/rules/rules_score/test/BUILD b/bazel/rules/rules_score/test/BUILD index ee636cf0..e00a0c6d 100644 --- a/bazel/rules/rules_score/test/BUILD +++ b/bazel/rules/rules_score/test/BUILD @@ -88,6 +88,7 @@ load( ) load( ":unit_component_test.bzl", + "component_excludes_feature_req_docs_test", "component_provider_test", "component_sphinx_sources_test", "unit_component_test_suite", @@ -677,6 +678,11 @@ component_sphinx_sources_test( target_under_test = ":test_component", ) +component_excludes_feature_req_docs_test( + name = "component_excludes_feature_req_docs_test", + target_under_test = ":test_component", +) + # Unit, Component, and Dependable Element test suite unit_component_test_suite(name = "unit_component_tests") diff --git a/bazel/rules/rules_score/test/unit_component_test.bzl b/bazel/rules/rules_score/test/unit_component_test.bzl index 7d7b9181..b51c0141 100644 --- a/bazel/rules/rules_score/test/unit_component_test.bzl +++ b/bazel/rules/rules_score/test/unit_component_test.bzl @@ -148,6 +148,36 @@ def _component_sphinx_sources_test_impl(ctx): component_sphinx_sources_test = analysistest.make(_component_sphinx_sources_test_impl) +def _component_excludes_feature_req_docs_test_impl(ctx): + """Test that a feature_requirements target listed in `requirements` + alongside a component_requirements target is not re-rendered into the + component's own Sphinx docs (SphinxSourcesInfo.srcs), avoiding duplicate + feature requirements pages across every component. It is still expected + in ComponentInfo/tests via feature_requirements' own mechanisms elsewhere + (e.g. the dependable_element level).""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + sphinx_srcs = target_under_test[SphinxSourcesInfo].srcs.to_list() + basenames = [f.basename for f in sphinx_srcs] + + asserts.true( + env, + "feat_req.rst" not in basenames, + "Component SphinxSourcesInfo.srcs should not include the rendered " + + "feature_requirements RST (found: %s)" % basenames, + ) + asserts.true( + env, + "comp_req.rst" in basenames, + "Component SphinxSourcesInfo.srcs should include the rendered " + + "component_requirements RST (found: %s)" % basenames, + ) + + return analysistest.end(env) + +component_excludes_feature_req_docs_test = analysistest.make(_component_excludes_feature_req_docs_test_impl) + # ============================================================================ # Dependable Element Tests # ============================================================================ @@ -171,5 +201,6 @@ def unit_component_test_suite(name): ":unit_sphinx_sources_test", ":component_provider_test", ":component_sphinx_sources_test", + ":component_excludes_feature_req_docs_test", ], )