Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"])

Expand All @@ -29,11 +29,29 @@ copyright_checker(
visibility = ["//visibility:public"],
)

sourcelinks_json(
name = "sourcelinks_json",
srcs = [
"//src:all_sources",
Copy link
Member

Choose a reason for hiding this comment

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

all following items are already included in 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",
],
visibility = ["//visibility:public"],
)

docs(
data = [
"@score_process//:needs_json",
],
source_dir = "docs",
sourcelinks = [":sourcelinks_json"],
)

cli_helper(
Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
73 changes: 66 additions & 7 deletions docs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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 @score_docs_as_code//scripts:merge_sourcelinks) \
--output $@ \
$(SRCS)
""",
tools = ["@score_docs_as_code//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 != "":
Expand Down Expand Up @@ -106,64 +134,69 @@ 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)",
},
)

py_binary(
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)",
},
)

py_binary(
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)",
},
)

py_binary(
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)",
},
)

py_binary(
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)",
},
)

Expand Down Expand Up @@ -193,3 +226,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 @score_docs_as_code//scripts:generate_sourcelinks) \
--output $@ \
$(SRCS)
""",
tools = ["@score_docs_as_code//scripts:generate_sourcelinks"],
visibility = visibility,
)
61 changes: 56 additions & 5 deletions docs/how-to/source_to_doc_links.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +28 to +31
Copy link
Contributor

Choose a reason for hiding this comment

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

If I remember correctly it was requested that we use non direct language (like you etc.)
Though I don't care too much about it personally.
What's the opinion of the others?

Here is a suggestion how that could look like:

Suggested change
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.
In the ``BUILD`` files, it can be specified which source files should be scanned.
This can be achieved with ``filegroup``, ``glob`` or whatever Bazel mechanism is prefered.
Then, the ``sourcelinks_json`` rule is used to scan those files.
Finally, the scanned results need to be passed into ``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"],
Copy link
Member

Choose a reason for hiding this comment

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

up to now docs() is defining internal targets like needs_json itself. All in one command. Can we do the same with sourcelinks_json?

)

Since the source links are Bazel targets, you can easily reference other modules as well.
32 changes: 32 additions & 0 deletions scripts/BUILD
Original file line number Diff line number Diff line change
@@ -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_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,
)

py_binary(
name = "merge_sourcelinks",
srcs = ["merge_sourcelinks.py"],
main = "merge_sourcelinks.py",
visibility = ["//visibility:public"],
)
72 changes: 72 additions & 0 deletions scripts/generate_sourcelinks_cli.py
Original file line number Diff line number Diff line change
@@ -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 (
Copy link
Contributor

Choose a reason for hiding this comment

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

Might need to either make this function public or make a public counterpart.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure yet. Eventually, we should be able to extract it from the Sphinx extension, because the extension shall only consume the json files.

_extract_references_from_file,

Check warning on line 26 in scripts/generate_sourcelinks_cli.py

View workflow job for this annotation

GitHub Actions / lint

"_extract_references_from_file" is private and used outside of the module in which it is declared (reportPrivateUsage)
)
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())
Loading
Loading