Skip to content

Commit fe212a0

Browse files
authored
Source links as Bazel target (#358)
* Have scan_code attribute instead of sourcelinks_json rule * Adapt to Sphinx-Needs 6 --------- Signed-off-by: Andreas Zwinkau <95761648+a-zw@users.noreply.github.com>
1 parent b5023ad commit fe212a0

27 files changed

Lines changed: 783 additions & 83 deletions

File tree

BUILD

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# SPDX-License-Identifier: Apache-2.0
1212
# *******************************************************************************
1313

14-
load("@aspect_rules_py//py:defs.bzl", "py_library")
1514
load("@score_tooling//:defs.bzl", "cli_helper", "copyright_checker")
1615
load("//:docs.bzl", "docs")
1716

@@ -33,6 +32,10 @@ docs(
3332
data = [
3433
"@score_process//:needs_json",
3534
],
35+
scan_code = [
36+
"//scripts_bazel:sources",
37+
"//src:all_sources",
38+
],
3639
source_dir = "docs",
3740
)
3841

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ bazel_dep(name = "score_process", version = "1.4.2")
104104

105105
# Add Linter
106106
bazel_dep(name = "rules_multitool", version = "1.9.0")
107-
bazel_dep(name = "score_tooling", version = "1.0.2")
107+
bazel_dep(name = "score_tooling", version = "1.0.5")
108108

109109
multitool_root = use_extension("@rules_multitool//multitool:extension.bzl", "multitool")
110110
use_repo(multitool_root, "actionlint_hub", "multitool", "ruff_hub", "shellcheck_hub", "yamlfmt_hub")

docs.bzl

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
# SPDX-License-Identifier: Apache-2.0
1212
# *******************************************************************************
1313

14+
"""
15+
Easy streamlined way for S-CORE docs-as-code.
16+
"""
17+
1418
# Multiple approaches are available to build the same documentation output:
1519
#
1620
# 1. **Esbonio via IDE support (`ide_support` target)**:
@@ -37,12 +41,10 @@
3741
#
3842
# For user-facing documentation, refer to `/README.md`.
3943

40-
load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_library")
41-
load("@pip_process//:requirements.bzl", "all_requirements", "requirement")
44+
load("@aspect_rules_py//py:defs.bzl", "py_binary")
45+
load("@pip_process//:requirements.bzl", "all_requirements")
4246
load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix")
43-
load("@rules_pkg//pkg:tar.bzl", "pkg_tar")
4447
load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_build_binary", "sphinx_docs")
45-
load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library")
4648
load("@score_tooling//:defs.bzl", "score_virtualenv")
4749

4850
def _rewrite_needs_json_to_docs_sources(labels):
@@ -56,10 +58,47 @@ def _rewrite_needs_json_to_docs_sources(labels):
5658
out.append(s)
5759
return out
5860

59-
def docs(source_dir = "docs", data = [], deps = []):
61+
def _rewrite_needs_json_to_sourcelinks(labels):
62+
"""Replace '@repo//:needs_json' -> '@repo//:sourcelinks_json' for every item."""
63+
out = []
64+
for x in labels:
65+
s = str(x)
66+
if s.endswith("//:needs_json"):
67+
out.append(s.replace("//:needs_json", "//:sourcelinks_json"))
68+
else:
69+
out.append(s)
70+
return out
71+
72+
def _merge_sourcelinks(name, sourcelinks):
73+
"""Merge multiple sourcelinks JSON files into a single file.
74+
75+
Args:
76+
name: Name for the merged sourcelinks target
77+
sourcelinks: List of sourcelinks JSON file targets
6078
"""
61-
Creates all targets related to documentation.
79+
80+
native.genrule(
81+
name = name,
82+
srcs = sourcelinks,
83+
outs = [name + ".json"],
84+
cmd = """
85+
$(location @score_docs_as_code//scripts_bazel:merge_sourcelinks) \
86+
--output $@ \
87+
$(SRCS)
88+
""",
89+
tools = ["@score_docs_as_code//scripts_bazel:merge_sourcelinks"],
90+
)
91+
92+
def docs(source_dir = "docs", data = [], deps = [], scan_code = []):
93+
"""Creates all targets related to documentation.
94+
6295
By using this function, you'll get any and all updates for documentation targets in one place.
96+
97+
Args:
98+
source_dir: The source directory containing documentation files. Defaults to "docs".
99+
data: Additional data files to include in the documentation build.
100+
deps: Additional dependencies for the documentation build.
101+
scan_code: List of code targets to scan for source code links.
63102
"""
64103

65104
call_path = native.package_name()
@@ -100,70 +139,79 @@ def docs(source_dir = "docs", data = [], deps = []):
100139
visibility = ["//visibility:public"],
101140
)
102141

