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
86 changes: 86 additions & 0 deletions bzl/bundle_rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,48 @@ DocsBundleInfo = provider(
},
)

CodeTargetSourcesInfo = provider(
doc = "Source files collected from an implementation target and its dependencies.",
fields = {
"sources": "Depset of direct and transitive source files.",
},
)

def _source_files_from_attributes(ctx):
"""Return files explicitly declared through source or header attributes."""
source_files = []
for attribute_name in ["srcs", "hdrs", "textual_hdrs"]:
if not hasattr(ctx.rule.attr, attribute_name):
continue
for source in getattr(ctx.rule.attr, attribute_name):
if type(source) == "File":
source_files.append(source)
else:
source_files.extend(source[DefaultInfo].files.to_list())
return source_files

def _collect_code_target_sources_impl(target, ctx):
"""Collect source files from an implementation target and its ``deps`` tree."""
dependency_sources = []
if hasattr(ctx.rule.attr, "deps"):
dependency_sources = [
dependency[CodeTargetSourcesInfo].sources
for dependency in ctx.rule.attr.deps
]
return [CodeTargetSourcesInfo(
sources = depset(
direct = _source_files_from_attributes(ctx),
transitive = dependency_sources,
),
)]

_collect_code_target_sources = aspect(
implementation = _collect_code_target_sources_impl,
attr_aspects = ["deps"],
provides = [CodeTargetSourcesInfo],
doc = "Collects sources recursively through standard implementation dependencies.",
)

def _parent_index_docname(mount_at):
"""Choose the page that links to a bundled subtree by default."""
parent = mount_at.rsplit("/", 1)[0] if "/" in mount_at else ""
Expand Down Expand Up @@ -321,3 +363,47 @@ def merge_bundle_sourcelinks(name, bundle, known_good = None, visibility = None)
known_good = known_good,
visibility = visibility,
)

def _code_targets_sourcelinks_impl(ctx):
"""Generate one source-link cache for the implementation targets of a bundle."""
source_files = depset(transitive = [
target[CodeTargetSourcesInfo].sources
for target in ctx.attr.code_targets
])
if not source_files.to_list():
fail("code_targets must declare source files through filegroups, srcs, hdrs, or textual_hdrs")

output = ctx.actions.declare_file(ctx.label.name + ".json")
arguments = ctx.actions.args()
arguments.add("--output", output.path)
arguments.add_all(source_files)
ctx.actions.run(
executable = ctx.executable._generate_sourcelinks,
arguments = [arguments],
inputs = source_files,
outputs = [output],
mnemonic = "GenerateCodeTargetSourcelinks",
)
return [DefaultInfo(files = depset([output]))]

_code_targets_sourcelinks = rule(
implementation = _code_targets_sourcelinks_impl,
attrs = {
"code_targets": attr.label_list(aspects = [_collect_code_target_sources]),
"_generate_sourcelinks": attr.label(
default = Label("//scripts_bazel:generate_sourcelinks"),
cfg = "exec",
executable = True,
),
},
doc = "Generates source-code links from implementation target source files.",
)

def generate_code_target_sourcelinks(name, code_targets, visibility = None):
"""Create a cached source-link JSON file for one documentation bundle."""
_code_targets_sourcelinks(
name = name,
code_targets = code_targets,
visibility = visibility,
)
return ":" + name
24 changes: 20 additions & 4 deletions docs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ load(
"create_bundle",
"merge_bundle_sourcelinks",
"external_docs_runfiles",
"generate_code_target_sourcelinks",
)
load(
"@score_docs_as_code//:bzl/mount_rules.bzl",
"create_mounts_manifest",
)

