From f6991e951f39558eb5dcb0c2bd54a1a93591dcd8 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Wed, 21 Jan 2026 16:01:57 +0100 Subject: [PATCH 01/17] Provide //:sourcelinks_json --- BUILD | 19 ++++- docs.bzl | 26 +++++++ scripts/BUILD | 25 +++++++ scripts/generate_sourcelinks_cli.py | 72 +++++++++++++++++++ src/BUILD | 28 ++++++-- src/extensions/score_draw_uml_funcs/BUILD | 10 ++- src/extensions/score_header_service/BUILD | 12 ++-- src/extensions/score_layout/BUILD | 11 +-- src/extensions/score_metamodel/BUILD | 35 ++++++--- src/extensions/score_source_code_linker/BUILD | 48 ++++++------- src/extensions/score_sphinx_bundle/BUILD | 8 ++- src/extensions/score_sync_toml/BUILD | 12 ++-- src/find_runfiles/BUILD | 8 ++- src/helper_lib/BUILD | 11 +-- 14 files changed, 265 insertions(+), 60 deletions(-) create mode 100644 scripts/BUILD create mode 100644 scripts/generate_sourcelinks_cli.py diff --git a/BUILD b/BUILD index 21a92e427..4dc359350 100644 --- a/BUILD +++ b/BUILD @@ -13,7 +13,7 @@ load("@aspect_rules_py//py:defs.bzl", "py_library") load("@score_tooling//:defs.bzl", "cli_helper", "copyright_checker") -load("//:docs.bzl", "docs") +load("//:docs.bzl", "docs", "sourcelinks_json") package(default_visibility = ["//visibility:public"]) @@ -29,6 +29,23 @@ copyright_checker( visibility = ["//visibility:public"], ) +sourcelinks_json( + name = "sourcelinks_json", + srcs = [ + "//src:all_sources", + "//src/extensions/score_draw_uml_funcs:all_sources", + "//src/extensions/score_header_service:all_sources", + "//src/extensions/score_layout:all_sources", + "//src/extensions/score_metamodel:all_sources", + "//src/extensions/score_source_code_linker:all_sources", + "//src/extensions/score_sphinx_bundle:all_sources", + "//src/extensions/score_sync_toml:all_sources", + "//src/helper_lib:all_sources", + "//src/find_runfiles:all_sources", + ], + visibility = ["//visibility:public"], +) + docs( data = [ "@score_process//:needs_json", diff --git a/docs.bzl b/docs.bzl index 00f1c676c..91c66ef66 100644 --- a/docs.bzl +++ b/docs.bzl @@ -193,3 +193,29 @@ def docs(source_dir = "docs", data = [], deps = []): tools = data, visibility = ["//visibility:public"], ) + +def sourcelinks_json(name, srcs, visibility = None): + """ + 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 + visibility: Visibility of the target + """ + output_file = name + ".json" + + native.genrule( + name = name, + srcs = srcs, + outs = [output_file], + cmd = """ + $(location //scripts:generate_sourcelinks) \ + --output $@ \ + $(SRCS) + """, + tools = ["//scripts:generate_sourcelinks"], + visibility = visibility, + ) diff --git a/scripts/BUILD b/scripts/BUILD new file mode 100644 index 000000000..7acc067ef --- /dev/null +++ b/scripts/BUILD @@ -0,0 +1,25 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +load("@aspect_rules_py//py:defs.bzl", "py_binary") +load("@pip_process//:requirements.bzl", "all_requirements") + +py_binary( + name = "generate_sourcelinks", + srcs = ["generate_sourcelinks_cli.py"], + main = "generate_sourcelinks_cli.py", + visibility = ["//visibility:public"], + deps = [ + "//src/extensions/score_source_code_linker", + ] + all_requirements, +) diff --git a/scripts/generate_sourcelinks_cli.py b/scripts/generate_sourcelinks_cli.py new file mode 100644 index 000000000..a70b878a0 --- /dev/null +++ b/scripts/generate_sourcelinks_cli.py @@ -0,0 +1,72 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +""" +CLI tool to generate source code links JSON from source files. +This is used by the Bazel sourcelinks_json rule to create a JSON file +with all source code links for documentation needs. +""" + +import argparse +import logging +import sys +from pathlib import Path + +from src.extensions.score_source_code_linker.generate_source_code_links_json import ( + _extract_references_from_file, +) +from src.extensions.score_source_code_linker.needlinks import ( + store_source_code_links_json, +) + +logging.basicConfig(level=logging.INFO, format="%(message)s") +logger = logging.getLogger(__name__) + + +def main(): + parser = argparse.ArgumentParser( + description="Generate source code links JSON from source files" + ) + parser.add_argument( + "--output", + required=True, + type=Path, + help="Output JSON file path", + ) + parser.add_argument( + "files", + nargs="*", + type=Path, + help="Source files to scan for traceability tags", + ) + + args = parser.parse_args() + + all_need_references = [] + for file_path in args.files: + abs_file_path = file_path.resolve() + if abs_file_path.exists(): + references = _extract_references_from_file( + abs_file_path.parent, Path(abs_file_path.name) + ) + all_need_references.extend(references) + + store_source_code_links_json(args.output, all_need_references) + logger.info( + f"Found {len(all_need_references)} need references in {len(args.files)} files" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/BUILD b/src/BUILD index ddf506de1..fdcde729a 100644 --- a/src/BUILD +++ b/src/BUILD @@ -10,14 +10,11 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* + load("@aspect_rules_lint//format:defs.bzl", "format_multirun", "format_test") -load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_library") -load("@pip_process//:requirements.bzl", "all_requirements", "requirement") -load("@rules_pkg//pkg:mappings.bzl", "pkg_files") -load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load("@aspect_rules_py//py:defs.bzl", "py_library") load("@rules_python//python:pip.bzl", "compile_pip_requirements") -load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_build_binary") -load("@score_tooling//:defs.bzl", "dash_license_checker", "score_virtualenv") +load("@score_tooling//:defs.bzl", "dash_license_checker") # These are only exported because they're passed as files to the //docs.bzl # macros, and thus must be visible to other packages. They should only be @@ -28,6 +25,25 @@ exports_files( "requirements.txt", "incremental.py", "dummy.py", + "generate_sourcelinks_cli.py", + ], + visibility = ["//visibility:public"], +) + +filegroup( + name = "all_sources", + srcs = glob( + ["*.py"] + ) + [ + "//src/extensions/score_draw_uml_funcs:all_sources", + "//src/extensions/score_header_service:all_sources", + "//src/extensions/score_layout:all_sources", + "//src/extensions/score_metamodel:all_sources", + "//src/extensions/score_source_code_linker:all_sources", + "//src/extensions/score_sphinx_bundle:all_sources", + "//src/extensions/score_sync_toml:all_sources", + "//src/helper_lib:all_sources", + "//src/find_runfiles:all_sources", ], visibility = ["//visibility:public"], ) diff --git a/src/extensions/score_draw_uml_funcs/BUILD b/src/extensions/score_draw_uml_funcs/BUILD index 21d8622e4..b16000a61 100644 --- a/src/extensions/score_draw_uml_funcs/BUILD +++ b/src/extensions/score_draw_uml_funcs/BUILD @@ -13,11 +13,15 @@ load("@aspect_rules_py//py:defs.bzl", "py_library") load("@pip_process//:requirements.bzl", "all_requirements") +filegroup( + name = "all_sources", + srcs = glob(["*.py"]), + visibility = ["//visibility:public"], +) + py_library( name = "score_draw_uml_funcs", - srcs = glob( - ["*.py"], - ), + srcs = [":all_sources"], imports = ["."], visibility = ["//visibility:public"], # TODO: Figure out if all requirements are needed or if we can break it down a bit diff --git a/src/extensions/score_header_service/BUILD b/src/extensions/score_header_service/BUILD index d7a7b65cc..481858112 100644 --- a/src/extensions/score_header_service/BUILD +++ b/src/extensions/score_header_service/BUILD @@ -10,16 +10,20 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* + load("@aspect_rules_py//py:defs.bzl", "py_library") load("@pip_process//:requirements.bzl", "all_requirements") load("@score_tooling//:defs.bzl", "score_py_pytest") +filegroup( + name = "all_sources", + srcs = glob(["*.py"]), + visibility = ["//visibility:public"], +) + py_library( name = "score_header_service", - srcs = glob( - ["*.py"], - exclude = ["test/**"], - ), + srcs = [":all_sources"], imports = ["."], visibility = ["//visibility:public"], # TODO: Figure out if all requirements are needed or if we can break it down a bit diff --git a/src/extensions/score_layout/BUILD b/src/extensions/score_layout/BUILD index d6d472746..03bbe1122 100644 --- a/src/extensions/score_layout/BUILD +++ b/src/extensions/score_layout/BUILD @@ -13,17 +13,20 @@ load("@aspect_rules_py//py:defs.bzl", "py_library") load("@pip_process//:requirements.bzl", "requirement") +filegroup( + name = "all_sources", + srcs = glob(["*.py", "assets/**/*"]), + visibility = ["//visibility:public"], +) + py_library( name = "score_layout", - srcs = glob([ - "*.py", + srcs = [":all_sources"], # Adding assets as src instead of data ensures they are included in the # library as they would normally be, and we do not need to go through bazel's # RUNFILES_DIR mechanism to access them. This makes the code much simpler. # And it makes the library far easier extractable from bazel into a normal # python package if we ever want to do that. - "assets/**", - ]), imports = ["."], visibility = ["//visibility:public"], deps = [requirement("sphinx")], diff --git a/src/extensions/score_metamodel/BUILD b/src/extensions/score_metamodel/BUILD index 40cb645f4..40f1862f7 100644 --- a/src/extensions/score_metamodel/BUILD +++ b/src/extensions/score_metamodel/BUILD @@ -10,20 +10,39 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* + load("@aspect_rules_py//py:defs.bzl", "py_library") load("@pip_process//:requirements.bzl", "all_requirements") load("@score_tooling//:defs.bzl", "score_py_pytest") -py_library( - name = "score_metamodel", +filegroup( + name = "sources", + srcs = glob( + ["*.py", "*.yaml", "*.json", "checks/*.py"] + ), +) + +filegroup( + name = "tests", srcs = glob( - ["**/*.py"], - ) + ["metamodel.yaml"], - data = glob(["*.yaml"]), # Needed to remove 'resolving of symlink' in score_metamodel.__init__ - imports = [ - ".", + ["tests/**/*"] + ), +) + +filegroup( + name = "all_sources", + srcs = [ + ":sources", + ":tests", ], visibility = ["//visibility:public"], +) + +py_library( + name = "score_metamodel", + srcs = [":sources"], + imports = ["."], + visibility = ["//visibility:public"], # 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/helper_lib"], ) @@ -31,7 +50,7 @@ py_library( score_py_pytest( name = "score_metamodel_tests", size = "small", - srcs = glob(["tests/*.py"]), + srcs = [":tests"], # All requirements already in the library so no need to have it double data = glob( [ diff --git a/src/extensions/score_source_code_linker/BUILD b/src/extensions/score_source_code_linker/BUILD index 7a662df3b..ac18574b6 100644 --- a/src/extensions/score_source_code_linker/BUILD +++ b/src/extensions/score_source_code_linker/BUILD @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -10,28 +10,32 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -#******************************************************************************* -# Copyright (c) 2025 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 -# ******************************************************************************* -load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_library") -load("@pip_process//:requirements.bzl", "all_requirements") + +load("@aspect_rules_py//py:defs.bzl", "py_library") load("@score_tooling//:defs.bzl", "score_py_pytest") -py_library( - name = "score_source_code_linker", +filegroup( + name = "sources", + srcs = glob( + ["*.py"] + ), +) +filegroup( + name = "tests", srcs = glob( - ["**/*.py"], - exclude = ["tests/*.py"], + ["tests/*.py", "tests/*.json"] ), +) + +filegroup( + name = "all_sources", + srcs = [":sources", ":tests"], + visibility = ["//visibility:public"], +) + +py_library( + name = "score_source_code_linker", + srcs = [":all_sources"], imports = ["."], visibility = ["//visibility:public"], deps = ["@score_docs_as_code//src/helper_lib"], @@ -39,11 +43,7 @@ py_library( py_library( name = "source_code_linker_helpers", - srcs = [ - "needlinks.py", - "testlink.py", - "xml_parser.py", - ], + srcs = [":all_sources"], imports = ["."], visibility = ["//visibility:public"], ) diff --git a/src/extensions/score_sphinx_bundle/BUILD b/src/extensions/score_sphinx_bundle/BUILD index 10aa50a31..9145b5ab2 100644 --- a/src/extensions/score_sphinx_bundle/BUILD +++ b/src/extensions/score_sphinx_bundle/BUILD @@ -13,9 +13,15 @@ load("@aspect_rules_py//py:defs.bzl", "py_library") load("@pip_process//:requirements.bzl", "all_requirements") +filegroup( + name = "all_sources", + srcs = glob(["*.py"]), + visibility = ["//visibility:public"], +) + py_library( name = "score_sphinx_bundle", - srcs = ["__init__.py"], + srcs = [":all_sources"], visibility = ["//visibility:public"], deps = all_requirements + [ "@score_docs_as_code//src/extensions:score_plantuml", diff --git a/src/extensions/score_sync_toml/BUILD b/src/extensions/score_sync_toml/BUILD index e9be69268..7b41f3b5d 100644 --- a/src/extensions/score_sync_toml/BUILD +++ b/src/extensions/score_sync_toml/BUILD @@ -10,15 +10,19 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* + load("@aspect_rules_py//py:defs.bzl", "py_library") load("@pip_process//:requirements.bzl", "requirement") +filegroup( + name = "all_sources", + srcs = glob(["*.py", "*.toml"]), + visibility = ["//visibility:public"], +) + py_library( name = "score_sync_toml", - srcs = [ - "__init__.py", - "shared.toml", - ], + srcs = [":all_sources"], imports = ["."], visibility = ["//visibility:public"], deps = [ diff --git a/src/find_runfiles/BUILD b/src/find_runfiles/BUILD index a286c57f2..2112815c4 100644 --- a/src/find_runfiles/BUILD +++ b/src/find_runfiles/BUILD @@ -14,9 +14,15 @@ load("@aspect_rules_py//py:defs.bzl", "py_library") load("@pip_process//:requirements.bzl", "all_requirements") load("@score_tooling//:defs.bzl", "score_py_pytest") +filegroup( + name = "all_sources", + srcs = glob(["*.py"]), + visibility = ["//visibility:public"], +) + py_library( name = "find_runfiles", - srcs = ["__init__.py"], + srcs = [":all_sources"], imports = ["."], visibility = ["//visibility:public"], ) diff --git a/src/helper_lib/BUILD b/src/helper_lib/BUILD index 61d941754..c2c3161f5 100644 --- a/src/helper_lib/BUILD +++ b/src/helper_lib/BUILD @@ -14,12 +14,15 @@ load("@aspect_rules_py//py:defs.bzl", "py_library") load("@pip_process//:requirements.bzl", "all_requirements") load("@score_tooling//:defs.bzl", "score_py_pytest") +filegroup( + name = "all_sources", + srcs = glob(["*.py"]), + visibility = ["//visibility:public"], +) + py_library( name = "helper_lib", - srcs = [ - "__init__.py", - "additional_functions.py", - ], + srcs = [":all_sources"], imports = ["."], visibility = ["//visibility:public"], deps = ["@score_docs_as_code//src/extensions/score_source_code_linker:source_code_linker_helpers"], From 61dad86f21bb7136978dfa73e2ebe4c16e4a2545 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Thu, 22 Jan 2026 10:38:15 +0100 Subject: [PATCH 02/17] Funnel sourcelinks into Sphinx extension Currently, we are not using it yet, but this commit adds the Bazel scaffolding to make the json data available. --- BUILD | 1 + docs.bzl | 47 ++++++++++++-- scripts/BUILD | 7 ++ scripts/merge_sourcelinks.py | 64 +++++++++++++++++++ .../score_source_code_linker/__init__.py | 5 ++ 5 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 scripts/merge_sourcelinks.py diff --git a/BUILD b/BUILD index 4dc359350..a902c541d 100644 --- a/BUILD +++ b/BUILD @@ -51,6 +51,7 @@ docs( "@score_process//:needs_json", ], source_dir = "docs", + sourcelinks = [":sourcelinks_json"], ) cli_helper( diff --git a/docs.bzl b/docs.bzl index 91c66ef66..cef918e54 100644 --- a/docs.bzl +++ b/docs.bzl @@ -56,12 +56,40 @@ def _rewrite_needs_json_to_docs_sources(labels): out.append(s) return out -def docs(source_dir = "docs", data = [], deps = []): +def _merge_sourcelinks(name, sourcelinks): + """Merge multiple sourcelinks JSON files into a single file. + + Args: + name: Name for the merged sourcelinks target + sourcelinks: List of sourcelinks JSON file targets """ - Creates all targets related to documentation. + + native.genrule( + name = name, + srcs = sourcelinks, + outs = [name + ".json"], + cmd = """ + $(location //scripts:merge_sourcelinks) \ + --output $@ \ + $(SRCS) + """, + tools = ["//scripts:merge_sourcelinks"], + ) + +def docs(source_dir = "docs", data = [], deps = [], sourcelinks = []): + """Creates all targets related to documentation. + By using this function, you'll get any and all updates for documentation targets in one place. + + Args: + source_dir: The source directory containing documentation files. Defaults to "docs". + data: Additional data files to include in the documentation build. + deps: Additional dependencies for the documentation build. + sourcelinks: Source code links configuration for traceability. """ + _merge_sourcelinks(name="merged_sourcelinks", sourcelinks=sourcelinks) + call_path = native.package_name() if call_path != "": @@ -106,12 +134,13 @@ def docs(source_dir = "docs", data = [], deps = []): name = "docs", tags = ["cli_help=Build documentation:\nbazel run //:docs"], srcs = ["@score_docs_as_code//src:incremental.py"], - data = data, + data = data + [":merged_sourcelinks"], deps = deps, env = { "SOURCE_DIRECTORY": source_dir, "DATA": str(data), "ACTION": "incremental", + "SCORE_SOURCELINKS": "$(location :merged_sourcelinks)", }, ) @@ -119,12 +148,13 @@ def docs(source_dir = "docs", data = [], deps = []): name = "docs_combo_experimental", tags = ["cli_help=Build full documentation with all dependencies:\nbazel run //:docs_combo_experimental"], srcs = ["@score_docs_as_code//src:incremental.py"], - data = data_with_docs_sources, + data = data_with_docs_sources + [":merged_sourcelinks"], deps = deps, env = { "SOURCE_DIRECTORY": source_dir, "DATA": str(data_with_docs_sources), "ACTION": "incremental", + "SCORE_SOURCELINKS": "$(location :merged_sourcelinks)", }, ) @@ -132,12 +162,13 @@ def docs(source_dir = "docs", data = [], deps = []): name = "docs_check", tags = ["cli_help=Verify documentation:\nbazel run //:docs_check"], srcs = ["@score_docs_as_code//src:incremental.py"], - data = data, + data = data + [":merged_sourcelinks"], deps = deps, env = { "SOURCE_DIRECTORY": source_dir, "DATA": str(data), "ACTION": "check", + "SCORE_SOURCELINKS": "$(location :merged_sourcelinks)", }, ) @@ -145,12 +176,13 @@ def docs(source_dir = "docs", data = [], deps = []): name = "live_preview", tags = ["cli_help=Live preview documentation in the browser:\nbazel run //:live_preview"], srcs = ["@score_docs_as_code//src:incremental.py"], - data = data, + data = data + [":merged_sourcelinks"], deps = deps, env = { "SOURCE_DIRECTORY": source_dir, "DATA": str(data), "ACTION": "live_preview", + "SCORE_SOURCELINKS": "$(location :merged_sourcelinks)", }, ) @@ -158,12 +190,13 @@ def docs(source_dir = "docs", data = [], deps = []): name = "live_preview_combo_experimental", tags = ["cli_help=Live preview full documentation with all dependencies in the browser:\nbazel run //:live_preview_combo_experimental"], srcs = ["@score_docs_as_code//src:incremental.py"], - data = data_with_docs_sources, + data = data_with_docs_sources + [":merged_sourcelinks"], deps = deps, env = { "SOURCE_DIRECTORY": source_dir, "DATA": str(data_with_docs_sources), "ACTION": "live_preview", + "SCORE_SOURCELINKS": "$(location :merged_sourcelinks)", }, ) diff --git a/scripts/BUILD b/scripts/BUILD index 7acc067ef..ab9f0a9f6 100644 --- a/scripts/BUILD +++ b/scripts/BUILD @@ -23,3 +23,10 @@ py_binary( "//src/extensions/score_source_code_linker", ] + all_requirements, ) + +py_binary( + name = "merge_sourcelinks", + srcs = ["merge_sourcelinks.py"], + main = "merge_sourcelinks.py", + visibility = ["//visibility:public"], +) diff --git a/scripts/merge_sourcelinks.py b/scripts/merge_sourcelinks.py new file mode 100644 index 000000000..15e6e3b9c --- /dev/null +++ b/scripts/merge_sourcelinks.py @@ -0,0 +1,64 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Merge multiple sourcelinks JSON files into a single JSON file. +""" + +import argparse +import json +import logging +import sys +from pathlib import Path + +logging.basicConfig(level=logging.INFO, format="%(message)s") +logger = logging.getLogger(__name__) + + +def main(): + parser = argparse.ArgumentParser( + description="Merge multiple sourcelinks JSON files into one" + ) + parser.add_argument( + "--output", + required=True, + type=Path, + help="Output merged JSON file path", + ) + parser.add_argument( + "files", + nargs="*", + type=Path, + help="Input JSON files to merge", + ) + + args = parser.parse_args() + + merged = [] + for json_file in args.files: + if json_file.exists(): + with open(json_file, "r") as f: + data = json.load(f) + if isinstance(data, list): + merged.extend(data) + + args.output.parent.mkdir(parents=True, exist_ok=True) + with open(args.output, "w") as f: + json.dump(merged, f, indent=2, ensure_ascii=False) + + logger.info(f"Merged {len(args.files)} files into {len(merged)} total references") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/extensions/score_source_code_linker/__init__.py b/src/extensions/score_source_code_linker/__init__.py index 876e4fccc..39adb484f 100644 --- a/src/extensions/score_source_code_linker/__init__.py +++ b/src/extensions/score_source_code_linker/__init__.py @@ -20,6 +20,7 @@ # req-Id: tool_req__docs_dd_link_source_code_link # This whole directory implements the above mentioned tool requirements +import os from collections import defaultdict from copy import deepcopy from pathlib import Path @@ -175,6 +176,10 @@ def setup_source_code_linker(app: Sphinx, ws_root: Path): app.outdir, "score_source_code_linker_cache.json" ) + score_sourcelinks_json = os.environ.get("SCORE_SOURCELINKS") + if score_sourcelinks_json: + print(f"TODO: Use {score_sourcelinks_json} for source code linker") + if ( not scl_cache_json.exists() or not app.config.skip_rescanning_via_source_code_linker From d31a8fc8a11a98a96c1acbb553bb82976f58f829 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Thu, 22 Jan 2026 13:16:53 +0100 Subject: [PATCH 03/17] Read source links json when available --- .../score_source_code_linker/__init__.py | 21 +++++++++++++------ .../score_source_code_linker/needlinks.py | 7 +++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/extensions/score_source_code_linker/__init__.py b/src/extensions/score_source_code_linker/__init__.py index 39adb484f..ed6548079 100644 --- a/src/extensions/score_source_code_linker/__init__.py +++ b/src/extensions/score_source_code_linker/__init__.py @@ -130,9 +130,13 @@ def build_and_save_combined_file(outdir: Path): Reads the saved partial caches of codelink & testlink Builds the combined JSON cache & saves it """ - source_code_links = load_source_code_links_json( - get_cache_filename(outdir, "score_source_code_linker_cache.json") - ) + source_code_links_json = os.environ.get("SCORE_SOURCELINKS") + if not source_code_links_json: + source_code_links_json = get_cache_filename(outdir, "score_source_code_linker_cache.json") + else: + source_code_links_json = Path(source_code_links_json) + + source_code_links = load_source_code_links_json(source_code_links_json) test_code_links = load_test_xml_parsed_json( get_cache_filename(outdir, "score_xml_parser_cache.json") ) @@ -172,13 +176,16 @@ def setup_source_code_linker(app: Sphinx, ws_root: Path): }, } + score_sourcelinks_json = os.environ.get("SCORE_SOURCELINKS") + if score_sourcelinks_json: + # No need to generate the JSON file if this env var is set + # because it points to an existing file with the needed data. + return + scl_cache_json = get_cache_filename( app.outdir, "score_source_code_linker_cache.json" ) - score_sourcelinks_json = os.environ.get("SCORE_SOURCELINKS") - if score_sourcelinks_json: - print(f"TODO: Use {score_sourcelinks_json} for source code linker") if ( not scl_cache_json.exists() @@ -200,6 +207,7 @@ def register_test_code_linker(app: Sphinx): def setup_test_code_linker(app: Sphinx, env: BuildEnvironment): + # TODO instead of implementing our own caching here, we should rely on Bazel tl_cache_json = get_cache_filename(app.outdir, "score_xml_parser_cache.json") if ( not tl_cache_json.exists() @@ -249,6 +257,7 @@ def register_combined_linker(app: Sphinx): def setup_combined_linker(app: Sphinx, _: BuildEnvironment): grouped_cache = get_cache_filename(app.outdir, "score_scl_grouped_cache.json") gruped_cache_exists = grouped_cache.exists() + # TODO this cache should be done via Bazel if not gruped_cache_exists or not app.config.skip_rescanning_via_source_code_linker: LOGGER.debug( "Did not find combined json 'score_scl_grouped_cache.json' in _build." diff --git a/src/extensions/score_source_code_linker/needlinks.py b/src/extensions/score_source_code_linker/needlinks.py index c890b13e4..348147292 100644 --- a/src/extensions/score_source_code_linker/needlinks.py +++ b/src/extensions/score_source_code_linker/needlinks.py @@ -13,6 +13,7 @@ # req-Id: tool_req__docs_dd_link_source_code_link import json +import os from dataclasses import asdict, dataclass from pathlib import Path from typing import Any @@ -80,6 +81,12 @@ def store_source_code_links_json(file: Path, needlist: list[NeedLink]): def load_source_code_links_json(file: Path) -> list[NeedLink]: + if not file.is_absolute(): + # use env variable set by Bazel + ws_root = os.environ.get("BUILD_WORKSPACE_DIRECTORY") + if ws_root: + file = Path(ws_root) / file + links: list[NeedLink] = json.loads( file.read_text(encoding="utf-8"), object_hook=needlink_decoder, From ac852e56c4b73463da7a579adc9c06af5a14e778 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Thu, 22 Jan 2026 13:28:41 +0100 Subject: [PATCH 04/17] Fix CI checks --- BUILD | 2 +- docs.bzl | 2 +- scripts/merge_sourcelinks.py | 2 +- src/BUILD | 4 ++-- src/extensions/score_layout/BUILD | 15 +++++++++------ src/extensions/score_metamodel/BUILD | 9 +++++++-- src/extensions/score_source_code_linker/BUILD | 15 +++++++++++---- .../score_source_code_linker/__init__.py | 5 +++-- src/extensions/score_sync_toml/BUILD | 5 ++++- 9 files changed, 39 insertions(+), 20 deletions(-) diff --git a/BUILD b/BUILD index a902c541d..adbbd688a 100644 --- a/BUILD +++ b/BUILD @@ -40,8 +40,8 @@ sourcelinks_json( "//src/extensions/score_source_code_linker:all_sources", "//src/extensions/score_sphinx_bundle:all_sources", "//src/extensions/score_sync_toml:all_sources", - "//src/helper_lib:all_sources", "//src/find_runfiles:all_sources", + "//src/helper_lib:all_sources", ], visibility = ["//visibility:public"], ) diff --git a/docs.bzl b/docs.bzl index cef918e54..e65e7d00f 100644 --- a/docs.bzl +++ b/docs.bzl @@ -88,7 +88,7 @@ def docs(source_dir = "docs", data = [], deps = [], sourcelinks = []): sourcelinks: Source code links configuration for traceability. """ - _merge_sourcelinks(name="merged_sourcelinks", sourcelinks=sourcelinks) + _merge_sourcelinks(name = "merged_sourcelinks", sourcelinks = sourcelinks) call_path = native.package_name() diff --git a/scripts/merge_sourcelinks.py b/scripts/merge_sourcelinks.py index 15e6e3b9c..2d9141f22 100644 --- a/scripts/merge_sourcelinks.py +++ b/scripts/merge_sourcelinks.py @@ -47,7 +47,7 @@ def main(): merged = [] for json_file in args.files: if json_file.exists(): - with open(json_file, "r") as f: + with open(json_file) as f: data = json.load(f) if isinstance(data, list): merged.extend(data) diff --git a/src/BUILD b/src/BUILD index fdcde729a..f45f14fd7 100644 --- a/src/BUILD +++ b/src/BUILD @@ -33,7 +33,7 @@ exports_files( filegroup( name = "all_sources", srcs = glob( - ["*.py"] + ["*.py"], ) + [ "//src/extensions/score_draw_uml_funcs:all_sources", "//src/extensions/score_header_service:all_sources", @@ -42,8 +42,8 @@ filegroup( "//src/extensions/score_source_code_linker:all_sources", "//src/extensions/score_sphinx_bundle:all_sources", "//src/extensions/score_sync_toml:all_sources", - "//src/helper_lib:all_sources", "//src/find_runfiles:all_sources", + "//src/helper_lib:all_sources", ], visibility = ["//visibility:public"], ) diff --git a/src/extensions/score_layout/BUILD b/src/extensions/score_layout/BUILD index 03bbe1122..8e21188de 100644 --- a/src/extensions/score_layout/BUILD +++ b/src/extensions/score_layout/BUILD @@ -15,18 +15,21 @@ load("@pip_process//:requirements.bzl", "requirement") filegroup( name = "all_sources", - srcs = glob(["*.py", "assets/**/*"]), + srcs = glob([ + "*.py", + "assets/**/*", + ]), visibility = ["//visibility:public"], ) py_library( name = "score_layout", srcs = [":all_sources"], - # Adding assets as src instead of data ensures they are included in the - # library as they would normally be, and we do not need to go through bazel's - # RUNFILES_DIR mechanism to access them. This makes the code much simpler. - # And it makes the library far easier extractable from bazel into a normal - # python package if we ever want to do that. + # Adding assets as src instead of data ensures they are included in the + # library as they would normally be, and we do not need to go through bazel's + # RUNFILES_DIR mechanism to access them. This makes the code much simpler. + # And it makes the library far easier extractable from bazel into a normal + # python package if we ever want to do that. imports = ["."], visibility = ["//visibility:public"], deps = [requirement("sphinx")], diff --git a/src/extensions/score_metamodel/BUILD b/src/extensions/score_metamodel/BUILD index 40f1862f7..ea76fdcc8 100644 --- a/src/extensions/score_metamodel/BUILD +++ b/src/extensions/score_metamodel/BUILD @@ -18,14 +18,19 @@ load("@score_tooling//:defs.bzl", "score_py_pytest") filegroup( name = "sources", srcs = glob( - ["*.py", "*.yaml", "*.json", "checks/*.py"] + [ + "*.py", + "*.yaml", + "*.json", + "checks/*.py", + ], ), ) filegroup( name = "tests", srcs = glob( - ["tests/**/*"] + ["tests/**/*"], ), ) diff --git a/src/extensions/score_source_code_linker/BUILD b/src/extensions/score_source_code_linker/BUILD index ac18574b6..ab9b64a54 100644 --- a/src/extensions/score_source_code_linker/BUILD +++ b/src/extensions/score_source_code_linker/BUILD @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation +# Copyright (c) 2025 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -17,19 +17,26 @@ load("@score_tooling//:defs.bzl", "score_py_pytest") filegroup( name = "sources", srcs = glob( - ["*.py"] + ["*.py"], ), ) + filegroup( name = "tests", srcs = glob( - ["tests/*.py", "tests/*.json"] + [ + "tests/*.py", + "tests/*.json", + ], ), ) filegroup( name = "all_sources", - srcs = [":sources", ":tests"], + srcs = [ + ":sources", + ":tests", + ], visibility = ["//visibility:public"], ) diff --git a/src/extensions/score_source_code_linker/__init__.py b/src/extensions/score_source_code_linker/__init__.py index ed6548079..c9e57b97d 100644 --- a/src/extensions/score_source_code_linker/__init__.py +++ b/src/extensions/score_source_code_linker/__init__.py @@ -132,7 +132,9 @@ def build_and_save_combined_file(outdir: Path): """ source_code_links_json = os.environ.get("SCORE_SOURCELINKS") if not source_code_links_json: - source_code_links_json = get_cache_filename(outdir, "score_source_code_linker_cache.json") + source_code_links_json = get_cache_filename( + outdir, "score_source_code_linker_cache.json" + ) else: source_code_links_json = Path(source_code_links_json) @@ -186,7 +188,6 @@ def setup_source_code_linker(app: Sphinx, ws_root: Path): app.outdir, "score_source_code_linker_cache.json" ) - if ( not scl_cache_json.exists() or not app.config.skip_rescanning_via_source_code_linker diff --git a/src/extensions/score_sync_toml/BUILD b/src/extensions/score_sync_toml/BUILD index 7b41f3b5d..fdb8acb35 100644 --- a/src/extensions/score_sync_toml/BUILD +++ b/src/extensions/score_sync_toml/BUILD @@ -16,7 +16,10 @@ load("@pip_process//:requirements.bzl", "requirement") filegroup( name = "all_sources", - srcs = glob(["*.py", "*.toml"]), + srcs = glob([ + "*.py", + "*.toml", + ]), visibility = ["//visibility:public"], ) From 69991f3bd06ca19c3e3922def452acb7289afcfb Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Thu, 22 Jan 2026 17:04:13 +0100 Subject: [PATCH 05/17] Fix tests --- MODULE.bazel | 2 +- src/extensions/score_metamodel/BUILD | 6 +++--- src/extensions/score_source_code_linker/BUILD | 14 +++++++++----- .../tests/test_codelink.py | 14 ++++++++++++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index c7f9c56aa..33c76fc9c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -104,7 +104,7 @@ bazel_dep(name = "score_process", version = "1.4.2") # Add Linter bazel_dep(name = "rules_multitool", version = "1.9.0") -bazel_dep(name = "score_tooling", version = "1.0.2") +bazel_dep(name = "score_tooling", version = "1.0.5") multitool_root = use_extension("@rules_multitool//multitool:extension.bzl", "multitool") use_repo(multitool_root, "actionlint_hub", "multitool", "ruff_hub", "shellcheck_hub", "yamlfmt_hub") diff --git a/src/extensions/score_metamodel/BUILD b/src/extensions/score_metamodel/BUILD index ea76fdcc8..cc0dd2484 100644 --- a/src/extensions/score_metamodel/BUILD +++ b/src/extensions/score_metamodel/BUILD @@ -30,7 +30,7 @@ filegroup( filegroup( name = "tests", srcs = glob( - ["tests/**/*"], + ["tests/*.py"], ), ) @@ -55,13 +55,13 @@ py_library( score_py_pytest( name = "score_metamodel_tests", size = "small", - srcs = [":tests"], + srcs = glob(["tests/*.py"]), # All requirements already in the library so no need to have it double data = glob( [ "tests/**/*.rst", "tests/**/*.yaml", - ], + ] + ["tests/rst/conf.py"], ), deps = [":score_metamodel"], ) diff --git a/src/extensions/score_source_code_linker/BUILD b/src/extensions/score_source_code_linker/BUILD index ab9b64a54..8dd7deb11 100644 --- a/src/extensions/score_source_code_linker/BUILD +++ b/src/extensions/score_source_code_linker/BUILD @@ -42,15 +42,19 @@ filegroup( py_library( name = "score_source_code_linker", - srcs = [":all_sources"], + srcs = [":sources"], imports = ["."], visibility = ["//visibility:public"], - deps = ["@score_docs_as_code//src/helper_lib"], + deps = ["//src/helper_lib"], ) py_library( name = "source_code_linker_helpers", - srcs = [":all_sources"], + srcs = [ + "needlinks.py", + "testlink.py", + "xml_parser.py", + ], imports = ["."], visibility = ["//visibility:public"], ) @@ -65,10 +69,10 @@ score_py_pytest( "-s", "-vv", ], - data = glob(["**/*.json"]), + data = glob(["tests/*.json"]), imports = ["."], deps = [ ":score_source_code_linker", - "@score_docs_as_code//src/extensions/score_metamodel", + "//src/extensions/score_metamodel", ], ) diff --git a/src/extensions/score_source_code_linker/tests/test_codelink.py b/src/extensions/score_source_code_linker/tests/test_codelink.py index 9e360d1a5..bf3d75615 100644 --- a/src/extensions/score_source_code_linker/tests/test_codelink.py +++ b/src/extensions/score_source_code_linker/tests/test_codelink.py @@ -21,9 +21,19 @@ import pytest from attribute_plugin import add_test_properties # type: ignore[import-untyped] -from sphinx_needs.data import NeedsMutable +from sphinx_needs.data import NeedsMutable, NeedsInfoType + + +def test_need(**kwargs: Any) -> NeedsInfoType: + """Convinience function to create a NeedsInfoType object with some defaults.""" + + kwargs.setdefault("id", "test_need") + kwargs.setdefault("docname", "docname") + kwargs.setdefault("doctype", "rst") + kwargs.setdefault("lineno", "42") + + return NeedsInfoType(**kwargs) -from src.extensions.score_metamodel.tests import need as test_need # Import the module under test # Note: You'll need to adjust these imports based on your actual module structure From 740c4e2c95c61a9dcc9fa9ea6f337f7cdbf6c987 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Thu, 22 Jan 2026 17:30:52 +0100 Subject: [PATCH 06/17] Document sourcelinks_json how to --- docs/how-to/source_to_doc_links.rst | 61 ++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/docs/how-to/source_to_doc_links.rst b/docs/how-to/source_to_doc_links.rst index f36866a30..3763b6d71 100644 --- a/docs/how-to/source_to_doc_links.rst +++ b/docs/how-to/source_to_doc_links.rst @@ -2,14 +2,65 @@ Reference Docs in Source Code ============================= In your C++/Rust/Python source code, you want to reference requirements (needs). -The docs-as-code tool will create backlinks in the documentation. +The docs-as-code tool will create backlinks in the documentation in two steps: + +1. You add a special comment in your source code that references the need ID. +2. Scan for those comments and provide needs links to your documentation. + +For an example result, look at the attribute ``source_code_link`` +of :need:`tool_req__docs_common_attr_title`. + +Comments in Source Code +----------------------- Use a comment and start with ``req-Id:`` or ``req-traceability:`` followed by the need ID. .. code-block:: python - # req-Id: TOOL_REQ__EXAMPLE_ID - # req-traceability: TOOL_REQ__EXAMPLE_ID + # req-Id: TOOL_REQ__EXAMPLE_ID + # req-traceability: TOOL_REQ__EXAMPLE_ID -For an example, look at the attribute ``source_code_link`` -of :need:`tool_req__docs_common_attr_title`. +For other languages (C++, Rust, etc.), use the appropriate comment syntax. + +Scanning Source Code for Links +------------------------------ + +In you ``BUILD`` files, you specify which source files to scan +with ``filegroup`` or ``glob`` or whatever Bazel mechanism you prefer. +Then, you use the ``sourcelinks_json`` rule to scan those files. +Finally, pass the scan results to the ``docs`` rule as ``sourcelinks`` attribute. + + +.. code-block:: starlark + :emphasize-lines: 1, 12, 26 + :linenos: + + load("//:docs.bzl", "docs", "sourcelinks_json") + + filegroup( + name = "some_sources", + srcs = [ + "foo.py", + "bar.cpp", + "data.yaml", + ] + glob(["subdir/**/.py"]), + ) + + sourcelinks_json( + name = "my_source_links", + srcs = [ + ":some_sources", + "//src:all_sources", + # whatever + ], + ) + + docs( + data = [ + "@score_process//:needs_json", + ], + source_dir = "docs", + sourcelinks = [":my_source_links"], + ) + +Since the source links are Bazel targets, you can easily reference other modules as well. From c4d8950156412c6e8ebe534115320aec60f447e0 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Thu, 22 Jan 2026 17:38:59 +0100 Subject: [PATCH 07/17] Fix linter warnings --- .../tests/test_codelink.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/extensions/score_source_code_linker/tests/test_codelink.py b/src/extensions/score_source_code_linker/tests/test_codelink.py index bf3d75615..88794c05b 100644 --- a/src/extensions/score_source_code_linker/tests/test_codelink.py +++ b/src/extensions/score_source_code_linker/tests/test_codelink.py @@ -21,19 +21,7 @@ import pytest from attribute_plugin import add_test_properties # type: ignore[import-untyped] -from sphinx_needs.data import NeedsMutable, NeedsInfoType - - -def test_need(**kwargs: Any) -> NeedsInfoType: - """Convinience function to create a NeedsInfoType object with some defaults.""" - - kwargs.setdefault("id", "test_need") - kwargs.setdefault("docname", "docname") - kwargs.setdefault("doctype", "rst") - kwargs.setdefault("lineno", "42") - - return NeedsInfoType(**kwargs) - +from sphinx_needs.data import NeedsInfoType, NeedsMutable # Import the module under test # Note: You'll need to adjust these imports based on your actual module structure @@ -66,6 +54,17 @@ def test_need(**kwargs: Any) -> NeedsInfoType: """ +def test_need(**kwargs: Any) -> NeedsInfoType: + """Convinience function to create a NeedsInfoType object with some defaults.""" + + kwargs.setdefault("id", "test_need") + kwargs.setdefault("docname", "docname") + kwargs.setdefault("doctype", "rst") + kwargs.setdefault("lineno", "42") + + return NeedsInfoType(**kwargs) + + def encode_comment(s: str) -> str: return s.replace(" ", "-----", 1) From 2842afbb29d0a73378616d5777ba9878ed9e3b17 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Fri, 23 Jan 2026 15:49:02 +0100 Subject: [PATCH 08/17] Test the new scripts --- scripts/tests/BUILD | 32 +++++++ .../tests/generate_sourcelinks_cli_test.py | 74 ++++++++++++++ scripts/tests/merge_sourcelinks_test.py | 96 +++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 scripts/tests/BUILD create mode 100644 scripts/tests/generate_sourcelinks_cli_test.py create mode 100644 scripts/tests/merge_sourcelinks_test.py diff --git a/scripts/tests/BUILD b/scripts/tests/BUILD new file mode 100644 index 000000000..bcd78ea7f --- /dev/null +++ b/scripts/tests/BUILD @@ -0,0 +1,32 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +load("@aspect_rules_py//py:defs.bzl", "py_test") +load("@pip_process//:requirements.bzl", "all_requirements") + +py_test( + name = "generate_sourcelinks_cli_test", + srcs = ["generate_sourcelinks_cli_test.py"], + deps = [ + "//scripts:generate_sourcelinks", + "//src/extensions/score_source_code_linker", + ] + all_requirements, +) + +py_test( + name = "merge_sourcelinks_test", + srcs = ["merge_sourcelinks_test.py"], + deps = [ + "//scripts:merge_sourcelinks", + ] + all_requirements, +) diff --git a/scripts/tests/generate_sourcelinks_cli_test.py b/scripts/tests/generate_sourcelinks_cli_test.py new file mode 100644 index 000000000..daf2bd3ac --- /dev/null +++ b/scripts/tests/generate_sourcelinks_cli_test.py @@ -0,0 +1,74 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +"""Tests for generate_sourcelinks_cli.py""" + +import json +import subprocess +import sys + + +def test_generate_sourcelinks_cli_basic(tmp_path): + """Test basic functionality of generate_sourcelinks_cli.""" + # Create a test source file with a traceability tag + test_file = tmp_path / "test_source.py" + test_file.write_text( + """ +# Some code here +# score:req:REQ_001 +def some_function(): + pass +""" + ) + + output_file = tmp_path / "output.json" + + # Execute the script + result = subprocess.run( + [ + sys.executable, + "scripts/generate_sourcelinks_cli.py", + "--output", + str(output_file), + str(test_file), + ], + cwd="/home/zwa2lr/git/score/docs-as-code", + ) + + assert result.returncode == 0 + assert output_file.exists() + + # Check the output content + with open(output_file) as f: + data = json.load(f) + assert isinstance(data, list) + assert len(data) > 0 + + # Verify schema of each entry + for entry in data: + assert "file" in entry + assert "line" in entry + assert "tag" in entry + assert "need" in entry + assert "full_line" in entry + + # Verify types + assert isinstance(entry["file"], str) + assert isinstance(entry["line"], int) + assert isinstance(entry["tag"], str) + assert isinstance(entry["need"], str) + assert isinstance(entry["full_line"], str) + + # Verify specific content for our test case + assert any(entry["need"] == "REQ_001" for entry in data) + assert any("score:req:REQ_001" in entry["tag"] for entry in data) diff --git a/scripts/tests/merge_sourcelinks_test.py b/scripts/tests/merge_sourcelinks_test.py new file mode 100644 index 000000000..186d7d40f --- /dev/null +++ b/scripts/tests/merge_sourcelinks_test.py @@ -0,0 +1,96 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +"""Tests for merge_sourcelinks.py""" + +import json +import subprocess +import sys + + +def test_merge_sourcelinks_basic(tmp_path): + """Test basic merge functionality.""" + # Create test JSON files with correct schema + file1 = tmp_path / "links1.json" + file1.write_text( + json.dumps( + [ + { + "file": "test1.py", + "line": 10, + "tag": "# score:req:", + "need": "REQ_001", + "full_line": "# score:req:REQ_001", + } + ] + ) + ) + + file2 = tmp_path / "links2.json" + file2.write_text( + json.dumps( + [ + { + "file": "test2.py", + "line": 20, + "tag": "# score:req:", + "need": "REQ_002", + "full_line": "# score:req:REQ_002", + } + ] + ) + ) + + output_file = tmp_path / "merged.json" + + result = subprocess.run( + [ + sys.executable, + "scripts/merge_sourcelinks.py", + "--output", + str(output_file), + str(file1), + str(file2), + ], + cwd="/home/zwa2lr/git/score/docs-as-code", + ) + + assert result.returncode == 0 + assert output_file.exists() + + with open(output_file) as f: + data = json.load(f) + + assert len(data) == 2 + + # Verify schema of merged entries + for entry in data: + assert "file" in entry + assert "line" in entry + assert "tag" in entry + assert "need" in entry + assert "full_line" in entry + + assert isinstance(entry["file"], str) + assert isinstance(entry["line"], int) + assert isinstance(entry["tag"], str) + assert isinstance(entry["need"], str) + assert isinstance(entry["full_line"], str) + + # Verify specific entries + assert any( + entry["need"] == "REQ_001" and entry["file"] == "test1.py" for entry in data + ) + assert any( + entry["need"] == "REQ_002" and entry["file"] == "test2.py" for entry in data + ) From 502f550fe150a2dad1da4b94de5c0d95dc4f15e6 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Fri, 23 Jan 2026 17:04:22 +0100 Subject: [PATCH 09/17] Explicit module Fixes consumer checks --- docs.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs.bzl b/docs.bzl index e65e7d00f..e39846b6a 100644 --- a/docs.bzl +++ b/docs.bzl @@ -69,11 +69,11 @@ def _merge_sourcelinks(name, sourcelinks): srcs = sourcelinks, outs = [name + ".json"], cmd = """ - $(location //scripts:merge_sourcelinks) \ + $(location @score_docs_as_code//scripts:merge_sourcelinks) \ --output $@ \ $(SRCS) """, - tools = ["//scripts:merge_sourcelinks"], + tools = ["@score_docs_as_code//scripts:merge_sourcelinks"], ) def docs(source_dir = "docs", data = [], deps = [], sourcelinks = []): @@ -245,10 +245,10 @@ def sourcelinks_json(name, srcs, visibility = None): srcs = srcs, outs = [output_file], cmd = """ - $(location //scripts:generate_sourcelinks) \ + $(location @score_docs_as_code//scripts:generate_sourcelinks) \ --output $@ \ $(SRCS) """, - tools = ["//scripts:generate_sourcelinks"], + tools = ["@score_docs_as_code//scripts:generate_sourcelinks"], visibility = visibility, ) From c6661553236f1c9e2fad8714c0f393aefca29f25 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Tue, 27 Jan 2026 09:16:43 +0100 Subject: [PATCH 10/17] Move into new scripts_bazel/ --- docs.bzl | 8 ++++---- scripts/README.md | 3 +++ {scripts => scripts_bazel}/BUILD | 0 scripts_bazel/README.md | 3 +++ {scripts => scripts_bazel}/generate_sourcelinks_cli.py | 0 {scripts => scripts_bazel}/merge_sourcelinks.py | 0 {scripts => scripts_bazel}/tests/BUILD | 4 ++-- .../tests/generate_sourcelinks_cli_test.py | 0 .../tests/merge_sourcelinks_test.py | 0 9 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 scripts/README.md rename {scripts => scripts_bazel}/BUILD (100%) create mode 100644 scripts_bazel/README.md rename {scripts => scripts_bazel}/generate_sourcelinks_cli.py (100%) rename {scripts => scripts_bazel}/merge_sourcelinks.py (100%) rename {scripts => scripts_bazel}/tests/BUILD (91%) rename {scripts => scripts_bazel}/tests/generate_sourcelinks_cli_test.py (100%) rename {scripts => scripts_bazel}/tests/merge_sourcelinks_test.py (100%) diff --git a/docs.bzl b/docs.bzl index e39846b6a..36b8f397d 100644 --- a/docs.bzl +++ b/docs.bzl @@ -69,11 +69,11 @@ def _merge_sourcelinks(name, sourcelinks): srcs = sourcelinks, outs = [name + ".json"], cmd = """ - $(location @score_docs_as_code//scripts:merge_sourcelinks) \ + $(location @score_docs_as_code//scripts_bazel:merge_sourcelinks) \ --output $@ \ $(SRCS) """, - tools = ["@score_docs_as_code//scripts:merge_sourcelinks"], + tools = ["@score_docs_as_code//scripts_bazel:merge_sourcelinks"], ) def docs(source_dir = "docs", data = [], deps = [], sourcelinks = []): @@ -245,10 +245,10 @@ def sourcelinks_json(name, srcs, visibility = None): srcs = srcs, outs = [output_file], cmd = """ - $(location @score_docs_as_code//scripts:generate_sourcelinks) \ + $(location @score_docs_as_code//scripts_bazel:generate_sourcelinks) \ --output $@ \ $(SRCS) """, - tools = ["@score_docs_as_code//scripts:generate_sourcelinks"], + tools = ["@score_docs_as_code//scripts_bazel:generate_sourcelinks"], visibility = visibility, ) diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 000000000..e28c9eefa --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,3 @@ +# Scripts + +The scripts directory is only for local development (linters) so far. diff --git a/scripts/BUILD b/scripts_bazel/BUILD similarity index 100% rename from scripts/BUILD rename to scripts_bazel/BUILD diff --git a/scripts_bazel/README.md b/scripts_bazel/README.md new file mode 100644 index 000000000..7728a1736 --- /dev/null +++ b/scripts_bazel/README.md @@ -0,0 +1,3 @@ +# Scripts Bazel + +This folder contains executables to be used within Bazel rules. diff --git a/scripts/generate_sourcelinks_cli.py b/scripts_bazel/generate_sourcelinks_cli.py similarity index 100% rename from scripts/generate_sourcelinks_cli.py rename to scripts_bazel/generate_sourcelinks_cli.py diff --git a/scripts/merge_sourcelinks.py b/scripts_bazel/merge_sourcelinks.py similarity index 100% rename from scripts/merge_sourcelinks.py rename to scripts_bazel/merge_sourcelinks.py diff --git a/scripts/tests/BUILD b/scripts_bazel/tests/BUILD similarity index 91% rename from scripts/tests/BUILD rename to scripts_bazel/tests/BUILD index bcd78ea7f..5be050c67 100644 --- a/scripts/tests/BUILD +++ b/scripts_bazel/tests/BUILD @@ -18,7 +18,7 @@ py_test( name = "generate_sourcelinks_cli_test", srcs = ["generate_sourcelinks_cli_test.py"], deps = [ - "//scripts:generate_sourcelinks", + "//scripts_bazel:generate_sourcelinks", "//src/extensions/score_source_code_linker", ] + all_requirements, ) @@ -27,6 +27,6 @@ py_test( name = "merge_sourcelinks_test", srcs = ["merge_sourcelinks_test.py"], deps = [ - "//scripts:merge_sourcelinks", + "//scripts_bazel:merge_sourcelinks", ] + all_requirements, ) diff --git a/scripts/tests/generate_sourcelinks_cli_test.py b/scripts_bazel/tests/generate_sourcelinks_cli_test.py similarity index 100% rename from scripts/tests/generate_sourcelinks_cli_test.py rename to scripts_bazel/tests/generate_sourcelinks_cli_test.py diff --git a/scripts/tests/merge_sourcelinks_test.py b/scripts_bazel/tests/merge_sourcelinks_test.py similarity index 100% rename from scripts/tests/merge_sourcelinks_test.py rename to scripts_bazel/tests/merge_sourcelinks_test.py From e0405c4ee35d2cf0333dbb7e55165f3d9172d3b1 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Tue, 27 Jan 2026 09:48:33 +0100 Subject: [PATCH 11/17] Add a concept page about "docs dependencies" --- docs/concepts/docs_deps.rst | 41 +++++++++++++++++++++++++++++++++++++ docs/concepts/index.rst | 1 + 2 files changed, 42 insertions(+) create mode 100644 docs/concepts/docs_deps.rst diff --git a/docs/concepts/docs_deps.rst b/docs/concepts/docs_deps.rst new file mode 100644 index 000000000..fe55d7600 --- /dev/null +++ b/docs/concepts/docs_deps.rst @@ -0,0 +1,41 @@ + +.. _docs_dependencies: + +========================== +Docs Dependencies +========================== + +When running ``bazel run :docs``, the documentation build system orchestrates multiple interconnected dependencies to produce HTML documentation. + +1. Gather inputs (Bazel may do this parallelized): + + * Extract source code links from C++/Rust/Python code via ``sourcelinks_json`` rule. + + * Optionally, merge source links using the ``merge_sourcelinks`` rule. + + * Needs (requirements) are gathered from various ``needs_json`` targets specified in the ``data`` attribute. + +2. Documentation sources are read from the specified source directory (default: ``docs/``). + Sphinx processes the documentation sources along with the merged data to generate the final HTML output. + +.. plantuml:: + + @startuml + left to right direction + + collections "Documentation Sources" as DocsSource + collections "Needs JSON Targets" as NeedsTargets + collections "Source Code Links" as SourceLinks + artifact "Merge Data" as Merge + process "Sphinx Processing" as Sphinx + artifact "HTML Output" as HTMLOutput + collections "S-CORE extensions" as SCoreExt + + DocsSource --> Sphinx + NeedsTargets --> Sphinx + SCoreExt --> Sphinx + SourceLinks --> Merge + Merge --> Sphinx + Sphinx --> HTMLOutput + + @enduml diff --git a/docs/concepts/index.rst b/docs/concepts/index.rst index 6357bdf51..9351e15fe 100644 --- a/docs/concepts/index.rst +++ b/docs/concepts/index.rst @@ -9,3 +9,4 @@ Here you find explanations how and why docs-as-code works the way it does. :maxdepth: 1 bidirectional_traceability + docs_deps From e971a0f7b9ee8b0f5cfae7954f7731908bf7bc36 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Tue, 27 Jan 2026 09:56:08 +0100 Subject: [PATCH 12/17] Fix warnings --- scripts_bazel/generate_sourcelinks_cli.py | 2 +- scripts_bazel/tests/generate_sourcelinks_cli_test.py | 3 ++- scripts_bazel/tests/merge_sourcelinks_test.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts_bazel/generate_sourcelinks_cli.py b/scripts_bazel/generate_sourcelinks_cli.py index a70b878a0..16850494d 100644 --- a/scripts_bazel/generate_sourcelinks_cli.py +++ b/scripts_bazel/generate_sourcelinks_cli.py @@ -23,7 +23,7 @@ from pathlib import Path from src.extensions.score_source_code_linker.generate_source_code_links_json import ( - _extract_references_from_file, + _extract_references_from_file, # pyright: ignore[reportPrivateUsage] TODO: move it out of the extension and into this script ) from src.extensions.score_source_code_linker.needlinks import ( store_source_code_links_json, diff --git a/scripts_bazel/tests/generate_sourcelinks_cli_test.py b/scripts_bazel/tests/generate_sourcelinks_cli_test.py index daf2bd3ac..012c32bdc 100644 --- a/scripts_bazel/tests/generate_sourcelinks_cli_test.py +++ b/scripts_bazel/tests/generate_sourcelinks_cli_test.py @@ -16,9 +16,10 @@ import json import subprocess import sys +from pathlib import Path -def test_generate_sourcelinks_cli_basic(tmp_path): +def test_generate_sourcelinks_cli_basic(tmp_path: Path) -> None: """Test basic functionality of generate_sourcelinks_cli.""" # Create a test source file with a traceability tag test_file = tmp_path / "test_source.py" diff --git a/scripts_bazel/tests/merge_sourcelinks_test.py b/scripts_bazel/tests/merge_sourcelinks_test.py index 186d7d40f..ecb4e10ff 100644 --- a/scripts_bazel/tests/merge_sourcelinks_test.py +++ b/scripts_bazel/tests/merge_sourcelinks_test.py @@ -16,9 +16,10 @@ import json import subprocess import sys +from pathlib import Path -def test_merge_sourcelinks_basic(tmp_path): +def test_merge_sourcelinks_basic(tmp_path: Path) -> None: """Test basic merge functionality.""" # Create test JSON files with correct schema file1 = tmp_path / "links1.json" From 4955337c0b5114e9f291ae01598a68e28a0aac04 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Tue, 27 Jan 2026 10:06:44 +0100 Subject: [PATCH 13/17] Review feedback. Simplified things. --- BUILD | 13 +------------ scripts_bazel/BUILD | 6 ++++++ scripts_bazel/generate_sourcelinks_cli.py | 10 +++++----- scripts_bazel/merge_sourcelinks.py | 10 ++++------ 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/BUILD b/BUILD index adbbd688a..0b3feb381 100644 --- a/BUILD +++ b/BUILD @@ -31,18 +31,7 @@ copyright_checker( sourcelinks_json( name = "sourcelinks_json", - srcs = [ - "//src:all_sources", - "//src/extensions/score_draw_uml_funcs:all_sources", - "//src/extensions/score_header_service:all_sources", - "//src/extensions/score_layout:all_sources", - "//src/extensions/score_metamodel:all_sources", - "//src/extensions/score_source_code_linker:all_sources", - "//src/extensions/score_sphinx_bundle:all_sources", - "//src/extensions/score_sync_toml:all_sources", - "//src/find_runfiles:all_sources", - "//src/helper_lib:all_sources", - ], + srcs = ["//src:all_sources", "//scripts_bazel:sources"], visibility = ["//visibility:public"], ) diff --git a/scripts_bazel/BUILD b/scripts_bazel/BUILD index ab9f0a9f6..f332a4af3 100644 --- a/scripts_bazel/BUILD +++ b/scripts_bazel/BUILD @@ -14,6 +14,12 @@ load("@aspect_rules_py//py:defs.bzl", "py_binary") load("@pip_process//:requirements.bzl", "all_requirements") +filegroup( + name = "sources", + srcs = glob(["**/*.py"]), + visibility = ["//visibility:public"], +) + py_binary( name = "generate_sourcelinks", srcs = ["generate_sourcelinks_cli.py"], diff --git a/scripts_bazel/generate_sourcelinks_cli.py b/scripts_bazel/generate_sourcelinks_cli.py index 16850494d..4291b97c5 100644 --- a/scripts_bazel/generate_sourcelinks_cli.py +++ b/scripts_bazel/generate_sourcelinks_cli.py @@ -55,11 +55,11 @@ def main(): all_need_references = [] for file_path in args.files: abs_file_path = file_path.resolve() - if abs_file_path.exists(): - references = _extract_references_from_file( - abs_file_path.parent, Path(abs_file_path.name) - ) - all_need_references.extend(references) + assert abs_file_path.exists(), abs_file_path + references = _extract_references_from_file( + abs_file_path.parent, Path(abs_file_path.name) + ) + all_need_references.extend(references) store_source_code_links_json(args.output, all_need_references) logger.info( diff --git a/scripts_bazel/merge_sourcelinks.py b/scripts_bazel/merge_sourcelinks.py index 2d9141f22..f194e19ca 100644 --- a/scripts_bazel/merge_sourcelinks.py +++ b/scripts_bazel/merge_sourcelinks.py @@ -46,13 +46,11 @@ def main(): merged = [] for json_file in args.files: - if json_file.exists(): - with open(json_file) as f: - data = json.load(f) - if isinstance(data, list): - merged.extend(data) + with open(json_file) as f: + data = json.load(f) + assert isinstance(data, list), repr(data) + merged.extend(data) - args.output.parent.mkdir(parents=True, exist_ok=True) with open(args.output, "w") as f: json.dump(merged, f, indent=2, ensure_ascii=False) From 470ecaf19ce1d3b3fd92fed4f4790d0f5c03fd4a Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Tue, 27 Jan 2026 10:24:20 +0100 Subject: [PATCH 14/17] More review feedback --- BUILD | 6 ++++-- src/extensions/score_metamodel/BUILD | 4 ++-- src/extensions/score_source_code_linker/BUILD | 2 +- src/extensions/score_source_code_linker/__init__.py | 3 +++ src/extensions/score_sphinx_bundle/BUILD | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/BUILD b/BUILD index 0b3feb381..0eee408fb 100644 --- a/BUILD +++ b/BUILD @@ -11,7 +11,6 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@aspect_rules_py//py:defs.bzl", "py_library") load("@score_tooling//:defs.bzl", "cli_helper", "copyright_checker") load("//:docs.bzl", "docs", "sourcelinks_json") @@ -31,7 +30,10 @@ copyright_checker( sourcelinks_json( name = "sourcelinks_json", - srcs = ["//src:all_sources", "//scripts_bazel:sources"], + srcs = [ + "//scripts_bazel:sources", + "//src:all_sources", + ], visibility = ["//visibility:public"], ) diff --git a/src/extensions/score_metamodel/BUILD b/src/extensions/score_metamodel/BUILD index cc0dd2484..30392ca00 100644 --- a/src/extensions/score_metamodel/BUILD +++ b/src/extensions/score_metamodel/BUILD @@ -61,7 +61,7 @@ score_py_pytest( [ "tests/**/*.rst", "tests/**/*.yaml", - ] + ["tests/rst/conf.py"], - ), + ], + ) + ["tests/rst/conf.py"], deps = [":score_metamodel"], ) diff --git a/src/extensions/score_source_code_linker/BUILD b/src/extensions/score_source_code_linker/BUILD index 8dd7deb11..758fc786d 100644 --- a/src/extensions/score_source_code_linker/BUILD +++ b/src/extensions/score_source_code_linker/BUILD @@ -45,7 +45,7 @@ py_library( srcs = [":sources"], imports = ["."], visibility = ["//visibility:public"], - deps = ["//src/helper_lib"], + deps = ["@score_docs_as_code//src/helper_lib"], ) py_library( diff --git a/src/extensions/score_source_code_linker/__init__.py b/src/extensions/score_source_code_linker/__init__.py index c9e57b97d..86556cf86 100644 --- a/src/extensions/score_source_code_linker/__init__.py +++ b/src/extensions/score_source_code_linker/__init__.py @@ -132,6 +132,9 @@ def build_and_save_combined_file(outdir: Path): """ source_code_links_json = os.environ.get("SCORE_SOURCELINKS") if not source_code_links_json: + # Fallback to the obsolete way of doing source code links, + # just in case someone is not using the docs(sourcelinks=...) attribute. + # TODO: Remove this once backwards compatibility is not needed anymore. source_code_links_json = get_cache_filename( outdir, "score_source_code_linker_cache.json" ) diff --git a/src/extensions/score_sphinx_bundle/BUILD b/src/extensions/score_sphinx_bundle/BUILD index 9145b5ab2..26bb02892 100644 --- a/src/extensions/score_sphinx_bundle/BUILD +++ b/src/extensions/score_sphinx_bundle/BUILD @@ -15,7 +15,7 @@ load("@pip_process//:requirements.bzl", "all_requirements") filegroup( name = "all_sources", - srcs = glob(["*.py"]), + srcs = ["__init__.py"], visibility = ["//visibility:public"], ) From 4d7cab07985d680ed61fc8977a3b5a273cf910b8 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Tue, 27 Jan 2026 11:46:43 +0100 Subject: [PATCH 15/17] Clean up tests --- scripts_bazel/tests/generate_sourcelinks_cli_test.py | 3 +-- scripts_bazel/tests/merge_sourcelinks_test.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts_bazel/tests/generate_sourcelinks_cli_test.py b/scripts_bazel/tests/generate_sourcelinks_cli_test.py index 012c32bdc..61fc79bb0 100644 --- a/scripts_bazel/tests/generate_sourcelinks_cli_test.py +++ b/scripts_bazel/tests/generate_sourcelinks_cli_test.py @@ -43,7 +43,6 @@ def some_function(): str(output_file), str(test_file), ], - cwd="/home/zwa2lr/git/score/docs-as-code", ) assert result.returncode == 0 @@ -51,7 +50,7 @@ def some_function(): # Check the output content with open(output_file) as f: - data = json.load(f) + data: list[dict[str, str | int]] = json.load(f) assert isinstance(data, list) assert len(data) > 0 diff --git a/scripts_bazel/tests/merge_sourcelinks_test.py b/scripts_bazel/tests/merge_sourcelinks_test.py index ecb4e10ff..2f146c522 100644 --- a/scripts_bazel/tests/merge_sourcelinks_test.py +++ b/scripts_bazel/tests/merge_sourcelinks_test.py @@ -63,15 +63,14 @@ def test_merge_sourcelinks_basic(tmp_path: Path) -> None: str(file1), str(file2), ], - cwd="/home/zwa2lr/git/score/docs-as-code", ) assert result.returncode == 0 assert output_file.exists() with open(output_file) as f: - data = json.load(f) - + data: list[dict[str, str | int]] = json.load(f) + assert isinstance(data, list) assert len(data) == 2 # Verify schema of merged entries From b6aecd485b459e88b695004eeb93c67ee49071b5 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Tue, 27 Jan 2026 14:32:22 +0100 Subject: [PATCH 16/17] Actually test --- scripts_bazel/tests/BUILD | 6 ++--- .../tests/generate_sourcelinks_cli_test.py | 10 ++++----- scripts_bazel/tests/merge_sourcelinks_test.py | 22 +++++++++++-------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/scripts_bazel/tests/BUILD b/scripts_bazel/tests/BUILD index 5be050c67..2290a6a24 100644 --- a/scripts_bazel/tests/BUILD +++ b/scripts_bazel/tests/BUILD @@ -11,10 +11,10 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@aspect_rules_py//py:defs.bzl", "py_test") load("@pip_process//:requirements.bzl", "all_requirements") +load("@score_tooling//:defs.bzl", "score_py_pytest") -py_test( +score_py_pytest( name = "generate_sourcelinks_cli_test", srcs = ["generate_sourcelinks_cli_test.py"], deps = [ @@ -23,7 +23,7 @@ py_test( ] + all_requirements, ) -py_test( +score_py_pytest( name = "merge_sourcelinks_test", srcs = ["merge_sourcelinks_test.py"], deps = [ diff --git a/scripts_bazel/tests/generate_sourcelinks_cli_test.py b/scripts_bazel/tests/generate_sourcelinks_cli_test.py index 61fc79bb0..f25acc5ab 100644 --- a/scripts_bazel/tests/generate_sourcelinks_cli_test.py +++ b/scripts_bazel/tests/generate_sourcelinks_cli_test.py @@ -18,6 +18,8 @@ import sys from pathlib import Path +_MY_PATH = Path(__file__).parent + def test_generate_sourcelinks_cli_basic(tmp_path: Path) -> None: """Test basic functionality of generate_sourcelinks_cli.""" @@ -26,7 +28,7 @@ def test_generate_sourcelinks_cli_basic(tmp_path: Path) -> None: test_file.write_text( """ # Some code here -# score:req:REQ_001 +# req-Id: tool_req__docs_arch_types def some_function(): pass """ @@ -38,7 +40,7 @@ def some_function(): result = subprocess.run( [ sys.executable, - "scripts/generate_sourcelinks_cli.py", + _MY_PATH.parent / "generate_sourcelinks_cli.py", "--output", str(output_file), str(test_file), @@ -69,6 +71,4 @@ def some_function(): assert isinstance(entry["need"], str) assert isinstance(entry["full_line"], str) - # Verify specific content for our test case - assert any(entry["need"] == "REQ_001" for entry in data) - assert any("score:req:REQ_001" in entry["tag"] for entry in data) + assert any(entry["need"] == "tool_req__docs_arch_types" for entry in data) diff --git a/scripts_bazel/tests/merge_sourcelinks_test.py b/scripts_bazel/tests/merge_sourcelinks_test.py index 2f146c522..9f92cfd6c 100644 --- a/scripts_bazel/tests/merge_sourcelinks_test.py +++ b/scripts_bazel/tests/merge_sourcelinks_test.py @@ -18,6 +18,8 @@ import sys from pathlib import Path +_MY_PATH = Path(__file__).parent + def test_merge_sourcelinks_basic(tmp_path: Path) -> None: """Test basic merge functionality.""" @@ -29,9 +31,9 @@ def test_merge_sourcelinks_basic(tmp_path: Path) -> None: { "file": "test1.py", "line": 10, - "tag": "# score:req:", - "need": "REQ_001", - "full_line": "# score:req:REQ_001", + "tag": "# req-Id:", + "need": "tool_req__docs_arch_types", + "full_line": "# req-Id: tool_req__docs_arch_types", } ] ) @@ -44,9 +46,9 @@ def test_merge_sourcelinks_basic(tmp_path: Path) -> None: { "file": "test2.py", "line": 20, - "tag": "# score:req:", - "need": "REQ_002", - "full_line": "# score:req:REQ_002", + "tag": "# req-Id:", + "need": "gd_req__req_validity", + "full_line": "# req-Id: gd_req__req_validity", } ] ) @@ -57,7 +59,7 @@ def test_merge_sourcelinks_basic(tmp_path: Path) -> None: result = subprocess.run( [ sys.executable, - "scripts/merge_sourcelinks.py", + _MY_PATH.parent / "merge_sourcelinks.py", "--output", str(output_file), str(file1), @@ -89,8 +91,10 @@ def test_merge_sourcelinks_basic(tmp_path: Path) -> None: # Verify specific entries assert any( - entry["need"] == "REQ_001" and entry["file"] == "test1.py" for entry in data + entry["need"] == "tool_req__docs_arch_types" and entry["file"] == "test1.py" + for entry in data ) assert any( - entry["need"] == "REQ_002" and entry["file"] == "test2.py" for entry in data + entry["need"] == "gd_req__req_validity" and entry["file"] == "test2.py" + for entry in data ) From d4661f12a088db75940ef7a9861adb8408793d5d Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau <95761648+a-zw@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:38:42 +0100 Subject: [PATCH 17/17] Update docs/concepts/docs_deps.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Maximilian Sören Pollak Signed-off-by: Andreas Zwinkau <95761648+a-zw@users.noreply.github.com> --- docs/concepts/docs_deps.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/concepts/docs_deps.rst b/docs/concepts/docs_deps.rst index fe55d7600..12aca5b49 100644 --- a/docs/concepts/docs_deps.rst +++ b/docs/concepts/docs_deps.rst @@ -9,7 +9,7 @@ When running ``bazel run :docs``, the documentation build system orchestrates mu 1. Gather inputs (Bazel may do this parallelized): - * Extract source code links from C++/Rust/Python code via ``sourcelinks_json`` rule. + * Extract source code links from files via ``sourcelinks_json`` rule. * Optionally, merge source links using the ``merge_sourcelinks`` rule.