142+
_sourcelinks_json(name = "sourcelinks_json", srcs = scan_code)
143+
103144
data_with_docs_sources = _rewrite_needs_json_to_docs_sources(data)
145+
additional_combo_sourcelinks = _rewrite_needs_json_to_sourcelinks(data)
146+
_merge_sourcelinks(name = "merged_sourcelinks", sourcelinks = [":sourcelinks_json"] + additional_combo_sourcelinks)
104147

105148
py_binary(
106149
name = "docs",
107150
tags = ["cli_help=Build documentation:\nbazel run //:docs"],
108151
srcs = ["@score_docs_as_code//src:incremental.py"],
109-
data = data,
152+
data = data + [":sourcelinks_json"],
110153
deps = deps,
111154
env = {
112155
"SOURCE_DIRECTORY": source_dir,
113156
"DATA": str(data),
114157
"ACTION": "incremental",
158+
"SCORE_SOURCELINKS": "$(location :sourcelinks_json)",
115159
},
116160
)
117161

118162
py_binary(
119163
name = "docs_combo_experimental",
120164
tags = ["cli_help=Build full documentation with all dependencies:\nbazel run //:docs_combo_experimental"],
121165
srcs = ["@score_docs_as_code//src:incremental.py"],
122-
data = data_with_docs_sources,
166+
data = data_with_docs_sources + [":merged_sourcelinks"],
123167
deps = deps,
124168
env = {
125169
"SOURCE_DIRECTORY": source_dir,
126170
"DATA": str(data_with_docs_sources),
127171
"ACTION": "incremental",
172+
"SCORE_SOURCELINKS": "$(location :merged_sourcelinks)",
128173
},
129174
)
130175

131176
py_binary(
132177
name = "docs_check",
133178
tags = ["cli_help=Verify documentation:\nbazel run //:docs_check"],
134179
srcs = ["@score_docs_as_code//src:incremental.py"],
135-
data = data,
180+
data = data + [":sourcelinks_json"],
136181
deps = deps,
137182
env = {
138183
"SOURCE_DIRECTORY": source_dir,
139184
"DATA": str(data),
140185
"ACTION": "check",
186+
"SCORE_SOURCELINKS": "$(location :sourcelinks_json)",
141187
},
142188
)
143189

144190
py_binary(
145191
name = "live_preview",
146192
tags = ["cli_help=Live preview documentation in the browser:\nbazel run //:live_preview"],
147193
srcs = ["@score_docs_as_code//src:incremental.py"],
148-
data = data,
194+
data = data + [":sourcelinks_json"],
149195
deps = deps,
150196
env = {
151197
"SOURCE_DIRECTORY": source_dir,
152198
"DATA": str(data),
153199
"ACTION": "live_preview",
200+
"SCORE_SOURCELINKS": "$(location :sourcelinks_json)",
154201
},
155202
)
156203

157204
py_binary(
158205
name = "live_preview_combo_experimental",
159206
tags = ["cli_help=Live preview full documentation with all dependencies in the browser:\nbazel run //:live_preview_combo_experimental"],
160207
srcs = ["@score_docs_as_code//src:incremental.py"],
161-
data = data_with_docs_sources,
208+
data = data_with_docs_sources + [":merged_sourcelinks"],
162209
deps = deps,
163210
env = {
164211
"SOURCE_DIRECTORY": source_dir,
165212
"DATA": str(data_with_docs_sources),
166213
"ACTION": "live_preview",
214+
"SCORE_SOURCELINKS": "$(location :merged_sourcelinks)",
167215
},
168216
)
169217

@@ -193,3 +241,28 @@ def docs(source_dir = "docs", data = [], deps = []):
193241
tools = data,
194242
visibility = ["//visibility:public"],
195243
)
244+
245+
def _sourcelinks_json(name, srcs):
246+
"""
247+
Creates a target that generates a JSON file with source code links.
248+
249+
See https://eclipse-score.github.io/docs-as-code/main/how-to/source_to_doc_links.html
250+
251+
Args:
252+
name: Name of the target
253+
srcs: Source files to scan for traceability tags
254+
"""
255+
output_file = name + ".json"
256+
257+
native.genrule(
258+
name = name,
259+
srcs = srcs,
260+
outs = [output_file],
261+
cmd = """
262+
$(location @score_docs_as_code//scripts_bazel:generate_sourcelinks) \
263+
--output $@ \
264+
$(SRCS)
265+
""",
266+
tools = ["@score_docs_as_code//scripts_bazel:generate_sourcelinks"],
267+
visibility = ["//visibility:public"],
268+
)

