Skip to content
Merged
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
10 changes: 10 additions & 0 deletions bazel/rules/rules_score/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ sphinx_docs_library(
strip_prefix = "validation/core/docs/specifications/",
)

# PlantUML diagrams referenced by clickable_plantuml/README.md
# (relative links like `docs/assets/clickable_plantuml_pipeline.puml`).
sphinx_docs_library(
name = "clickable_plantuml_doc_assets",
srcs = ["//plantuml/sphinx/clickable_plantuml:doc_assets"],
prefix = "bazel/rules/rules_score/docs/tool_reference/",
strip_prefix = "plantuml/sphinx/clickable_plantuml/",
)

# Source files from the standalone minimal example, mapped into the Sphinx
# source tree so that general.rst can reference them via literalinclude.
sphinx_docs_library(
Expand Down Expand Up @@ -194,6 +203,7 @@ sphinx_module(
":minimal_example_srcs",
":tool_readme_ai_checker_assets",
":validation_specs",
":clickable_plantuml_doc_assets",
],
index = "docs/index.rst",
renamed_srcs = {
Expand Down
3 changes: 2 additions & 1 deletion bazel/rules/rules_score/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ architectural_design(
**`bazel build`** — runs `puml_parser` on every `.puml` file, producing:
- a `.fbs.bin` FlatBuffers binary (diagram AST) — consumed by validation/core checks
- a `.lobster` traceability file (Interface elements only) — consumed by LOBSTER
- a `plantuml_links.json` — consumed by the `clickable_plantuml` Sphinx extension
- a `validation.log` from the `architectural-design` validation profile
- a `.idmap.json` sidecar — consumed by the `clickable_plantuml` Sphinx extension
to resolve cross-diagram links based on element *defines/references* roles

Diagrams in `public_api` are classified separately so their lobster items flow
through `public_api_lobster_files` for failure-mode traceability.
Expand Down
2 changes: 1 addition & 1 deletion bazel/rules/rules_score/docs/_assets/tooling_chain.puml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ rectangle "sphinx_module" <<rule>> as sphinx
' ── Tools ─────────────────────────────────────────────────────────────────────
rectangle "**TRLC**\ntrlc parser + trlc_rst\n(.trlc/.rsl -> .rst; TRLCRST lib)" <<tool>> as trlc
rectangle "**rst_to_trlc**\n(.rst -> .trlc)" <<own>> as r2t
rectangle "**PlantUML Parser**\nparser + linker (Rust)\n(.puml -> .fbs.bin + .lobster)" <<tool>> as puml
rectangle "**PlantUML Parser**\npuml_cli (Rust)\n(.puml -> .fbs.bin + .lobster + .idmap.json)" <<tool>> as puml
rectangle "**puml_cli** (FTA mode)\ninline metamodel + extract\n(.puml -> inlined .puml +\nfta_chains.json + root_causes.lobster)" <<own>> as fta
rectangle "**fmea_assembler**\nTRLCRST page build\n(.trlc + chains -> fmea.rst)" <<own>> as asm
rectangle "**Lobster**\nlobster-trlc / -report /\n-ci-report / gtest_report" <<tool>> as lob
Expand Down
20 changes: 15 additions & 5 deletions bazel/rules/rules_score/docs/tooling_architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,14 @@ are rendered under :doc:`tool_reference/index`.
- Converts RST requirement directives (``feat_req``, ``comp_req``, …) into
``.trlc`` records so requirements can be authored in either RST or TRLC.
* - **PlantUML Parser**
- ``@score_tooling//plantuml/parser:parser`` (Rust) + ``:linker``
- ``@score_tooling//plantuml/parser:parser`` (Rust)
- ``architectural_design``, ``unit_design``
- Parses ``.puml`` diagrams into a FlatBuffers AST (``.fbs.bin``, one
``root_type`` per diagram kind) and extracts interface ``.lobster``
items. The **linker** merges the FlatBuffers into ``plantuml_links.json``
for the ``clickable_plantuml`` Sphinx extension. Rejects syntactically
invalid diagrams with a non-zero exit code.
``root_type`` per diagram kind), extracts interface ``.lobster`` items,
and emits ``.idmap.json`` sidecars recording the *defines/references*
roles of each element. The ``clickable_plantuml`` Sphinx extension reads
these sidecars to resolve cross-diagram links without a separate linker
step. Rejects syntactically invalid diagrams with a non-zero exit code.
* - **puml_cli (FTA mode)**
- ``//plantuml/parser/puml_cli`` ``--fta-output-dir`` (Rust; FTA model in
the ``puml_fta`` crate)
Expand All @@ -121,6 +122,15 @@ are rendered under :doc:`tool_reference/index`.
parse: an overview table, one section per failure mode (detail + inline
fault tree + that chain's control measures), and trailing "Unlinked"
sections so nothing is dropped.
* - **safety_analysis_tools**
- ``//bazel/rules/rules_score:safety_analysis_tools``
(``src/safety_analysis_tools.py``, local)
- ``fmea``
- Assembles the failure-mode-centric ``fmea.rst`` from ``fta_chains.json``
plus the FailureMode / ControlMeasure records in one in-process TRLC
parse: an overview table, one section per failure mode (detail + inline
fault tree + that chain's control measures), and trailing "Unlinked"
sections so nothing is dropped.
* - **Lobster**
- ``@lobster//`` : ``lobster-trlc``, ``lobster-report``,
``lobster-ci-report``, ``lobster-html-report``, ``gtest_report``,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@

@startuml unit_2_class_diagram

' Foo is owned and elaborated by unit_1 (see unit_1_class_diagram.puml); it is
' shown here only as a memberless collaborator stub, purely for context on
' Bar's composition relationship.
namespace unit_1 {
class Foo <<final>>{
--
+ GetNumber() : uint8_t
+ SetNumber(value : uint8_t) : void
}
class Foo <<final>>
}

namespace unit_2 {
Expand Down
80 changes: 40 additions & 40 deletions bazel/rules/rules_score/private/architectural_design.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,27 @@ load("//bazel/rules/rules_score/private:verbosity.bzl", "VERBOSITY_ATTR", "get_l
# ============================================================================

def _run_puml_parser(ctx, puml_file):
"""Run the PlantUML parser on a single .puml file to produce a FlatBuffers binary
and a lobster traceability file.
"""Run the PlantUML parser on a single .puml file to produce a FlatBuffers binary,
a lobster traceability file, and an idmap sidecar.

The diagram type is auto-detected by the parser and encoded in the
FlatBuffers schema (each diagram type uses its own root_type).
Lobster output is produced in-process for component diagrams.

``--source-name`` is passed as ``puml_file.short_path`` so the ``source``
field embedded in the fbs/lobster/idmap outputs is a stable,
workspace-relative path. This is required by the `clickable_plantuml`
Sphinx extension, which matches idmap ``source`` keys against paths it
derives from Sphinx's own doctree — an unstable value (e.g. a sandbox
exec-root-relative path) would silently break cross-diagram linking.
`BUILD_WORKSPACE_DIRECTORY`, which `puml_cli` otherwise falls back on, is
only set for `bazel run`, never for build actions like this one.

Args:
ctx: Rule context
puml_file: The .puml File object to parse
Returns:
Tuple of (fbs_output, lobster_output) declared output Files.
Tuple of (fbs_output, lobster_output, idmap_output) declared output Files.
"""
file_stem = puml_file.basename.rsplit(".", 1)[0]
fbs_output = ctx.actions.declare_file(
Expand All @@ -52,10 +61,13 @@ def _run_puml_parser(ctx, puml_file):
lobster_output = ctx.actions.declare_file(
"{}/{}.lobster".format(ctx.label.name, file_stem),
)
idmap_output = ctx.actions.declare_file(
"{}/{}.idmap.json".format(ctx.label.name, file_stem),
)

ctx.actions.run(
inputs = [puml_file],
outputs = [fbs_output, lobster_output],
outputs = [fbs_output, lobster_output, idmap_output],
executable = ctx.executable._puml_parser,
arguments = [
"--file",
Expand All @@ -64,13 +76,17 @@ def _run_puml_parser(ctx, puml_file):
fbs_output.dirname,
"--lobster-output-dir",
lobster_output.dirname,
"--idmap-output-dir",
idmap_output.dirname,
"--source-name",
puml_file.short_path,
"--log-level",
get_log_level(ctx),
],
progress_message = "Parsing PlantUML diagram: %s" % puml_file.short_path,
)

return fbs_output, lobster_output
return fbs_output, lobster_output, idmap_output

def _parse_puml_diagrams(ctx, files):
"""Run the PlantUML parser on all .puml/.plantuml files in a list.
Expand All @@ -79,16 +95,18 @@ def _parse_puml_diagrams(ctx, files):
ctx: Rule context
files: List of File objects
Returns:
Tuple of (fbs_outputs, lobster_outputs) lists of generated Files.
Tuple of (fbs_outputs, lobster_outputs, idmap_outputs) lists of generated Files.
"""
fbs_outputs = []
lobster_outputs = []
idmap_outputs = []
for f in files:
if f.extension in ("puml", "plantuml"):
fbs, lobster = _run_puml_parser(ctx, f)
fbs, lobster, idmap = _run_puml_parser(ctx, f)
fbs_outputs.append(fbs)
lobster_outputs.append(lobster)
return fbs_outputs, lobster_outputs
idmap_outputs.append(idmap)
return fbs_outputs, lobster_outputs, idmap_outputs

def _run_validation(ctx, component_fbs_files, sequence_fbs_files, public_api_fbs_files, internal_api_fbs_files):
"""Run the architectural-design validation profile.
Expand Down Expand Up @@ -139,10 +157,10 @@ def _architectural_design_impl(ctx):

# Parse each architectural view separately so each provider field carries
# the flatbuffers for its own category.
static_fbs_list, static_lobster_list = _parse_puml_diagrams(ctx, ctx.files.static)
dynamic_fbs_list, dynamic_lobster_list = _parse_puml_diagrams(ctx, ctx.files.dynamic)
public_api_fbs_list, public_api_lobster_list = _parse_puml_diagrams(ctx, ctx.files.public_api)
internal_api_fbs_list, _internal_api_lobster_list = _parse_puml_diagrams(ctx, ctx.files.internal_api)
static_fbs_list, static_lobster_list, static_idmap_list = _parse_puml_diagrams(ctx, ctx.files.static)
dynamic_fbs_list, dynamic_lobster_list, dynamic_idmap_list = _parse_puml_diagrams(ctx, ctx.files.dynamic)
public_api_fbs_list, public_api_lobster_list, public_api_idmap_list = _parse_puml_diagrams(ctx, ctx.files.public_api)
internal_api_fbs_list, _internal_api_lobster_list, internal_api_idmap_list = _parse_puml_diagrams(ctx, ctx.files.internal_api)

static_fbs = depset(static_fbs_list)
dynamic_fbs = depset(dynamic_fbs_list)
Expand All @@ -155,29 +173,17 @@ def _architectural_design_impl(ctx):
transitive = [depset(ctx.files.static), depset(ctx.files.dynamic), depset(ctx.files.public_api), depset(ctx.files.internal_api)],
)

# Run the linker on all generated .fbs.bin files to produce a
# plantuml_links.json for the clickable_plantuml Sphinx extension.
all_fbs_files = static_fbs.to_list() + dynamic_fbs.to_list() + public_api_fbs.to_list() + internal_api_fbs.to_list()
plantuml_links_json = ctx.actions.declare_file(
"{}/plantuml_links.json".format(ctx.label.name),
# All idmap sidecars (across static/dynamic/public_api/internal_api) are
# staged into the sphinx sources so the `clickable_plantuml` extension can
# discover them (it scans `srcdir` recursively for `*.idmap.json`) and
# resolve cross-diagram links — including component diagrams linking to
# the class diagrams that elaborate their public/internal API interfaces.
all_idmap_files = depset(
static_idmap_list + dynamic_idmap_list + public_api_idmap_list + internal_api_idmap_list,
)
if all_fbs_files:
ctx.actions.run(
inputs = all_fbs_files,
outputs = [plantuml_links_json],
executable = ctx.executable._linker,
arguments = ["--fbs-files"] + [f.path for f in all_fbs_files] + ["--output", plantuml_links_json.path, "--log-level", get_log_level(ctx)],
progress_message = "Generating PlantUML links JSON for %s" % ctx.label.name,
)
else:
ctx.actions.write(
output = plantuml_links_json,
content = '{"links":[]}',
)

sphinx_files = depset(
[plantuml_links_json],
transitive = [all_source_files],
transitive = [all_idmap_files, all_source_files],
)

# Generate a thin RST wrapper for every .puml diagram so it appears as a
Expand Down Expand Up @@ -210,7 +216,7 @@ def _architectural_design_impl(ctx):
public_api_lobster_files = public_api_lobster,
validation_logs = [validation_log],
),
# Source diagram files + plantuml_links.json for the sphinx documentation build
# Source diagram files + *.idmap.json sidecars for the sphinx documentation build
SphinxSourcesInfo(
srcs = sphinx_srcs,
deps = sphinx_srcs,
Expand Down Expand Up @@ -258,13 +264,7 @@ def _architectural_design_attrs():
default = Label("@score_tooling//plantuml/parser:parser"),
executable = True,
cfg = "exec",
doc = "PlantUML parser tool that generates FlatBuffers from .puml files",
),
"_linker": attr.label(
default = Label("@score_tooling//plantuml/parser:linker"),
executable = True,
cfg = "exec",
doc = "Tool that generates plantuml_links.json from FlatBuffers diagram outputs",
doc = "PlantUML parser tool that generates FlatBuffers/lobster/idmap files from .puml files",
),
"_puml_rst_template": attr.label(
default = Label("//bazel/rules/rules_score:templates/puml_diagram.template.rst"),
Expand Down
56 changes: 45 additions & 11 deletions bazel/rules/rules_score/private/unit_design.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,44 +32,68 @@ load("//bazel/rules/rules_score/private:verbosity.bzl", "VERBOSITY_ATTR", "get_l
# ============================================================================

def _run_puml_parser(ctx, puml_file):
"""Run the PlantUML parser on one .puml file and emit a FlatBuffers file."""
"""Run the PlantUML parser on one .puml file and emit a FlatBuffers file
and an idmap sidecar.

``--source-name`` is passed as ``puml_file.short_path`` (mirroring
architectural_design.bzl's ``_run_puml_parser``) so the ``source`` field
embedded in the idmap output is a stable, workspace-relative path that
the `clickable_plantuml` Sphinx extension can match against paths it
derives from Sphinx's own doctree. Without this, unit design diagrams
(e.g. a unit's class diagram) can never become the "definition" side of
a cross-diagram link, so references to that unit from architectural
design diagrams (static or dynamic) never resolve.

Returns:
Tuple of (fbs_output, idmap_output) declared output Files.
"""
file_stem = puml_file.basename.rsplit(".", 1)[0]
fbs_output = ctx.actions.declare_file(
"{}/{}.fbs.bin".format(ctx.label.name, file_stem),
)
idmap_output = ctx.actions.declare_file(
"{}/{}.idmap.json".format(ctx.label.name, file_stem),
)

ctx.actions.run(
inputs = [puml_file],
outputs = [fbs_output],
outputs = [fbs_output, idmap_output],
executable = ctx.executable._puml_parser,
arguments = [
"--file",
puml_file.path,
"--fbs-output-dir",
fbs_output.dirname,
"--idmap-output-dir",
idmap_output.dirname,
"--source-name",
puml_file.short_path,
"--log-level",
get_log_level(ctx),
],
progress_message = "Parsing Unit Design PlantUML diagram: %s" % puml_file.short_path,
)

return fbs_output
return fbs_output, idmap_output

def _parse_puml_diagrams(ctx, files):
"""Run parser on all .puml/.plantuml files from a list and return fbs outputs."""
"""Run parser on all .puml/.plantuml files from a list and return fbs/idmap outputs."""
fbs_outputs = []
idmap_outputs = []
for f in files:
if f.extension in ("puml", "plantuml"):
fbs_outputs.append(_run_puml_parser(ctx, f))
return fbs_outputs
fbs, idmap = _run_puml_parser(ctx, f)
fbs_outputs.append(fbs)
idmap_outputs.append(idmap)
return fbs_outputs, idmap_outputs

def _unit_design_impl(ctx):
"""Implementation for unit_design rule.

Collects unit design artifacts (RST documents and PlantUML diagrams) and
provides them through the UnitDesignInfo and SphinxSourcesInfo providers.
PlantUML files are passed through for Sphinx rendering and parsed into
FlatBuffers binaries.
FlatBuffers binaries and idmap sidecars.

Args:
ctx: Rule context
Expand All @@ -82,8 +106,18 @@ def _unit_design_impl(ctx):
transitive = [depset(ctx.files.static), depset(ctx.files.dynamic)],
)

static_fbs = depset(_parse_puml_diagrams(ctx, ctx.files.static))
dynamic_fbs = depset(_parse_puml_diagrams(ctx, ctx.files.dynamic))
static_fbs_list, static_idmap_list = _parse_puml_diagrams(ctx, ctx.files.static)
dynamic_fbs_list, dynamic_idmap_list = _parse_puml_diagrams(ctx, ctx.files.dynamic)
static_fbs = depset(static_fbs_list)
dynamic_fbs = depset(dynamic_fbs_list)

# Stage idmap sidecars into the sphinx sources alongside the source
# diagrams so `clickable_plantuml` (which scans `srcdir` recursively for
# `*.idmap.json`) can discover them - see architectural_design.bzl's
# identical `all_idmap_files`/`sphinx_files` handling.
sphinx_srcs = depset(
transitive = [depset(static_idmap_list + dynamic_idmap_list), all_source_files],
)

return [
DefaultInfo(files = all_source_files),
Expand All @@ -93,8 +127,8 @@ def _unit_design_impl(ctx):
name = ctx.label.name,
),
SphinxSourcesInfo(
srcs = all_source_files,
deps = all_source_files,
srcs = sphinx_srcs,
deps = sphinx_srcs,
aux_srcs = depset(),
),
]
Expand Down
Loading
Loading