def docs_bundle(name, source_dir = None, entry_doc = "index", bundles = [], scan_code = [], visibility = None, **kwargs):
def docs_bundle(name, source_dir = None, entry_doc = "index", bundles = [], scan_code = [], code_targets = [], visibility = None, **kwargs):
"""A docs bundle, optionally composed of others.

Args:
Expand All @@ -76,8 +77,11 @@ def docs_bundle(name, source_dir = None, entry_doc = "index", bundles = [], scan
"mount_at": <where it shall me mounted>,
"attach_to": <optional document to attach the bundle to; for a bundle root it defaults to the mount_at parent's index>
}.
scan_code: Source-code targets to scan for source-code links owned by this
bundle.
scan_code: Deprecated. Explicit source files or filegroups to scan for
source-code links. Use `code_targets` for implementation targets.
code_targets: Implementation targets or filegroups to scan for source-code
links. Implementation target source files and their dependencies
are collected recursively; filegroups expand to their files.
visibility: Target visibility.
**kwargs: Additional attributes forwarded to the underlying rule.
"""
Expand All @@ -88,6 +92,12 @@ def docs_bundle(name, source_dir = None, entry_doc = "index", bundles = [], scan
sourcelinks_name = name + "_sourcelinks_json"
_sourcelinks_json(name = sourcelinks_name, srcs = scan_code)
sourcelinks = [":" + sourcelinks_name]
if code_targets:
code_targets_sourcelinks = generate_code_target_sourcelinks(
name = name + "_code_targets_sourcelinks_json",
code_targets = code_targets,
)
sourcelinks.append(code_targets_sourcelinks)

# Store the source directory relative to the workspace so bundle consumers
# can locate the original files without copying them.
Expand Down Expand Up @@ -142,6 +152,7 @@ def docs(
data = [],
deps = [],
scan_code = [],
code_targets = [],
test_sources = [],
known_good = None,
metamodel = None,
Expand All @@ -155,7 +166,11 @@ def docs(
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.
scan_code: List of code targets to scan for source code links.
scan_code: Deprecated. Explicit source files or filegroups to scan for source
code links. Use `code_targets` for implementation targets.
code_targets: Implementation targets or filegroups to scan for source code
links. Implementation targets are scanned recursively; filegroups
expand to their files.
test_sources: Optional list of repo-relative directory paths which will be used to filter testcases for documentation generation.
When empty (default), all testcases found in `bazel-testlogs` will be used.
known_good: Optional label to a "known good" JSON file for source links.
Expand Down Expand Up @@ -218,6 +233,7 @@ def docs(
entry_doc = "index",
bundles = bundles,
scan_code = scan_code,
code_targets = code_targets,
visibility = ["//visibility:public"],
)
merge_bundle_sourcelinks(
Expand Down
2 changes: 1 addition & 1 deletion docs/how-to/dashboards_and_quality_gates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Recommended Rollout
For a new consumer repository:

1. Start with local-only metrics.
2. Enable ``scan_code`` and verify ``source_code_link`` coverage first.
2. Enable ``code_targets`` and verify ``source_code_link`` coverage first.
3. Add test metadata and verify ``testlink`` coverage.
4. Introduce modest thresholds in CI.
5. Raise thresholds over time as the repository matures.
Expand Down
3 changes: 2 additions & 1 deletion docs/how-to/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ The `docs()` macro accepts the following arguments:
| `source_dir` | Directory of documentation source files (RST, MD) | Yes |
| `data` | List of `needs_json` targets that should be included in the documentation | No |
| `deps` | Additional Bazel Python dependencies | No |
| `scan_code` | Source code targets to scan for traceability tags | No |
| `code_targets` | Implementation targets to scan recursively for traceability tags | No |
| `scan_code` | Deprecated: explicit source files or filegroups to scan | No |
| `known_good` | Label to a "known good" JSON file for source links | No |
| `metamodel` | Label to a custom `metamodel.yaml` that replaces the default metamodel | No |

Expand Down
30 changes: 20 additions & 10 deletions docs/how-to/source_to_doc_links.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,37 @@ For other languages (C++, Rust, etc.), use the appropriate comment syntax.
Scanning Source Code for Links
------------------------------

In your ``BUILD`` files, you specify which source files to scan
with ``filegroup`` or ``glob`` or whatever Bazel mechanism you prefer.
Finally, pass the scan results to the ``docs`` rule as ``scan_code`` attribute.
In your ``BUILD`` files, pass the implementation targets to the ``docs`` rule
as ``code_targets``. Their ``srcs``, ``hdrs``, and ``textual_hdrs`` are scanned,
including those of their ``deps`` recursively. This means that a
``cc_executable`` also covers source files from the ``cc_library`` targets it
uses. You may also pass filegroups; their files are scanned directly.

.. code-block:: starlark
:emphasize-lines: 15
:emphasize-lines: 14
:linenos:

filegroup(
name = "some_sources",
cc_library(
name = "some_library",
srcs = [
"foo.py",
"bar.cpp",
"data.yaml",
] + glob(["subdir/**/*.py"]),
],
)

cc_executable(
name = "some_application",
srcs = ["main.cpp"],
deps = [":some_library"],
)

docs(
data = [
"@score_process//:needs_json",
],
source_dir = "docs",
scan_code = [":some_sources"],
code_targets = [":some_application"],
)

The older ``scan_code`` parameter remains available for existing configurations
that explicitly provide files or filegroups, but it is deprecated. Prefer
``code_targets`` for new configurations.
8 changes: 6 additions & 2 deletions docs/internals/extensions/source_code_linker.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ The Bazel parts are responsible for producing the **intermediate caches** that t
(step-1-per-repository-cache-generation)=
#### Step 1: Per-repository cache generation

All files provided via the `scan_code` attribute to the docs macro will be scanned for the requirement tags.
A *per repository JSON cache* will be generated and saved.
Each `docs_bundle` scans the source files selected by its `code_targets`
attribute. The targets' `srcs`, `hdrs`, and `textual_hdrs`, including their
dependencies, are collected recursively. A *per bundle JSON cache* is then
generated and saved; Bazel reuses it until its source inputs change.
`scan_code` remains available for explicit files or filegroups, but is
deprecated.
This script `scripts_bazel/generate_sourcelinks_cli.py` finds all codelinks per file, and gathers them into
one JSON cache per repository.
It also adds metadata to each needlink that is needed in further steps.
Expand Down
28 changes: 24 additions & 4 deletions docs/reference/bazel_macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,18 @@ Minimal example (root ``BUILD``)
If you don't provide the necessary Sphinx packages,
this function adds its own (but checks for conflicts).

- ``scan_code`` (list of bazel labels)
Source code targets to scan for traceability tags (``req-Id:`` annotations).
Used to generate the source-code-link JSON that maps tags back to source files.
- ``code_targets`` (list of Bazel labels)
Implementation targets or filegroups to scan for traceability tags
(``req-Id:`` annotations). Implementation target ``srcs``, ``hdrs``, and
``textual_hdrs`` are collected through their ``deps`` recursively; filegroups
expand to their files. Each documentation bundle produces one cache for its
declared targets; Bazel reuses that cache while its inputs are unchanged. The
generated JSON is supplied to ``live_preview`` just like a normal documentation
build.

- ``scan_code`` (list of Bazel labels, deprecated)
Explicit source files or filegroups to scan. Use ``code_targets`` for
implementation targets; it follows their dependencies automatically.

- ``metamodel`` (bazel label, optional)
Path to a custom ``metamodel.yaml`` file.
Expand Down Expand Up @@ -121,7 +130,7 @@ site).
visibility = ["//visibility:public"],
)

Signature: ``docs_bundle(name, source_dir = None, entry_doc = "index", bundles = [], scan_code = [], visibility = None)``.
Signature: ``docs_bundle(name, source_dir = None, entry_doc = "index", bundles = [], scan_code = [], code_targets = [], visibility = None)``.

- ``source_dir`` (string, optional)
Directory holding the bundle's own doc sources. It is globbed the same way as
Expand Down Expand Up @@ -162,6 +171,17 @@ Signature: ``docs_bundle(name, source_dir = None, entry_doc = "index", bundles =
same bundle be mounted at different locations by different consumers without
changing its canonical entry page.

- ``code_targets`` (list of Bazel labels, optional)
Implementation targets or filegroups to scan for requirement tags.
Implementation target ``srcs``, ``hdrs``, and ``textual_hdrs`` are collected
recursively from their ``deps``; filegroups expand to their files. The bundle
owns one cached scan result; Bazel only regenerates it when its collected source
inputs change.

- ``scan_code`` (list of Bazel labels, deprecated)
Explicit source files or filegroups to scan. Prefer ``code_targets`` for
implementation targets.

Edge cases
----------

Expand Down
Loading
Loading