docs/concepts/docs_deps.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
.. _docs_dependencies:
3+
4+
==========================
5+
Docs Dependencies
6+
==========================
7+
8+
When running ``bazel run :docs``, the documentation build system orchestrates multiple interconnected dependencies to produce HTML documentation.
9+
10+
1. Gather inputs (Bazel may do this parallelized):
11+
12+
* Extract source code links from files via ``sourcelinks_json`` rule.
13+
14+
* Optionally, merge source links using the ``merge_sourcelinks`` rule.
15+
16+
* Needs (requirements) are gathered from various ``needs_json`` targets specified in the ``data`` attribute.
17+
18+
2. Documentation sources are read from the specified source directory (default: ``docs/``).
19+
Sphinx processes the documentation sources along with the merged data to generate the final HTML output.
20+
21+
.. plantuml::
22+
23+
@startuml
24+
left to right direction
25+
26+
collections "Documentation Sources" as DocsSource
27+
collections "Needs JSON Targets" as NeedsTargets
28+
collections "Source Code Links" as SourceLinks
29+
artifact "Merge Data" as Merge
30+
process "Sphinx Processing" as Sphinx
31+
artifact "HTML Output" as HTMLOutput
32+
collections "S-CORE extensions" as SCoreExt
33+
34+
DocsSource --> Sphinx
35+
NeedsTargets --> Sphinx
36+
SCoreExt --> Sphinx
37+
SourceLinks --> Merge
38+
Merge --> Sphinx
39+
Sphinx --> HTMLOutput
40+
41+
@enduml

docs/concepts/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ Here you find explanations how and why docs-as-code works the way it does.
99
:maxdepth: 1
1010

1111
bidirectional_traceability
12+
docs_deps

docs/how-to/source_to_doc_links.rst

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,50 @@ Reference Docs in Source Code
22
=============================
33

44
In your C++/Rust/Python source code, you want to reference requirements (needs).
5-
The docs-as-code tool will create backlinks in the documentation.
5+
The docs-as-code tool will create backlinks in the documentation in two steps:
6+
7+
1. You add a special comment in your source code that references the need ID.
8+
2. Scan for those comments and provide needs links to your documentation.
9+
10+
For an example result, look at the attribute ``source_code_link``
11+
of :need:`tool_req__docs_common_attr_title`.
12+
13+
Comments in Source Code
14+
-----------------------
615

716
Use a comment and start with ``req-Id:`` or ``req-traceability:`` followed by the need ID.
817

918
.. code-block:: python
1019
11-
# req-Id: TOOL_REQ__EXAMPLE_ID
12-
# req-traceability: TOOL_REQ__EXAMPLE_ID
20+
# req-Id: TOOL_REQ__EXAMPLE_ID
21+
# req-traceability: TOOL_REQ__EXAMPLE_ID
1322
14-
For an example, look at the attribute ``source_code_link``
15-
of :need:`tool_req__docs_common_attr_title`.
23+
For other languages (C++, Rust, etc.), use the appropriate comment syntax.
24+
25+
Scanning Source Code for Links
26+
------------------------------
27+
28+
In you ``BUILD`` files, you specify which source files to scan
29+
with ``filegroup`` or ``glob`` or whatever Bazel mechanism you prefer.
30+
Finally, pass the scan results to the ``docs`` rule as ``scan_code`` attribute.
31+
32+
.. code-block:: starlark
33+
:emphasize-lines: 15
34+
:linenos:
35+
36+
filegroup(
37+
name = "some_sources",
38+
srcs = [
39+
"foo.py",
40+
"bar.cpp",
41+
"data.yaml",
42+
] + glob(["subdir/**/.py"]),
43+
)
44+
45+
docs(
46+
data = [
47+
"@score_process//:needs_json",
48+
],
49+
source_dir = "docs",
50+
scan_code = [":some_sources"],
51+
)

scripts/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Scripts
2+
3+
The scripts directory is only for local development (linters) so far.

scripts_bazel/BUILD

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# *******************************************************************************
2+
# Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# This program and the accompanying materials are made available under the
8+
# terms of the Apache License Version 2.0 which is available at
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# SPDX-License-Identifier: Apache-2.0
12+
# *******************************************************************************
13+
14+
load("@aspect_rules_py//py:defs.bzl", "py_binary")
15+
load("@pip_process//:requirements.bzl", "all_requirements")
16+
17+
filegroup(
18+
name = "sources",
19+
srcs = glob(["**/*.py"]),
20+
visibility = ["//visibility:public"],
21+
)
22+
23+
py_binary(
24+
name = "generate_sourcelinks",
25+
srcs = ["generate_sourcelinks_cli.py"],
26+
main = "generate_sourcelinks_cli.py",
27+
visibility = ["//visibility:public"],
28+
deps = [
29+
"//src/extensions/score_source_code_linker",
30+
] + all_requirements,
31+
)
32+
33+
py_binary(
34+
name = "merge_sourcelinks",
35+
srcs = ["merge_sourcelinks.py"],
36+
main = "merge_sourcelinks.py",
37+
visibility = ["//visibility:public"],
38+
)

scripts_bazel/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Scripts Bazel
2+
3+
This folder contains executables to be used within Bazel rules.

0 commit comments

Comments
 (0)