From 7d3533d5b0f1b7920f5c2d9c7674152c9d649dfd Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Thu, 3 Sep 2026 17:01:33 +0200 Subject: [PATCH] feat!: drop deprecated scan_code parameter --- bzl/bundle_rules.bzl | 16 ++-- docs.bzl | 56 ++--------- docs/how-to/source_to_doc_links.rst | 4 - docs/reference/bazel_macros.rst | 10 +- src/extensions/docs/source_code_linker.md | 2 - .../scenarios/reference_integration/BUILD | 4 + .../reference_integration/legacy_module/BUILD | 1 + .../docs/components/component/BUILD | 10 +- .../components/component/implementation.py | 19 ++++ .../docs/components/component/index.rst | 8 ++ .../reference_integration/modern_module/BUILD | 1 + .../docs/components/component/BUILD | 10 +- .../components/component/implementation.py | 18 ++++ .../docs/components/component/index.rst | 8 ++ .../docs_bzl/test_reference_integration.py | 95 ++++++++++++++++--- 15 files changed, 175 insertions(+), 87 deletions(-) create mode 100644 src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/implementation.py create mode 100644 src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/implementation.py diff --git a/bzl/bundle_rules.bzl b/bzl/bundle_rules.bzl index 737dc62f0..5fff287b3 100644 --- a/bzl/bundle_rules.bzl +++ b/bzl/bundle_rules.bzl @@ -384,10 +384,12 @@ def _docs_bundle_impl(ctx): child_source_files = [] child_external_runfiles = [] - sourcelinks = [ - struct(file = source_link, repository = ctx.label.workspace_name) - for source_link in ctx.files.sourcelinks - ] + sourcelinks = [] + if ctx.file.sourcelinks_json: + sourcelinks.append(struct( + file = ctx.file.sourcelinks_json, + repository = ctx.label.workspace_name, + )) for index, child in enumerate(ctx.attr.bundles): entries.extend([ _rebase_bundle_entry( @@ -432,7 +434,7 @@ _docs_bundle = rule( attrs = { "source_dir_globbed": attr.label_list(allow_files = True), "source_targets": attr.label_list(allow_files = True), - "sourcelinks": attr.label_list(allow_files = True), + "sourcelinks_json": attr.label(allow_single_file = True), "strip_prefix": attr.string(default = ""), "entry_doc": attr.string(default = "index"), "bundles": attr.label_list(providers = [DocsBundleInfo]), @@ -448,7 +450,7 @@ def create_bundle( bundles, source_dir_globbed = [], source_targets = [], - sourcelinks = [], + sourcelinks_json = None, strip_prefix = "", entry_doc = "index", data = [], @@ -464,7 +466,7 @@ def create_bundle( name = name, source_dir_globbed = source_dir_globbed, source_targets = source_targets, - sourcelinks = sourcelinks, + sourcelinks_json = sourcelinks_json, strip_prefix = strip_prefix, entry_doc = entry_doc, bundles = [bundle.bundle for bundle in parsed_bundles], diff --git a/docs.bzl b/docs.bzl index a863d512b..5c1d34d5c 100644 --- a/docs.bzl +++ b/docs.bzl @@ -74,6 +74,10 @@ def _module_name_without_prefix(): return "" return module_name.split("_", 1)[-1] +def _bundle_internal_target(name, target): + """Return the conventional name for a target internal to a bundle.""" + return name + ".__internal__." + target + def _generated_conf_impl(ctx): output = ctx.actions.declare_file(ctx.attr.output_path) ctx.actions.expand_template( @@ -121,7 +125,6 @@ def _declare_docs_bundle( data = [], entry_doc = "index", bundles = [], - scan_code = [], code_targets = [], visibility = None, **kwargs): @@ -155,8 +158,6 @@ def _declare_docs_bundle( "mount_at": , "attach_to": }. - scan_code: Deprecated. Explicit source files or filegroups to scan for - source-code links. Use `code_targets` for implementation targets. 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. @@ -169,22 +170,15 @@ def _declare_docs_bundle( ("docs_bundle(%s): srcs cannot be combined with source_dir; " + "put generated sources in a dedicated bundle") % name, ) - # Keep directory-discovered sources separate from explicit Bazel targets so # each kind can retain its own runtime path and staging behavior. source_dir_globbed = glob_doc_sources(source_dir) if source_dir != None else [] - sourcelinks = [] - if scan_code: - print("WARNING: docs_bundle(%s) uses deprecated scan_code; use code_targets instead." % name) - sourcelinks_name = name + "_sourcelinks_json" - _sourcelinks_json(name = sourcelinks_name, srcs = scan_code) - sourcelinks = [":" + sourcelinks_name] + sourcelinks_json = None if code_targets: - code_targets_sourcelinks = generate_code_target_sourcelinks( - name = name + "_code_targets_sourcelinks_json", + sourcelinks_json = generate_code_target_sourcelinks( + name = _bundle_internal_target(name, "sourcelinks_json"), code_targets = code_targets, ) - sourcelinks.append(code_targets_sourcelinks) # Store the source directory relative to the workspace so bundle consumers # can locate the original files without copying them. @@ -208,7 +202,7 @@ def _declare_docs_bundle( name = name, source_dir_globbed = source_dir_globbed, source_targets = srcs, - sourcelinks = sourcelinks, + sourcelinks_json = sourcelinks_json, strip_prefix = strip_prefix, entry_doc = entry_doc, bundles = bundles, @@ -224,7 +218,6 @@ def docs_bundle( data = [], entry_doc = "index", bundles = [], - scan_code = [], code_targets = [], visibility = None, **kwargs): @@ -242,7 +235,6 @@ def docs_bundle( data = data, entry_doc = entry_doc, bundles = bundles, - scan_code = scan_code, code_targets = code_targets, visibility = visibility, **kwargs @@ -290,7 +282,6 @@ def docs( data = [], deps = [], external_needs = [], - scan_code = [], code_targets = [], test_sources = [], known_good = None, @@ -312,8 +303,6 @@ def docs( files. deps: Additional dependencies for the documentation build. external_needs: List of external needs targets to include in the documentation build. - scan_code: Deprecated. Explicit source files or filegroups to scan for source - code links. Use `code_targets` for implementation targets. code_targets: Implementation targets or filegroups to scan for source code links. Implementation targets are scanned recursively; filegroups expand to their files. @@ -423,7 +412,6 @@ def docs( data = data, entry_doc = "index", bundles = bundles, - scan_code = scan_code, code_targets = code_targets, visibility = ["//visibility:public"], tags = ["manual"] @@ -598,31 +586,3 @@ def docs( actual = Label("//scripts_bazel:traceability_gate"), tags = ["manual"], ) - -def _sourcelinks_json(name, srcs): - """ - Creates a target that generates a JSON file with source code links. - - See https://eclipse-score.github.io/docs-as-code/main/how-to/source_to_doc_links.html - - Args: - name: Name of the target. - srcs: Source files to scan for traceability tags. - """ - output_file = name + ".json" - - generate_sourcelinks_tool = Label("//scripts_bazel:generate_sourcelinks") - - native.genrule( - name = name, - srcs = srcs, - outs = [output_file], - cmd = """ - $(location {generate_sourcelinks_tool}) \ - --output $@ \ - $(SRCS) - """.format(generate_sourcelinks_tool = generate_sourcelinks_tool), - tools = [generate_sourcelinks_tool], - visibility = ["//visibility:public"], - tags = ["manual"], - ) diff --git a/docs/how-to/source_to_doc_links.rst b/docs/how-to/source_to_doc_links.rst index 70292f780..4ddee27fd 100644 --- a/docs/how-to/source_to_doc_links.rst +++ b/docs/how-to/source_to_doc_links.rst @@ -69,7 +69,3 @@ uses. You may also pass filegroups; their files are scanned directly. source_dir = "docs", code_targets = [":some_application"], ) - -The older ``scan_code`` parameter remains available for existing configurations -that explicitly provide files or filegroups, but it is deprecated. Prefer -``code_targets`` for new configurations. diff --git a/docs/reference/bazel_macros.rst b/docs/reference/bazel_macros.rst index 9459c5c6f..0ae1c0e18 100644 --- a/docs/reference/bazel_macros.rst +++ b/docs/reference/bazel_macros.rst @@ -115,10 +115,6 @@ Minimal example (root ``BUILD``) generated JSON is supplied to ``live_preview`` just like a normal documentation build. -- ``scan_code`` (list of Bazel labels, deprecated) - Explicit source files or filegroups to scan. Use ``code_targets`` for - implementation targets; it follows their dependencies automatically. - - ``external_needs`` (list of bazel labels) External ``:needs_json_file`` targets from other modules/repositories for referencing their needs. @@ -169,7 +165,7 @@ site). visibility = ["//visibility:public"], ) -Signature: ``docs_bundle(name, source_dir = None, srcs = [], data = [], entry_doc = "index", bundles = [], scan_code = [], code_targets = [], visibility = None)``. +Signature: ``docs_bundle(name, source_dir = None, srcs = [], data = [], entry_doc = "index", bundles = [], code_targets = [], visibility = None)``. - ``source_dir`` (string, optional) Directory holding the bundle's own doc sources. It is globbed the same way as @@ -230,10 +226,6 @@ Signature: ``docs_bundle(name, source_dir = None, srcs = [], data = [], entry_do owns one cached scan result; Bazel only regenerates it when its collected source inputs change. -- ``scan_code`` (list of Bazel labels, deprecated) - Explicit source files or filegroups to scan. Prefer ``code_targets`` for - implementation targets. - Edge cases ---------- diff --git a/src/extensions/docs/source_code_linker.md b/src/extensions/docs/source_code_linker.md index 6cf0952c5..b7d5f7eeb 100644 --- a/src/extensions/docs/source_code_linker.md +++ b/src/extensions/docs/source_code_linker.md @@ -46,8 +46,6 @@ The Bazel parts are responsible for producing the **intermediate caches** that t Each `docs_bundle` scans the source files selected by its `code_targets` attribute. A *per bundle JSON cache* is then generated and saved; Bazel reuses it until its source inputs change. -`scan_code` remains available for explicit files or filegroups, but is -deprecated. This script `scripts_bazel/generate_sourcelinks_cli.py` finds all codelinks per file, and gathers them into one JSON cache per repository. It also adds metadata to each needlink that is needed in further steps. diff --git a/src/tests/docs_bzl/scenarios/reference_integration/BUILD b/src/tests/docs_bzl/scenarios/reference_integration/BUILD index f55f9d273..068bf0e6e 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/BUILD +++ b/src/tests/docs_bzl/scenarios/reference_integration/BUILD @@ -16,6 +16,10 @@ load("//:docs.bzl", "docs") # The reference integration combines platform feature requirements and two # software modules into one documentation site, just like the real S-CORE # integration. Each module's bundle contains its component documentation. +# Each component also scans an annotated implementation file through +# ``code_targets``. The integration test verifies that those source links +# survive the component -> module -> site bundle composition and are rendered +# on the mounted component pages. # The component packages live below the module's ``docs`` source directory, # matching the feature-package layout used by the real S-CORE integration. # diff --git a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/BUILD b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/BUILD index 7ab6b55b6..92a53a08c 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/BUILD +++ b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/BUILD @@ -30,6 +30,7 @@ docs( project = "S-CORE Legacy Module", project_url = "https://example.invalid/score-legacy-module", source_dir = "docs", + test_sources = ["src/tests/docs_bzl/scenarios/reference_integration"], data = [ "//src/tests/docs_bzl/scenarios/reference_integration/score_platform:needs_json", "@score_process_description//:needs_json", diff --git a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/BUILD b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/BUILD index 416739582..860a114ad 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/BUILD +++ b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/BUILD @@ -13,10 +13,17 @@ load("//:docs.bzl", "docs") +filegroup( + name = "component_sources", + srcs = ["implementation.py"], +) + # Keep the component as a complete documentation producer while placing its # Bazel package below the module's source directory. This mirrors the feature # package layout used by the S-CORE integration and exercises package-aware -# source ownership at runtime. +# source ownership at runtime. The implementation file is deliberately +# supplied through ``code_targets`` so its traceability annotation exercises +# the source-link generation path used by real component targets. docs( project = "S-CORE Legacy Component", project_url = "https://example.invalid/score-legacy-component", @@ -24,4 +31,5 @@ docs( data = [ "//src/tests/docs_bzl/scenarios/reference_integration/score_platform:needs_json", ], + code_targets = [":component_sources"], ) diff --git a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/implementation.py b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/implementation.py new file mode 100644 index 000000000..11a5b1c6a --- /dev/null +++ b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/implementation.py @@ -0,0 +1,19 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +# req-traceability: tool_req__legacy_component + + +def legacy_component_value(): + """Return a value from the legacy component implementation.""" + return "legacy" diff --git a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/index.rst b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/index.rst index 1016e4fcd..6b1e7f37e 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/index.rst +++ b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/index.rst @@ -17,3 +17,11 @@ S-CORE Legacy Component This component consumes platform feature requirements through the legacy ``data`` API and is mounted by the legacy module. + +.. tool_req:: Legacy component implementation is traceable + :id: tool_req__legacy_component + :version: 1 + + The legacy component implementation is covered by the component source + code-link scan. The integration test checks that this link is preserved + when the component is built alone, by its module, and by the full site. diff --git a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/BUILD b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/BUILD index cec0940f0..630119989 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/BUILD +++ b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/BUILD @@ -21,6 +21,7 @@ docs( project = "S-CORE Modern Module", project_url = "https://example.invalid/score-modern-module", source_dir = "docs", + test_sources = ["src/tests/docs_bzl/scenarios/reference_integration"], external_needs = [ "//src/tests/docs_bzl/scenarios/reference_integration/score_platform:needs_json", "@score_process_description//:needs_json", diff --git a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/BUILD b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/BUILD index 763f5846f..6d61dade9 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/BUILD +++ b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/BUILD @@ -13,9 +13,16 @@ load("//:docs.bzl", "docs") +filegroup( + name = "component_sources", + srcs = ["implementation.py"], +) + # This component is a nested documentation package, like the feature bundles # in the S-CORE integration. Its parent module owns the surrounding source -# tree and mounts this bundle at the component placement. +# tree and mounts this bundle at the component placement. The implementation +# file is supplied through ``code_targets`` to exercise source-link generation +# for a component that is propagated through the module bundle. docs( project = "S-CORE Modern Component", project_url = "https://example.invalid/score-modern-component", @@ -23,4 +30,5 @@ docs( external_needs = [ "//src/tests/docs_bzl/scenarios/reference_integration/score_platform:needs_json", ], + code_targets = [":component_sources"], ) diff --git a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/implementation.py b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/implementation.py new file mode 100644 index 000000000..89b43b3fa --- /dev/null +++ b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/implementation.py @@ -0,0 +1,18 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + + +def modern_component_value(): + """Return a value from the modern component implementation.""" + # req-traceability: tool_req__modern_component + return "modern" diff --git a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/index.rst b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/index.rst index 773536ef4..eea58d5ba 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/index.rst +++ b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/component/index.rst @@ -17,3 +17,11 @@ S-CORE Modern Component This component consumes platform feature requirements through the current ``external_needs`` API and is mounted by the modern module. + +.. tool_req:: Modern component implementation is traceable + :id: tool_req__modern_component + :version: 1 + + The modern component implementation is covered by the component source + code-link scan. The integration test checks that this link is preserved + when the component is built alone, by its module, and by the full site. diff --git a/src/tests/docs_bzl/test_reference_integration.py b/src/tests/docs_bzl/test_reference_integration.py index 7853f6420..dcdea4fc1 100644 --- a/src/tests/docs_bzl/test_reference_integration.py +++ b/src/tests/docs_bzl/test_reference_integration.py @@ -13,9 +13,9 @@ """Reference integration scenario for the public docs() API.""" -import pytest +from typing import cast -from src.tests.docs_bzl.helpers import load_needs, run_scenario +from src.tests.docs_bzl.helpers import built_output, load_needs, run_bazel, run_scenario def test_score_platform_publishes_feature_requirement(): @@ -28,18 +28,59 @@ def test_score_platform_publishes_feature_requirement(): assert "feat_req__platform__feature" in needs, sorted(needs) -@pytest.mark.parametrize( - "scenario", - [ - "reference_integration/legacy_module", - "reference_integration/modern_module", - "reference_integration/legacy_module/docs/components/component", - "reference_integration/modern_module/docs/components/component", - ], -) -def test_module_and_component_needs_targets_build(scenario: str): - """Each module and component can build its needs inventory independently.""" - run_scenario("build", scenario, ":needs_json") +def test_module_and_component_needs_targets_build_with_source_links(): + """Preserve component source links in standalone and composed needs builds. + + The component targets prove that ``code_targets`` creates the expected + links directly. The module targets prove that the links propagate through + a nested module bundle. All four targets are built in one Bazel invocation + because this suite deliberately drives coarse-grained integration cases. + """ + expected_links = [ + ( + "reference_integration/legacy_module", + "tool_req__legacy_component", + "legacy_module/docs/components/component/implementation.py#L14", + ), + ( + "reference_integration/modern_module", + "tool_req__modern_component", + "modern_module/docs/components/component/implementation.py#L17", + ), + ( + "reference_integration/legacy_module/docs/components/component", + "tool_req__legacy_component", + "legacy_module/docs/components/component/implementation.py#L14", + ), + ( + "reference_integration/modern_module/docs/components/component", + "tool_req__modern_component", + "modern_module/docs/components/component/implementation.py#L17", + ), + ] + run_bazel( + [ + "build", + *[ + f"//src/tests/docs_bzl/scenarios/{scenario}:needs_json" + for scenario, _, _ in expected_links + ], + ] + ) + + for scenario, need_id, source_link_fragment in expected_links: + needs = load_needs( + built_output( + f"scenarios/{scenario}", + "needs_json/_build/needs/needs.json", + ) + ) + need = needs.get(need_id) + assert isinstance(need, dict), sorted(needs) + typed_need = cast(dict[str, object], need) + source_code_link = typed_need.get("source_code_link") + assert isinstance(source_code_link, str) + assert source_link_fragment in source_code_link def test_nested_component_package_is_mounted_by_its_module(): @@ -50,13 +91,37 @@ def test_nested_component_package_is_mounted_by_its_module(): def test_reference_integration_builds_with_platform_requirements(): - """The integration mounts modules and their nested component packages.""" + """Mount modules and render their nested component source-code links.""" result = run_scenario("run", "reference_integration", ":docs") html = (result.build_dir / "index.html").read_text(encoding="utf-8") assert ( "score-platform/main/platform/feature.html#feat_req__platform__feature" in html ) + # Source links are rendered on the mounted component pages, not on this + # top-level page, because the needs themselves are owned by each component. + legacy_component_html = ( + result.build_dir + / "modules" + / "legacy_module" + / "components" + / "component" + / "index.html" + ).read_text(encoding="utf-8") + assert "legacy_module/docs/components/component/implementation.py#L14" in ( + legacy_component_html + ) + modern_component_html = ( + result.build_dir + / "modules" + / "modern_module" + / "components" + / "component" + / "index.html" + ).read_text(encoding="utf-8") + assert "modern_module/docs/components/component/implementation.py#L17" in ( + modern_component_html + ) assert (result.build_dir / "modules" / "legacy_module" / "index.html").is_file() assert ( result.build_dir