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
50 changes: 45 additions & 5 deletions bazel/rules/rules_score/private/architectural_design.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,20 @@ def _colocate_puml_with_wrapper(ctx, puml_files, output_dir):
same-directory symlinked copies.
"""
colocated = []
pkg_prefix = ctx.label.package + "/" if ctx.label.package else ""
for f in puml_files:
if f.extension not in ("puml", "plantuml"):
colocated.append(f)
continue
copy = ctx.actions.declare_file(
"{}/{}".format(output_dir, f.basename),
)

rel_dir = ""
if pkg_prefix and f.short_path.startswith(pkg_prefix):
rel_path = f.short_path[len(pkg_prefix):]
if "/" in rel_path:
rel_dir = rel_path.rsplit("/", 1)[0]

out_path = "{}/{}/{}".format(output_dir, rel_dir, f.basename) if rel_dir else "{}/{}".format(output_dir, f.basename)
copy = ctx.actions.declare_file(out_path)
ctx.actions.symlink(output = copy, target_file = f)
colocated.append(copy)
return colocated
Expand Down Expand Up @@ -240,12 +247,41 @@ def _architectural_design_impl(ctx):

# Generate a thin RST wrapper for every .puml diagram so it appears as a
# toctree entry in the dependable_element index.
rst_wrappers = make_puml_rst_wrappers(
static_wrappers = make_puml_rst_wrappers(
ctx,
ctx.files.static,
ctx.label.name,
ctx.file._puml_rst_template,
)
dynamic_wrappers = make_puml_rst_wrappers(
ctx,
ctx.files.dynamic,
ctx.label.name,
ctx.file._puml_rst_template,
)
public_api_wrappers = make_puml_rst_wrappers(
ctx,
ctx.files.static + ctx.files.dynamic + ctx.files.public_api + ctx.files.internal_api,
ctx.files.public_api,
ctx.label.name,
ctx.file._puml_rst_template,
)
internal_api_wrappers = make_puml_rst_wrappers(
ctx,
ctx.files.internal_api,
ctx.label.name,
ctx.file._puml_rst_template,
)

rst_wrappers = static_wrappers + dynamic_wrappers + public_api_wrappers + internal_api_wrappers

def _get_doc_files(files, wrappers):
docs = [f for f in files if f.extension in ("rst", "md")]
return depset(docs + wrappers)

static_doc_files = _get_doc_files(ctx.files.static, static_wrappers)
dynamic_doc_files = _get_doc_files(ctx.files.dynamic, dynamic_wrappers)
public_api_doc_files = _get_doc_files(ctx.files.public_api, public_api_wrappers)
internal_api_doc_files = _get_doc_files(ctx.files.internal_api, internal_api_wrappers)

validation_log = _run_validation(
ctx,
Expand All @@ -264,6 +300,10 @@ def _architectural_design_impl(ctx):
dynamic = dynamic_fbs,
public_api = public_api_fbs,
internal_api = internal_api_fbs,
static_doc_files = static_doc_files,
dynamic_doc_files = dynamic_doc_files,
public_api_doc_files = public_api_doc_files,
internal_api_doc_files = internal_api_doc_files,
name = ctx.label.name,
public_api_lobster_files = public_api_lobster,
validation_logs = [validation_log],
Expand Down
229 changes: 224 additions & 5 deletions bazel/rules/rules_score/private/dependable_element.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,204 @@ def _process_artifact_files(ctx, artifact_name, label):

return (output_files, index_refs)

def _process_architectural_design_files(ctx, label):
"""Process all files from an architectural_design label, returning output_files and classified refs.

Returns:
Tuple of (output_files, static_refs, dynamic_refs, public_api_refs, internal_api_refs, unclassified_refs)
"""
output_files = []
static_refs = []
dynamic_refs = []
public_api_refs = []
internal_api_refs = []
unclassified_refs = []

all_files = _get_sphinx_files(label)
doc_files = _filter_doc_files(all_files)

aux_files = []
if label[SphinxSourcesInfo].aux_srcs:
aux_files = label[SphinxSourcesInfo].aux_srcs.to_list()

if not doc_files and not aux_files:
return (output_files, static_refs, dynamic_refs, public_api_refs, internal_api_refs, unclassified_refs)

srcs_paths = {f.path: True for f in label[SphinxSourcesInfo].srcs.to_list()}
common_dir = _find_common_directory(doc_files + aux_files)

static_paths = {}
dynamic_paths = {}
public_api_paths = {}
internal_api_paths = {}

if ArchitecturalDesignInfo in label:
info = label[ArchitecturalDesignInfo]
if hasattr(info, "static_doc_files") and info.static_doc_files:
static_paths = {f.path: True for f in info.static_doc_files.to_list()}
if hasattr(info, "dynamic_doc_files") and info.dynamic_doc_files:
dynamic_paths = {f.path: True for f in info.dynamic_doc_files.to_list()}
if hasattr(info, "public_api_doc_files") and info.public_api_doc_files:
public_api_paths = {f.path: True for f in info.public_api_doc_files.to_list()}
if hasattr(info, "internal_api_doc_files") and info.internal_api_doc_files:
internal_api_paths = {f.path: True for f in info.internal_api_doc_files.to_list()}

for artifact_file in doc_files:
relative_path = _compute_relative_path(artifact_file, common_dir)

if _is_document_file(artifact_file) and artifact_file.path not in srcs_paths:
continue

output_file = _create_artifact_symlink(
ctx,
"architectural_design",
artifact_file,
relative_path,
)
output_files.append(output_file)

if _is_document_file(artifact_file):
doc_path = "architectural_design/" + relative_path
doc_ref = doc_path.removesuffix(".rst").removesuffix(".md")
if artifact_file.path in static_paths:
static_refs.append(doc_ref)
elif artifact_file.path in dynamic_paths:
dynamic_refs.append(doc_ref)
elif artifact_file.path in public_api_paths:
public_api_refs.append(doc_ref)
elif artifact_file.path in internal_api_paths:
internal_api_refs.append(doc_ref)
else:
unclassified_refs.append(doc_ref)

for artifact_file in aux_files:
relative_path = _compute_relative_path(artifact_file, common_dir)
output_file = _create_artifact_symlink(
ctx,
"architectural_design",
artifact_file,
relative_path,
)
output_files.append(output_file)

return (output_files, static_refs, dynamic_refs, public_api_refs, internal_api_refs, unclassified_refs)

def _generate_software_arch_page(
ctx,
feature_req_refs,
static_refs,
dynamic_refs,
public_api_refs,
internal_api_refs,
unclassified_refs,
dependability_refs,
output_files):
"""Generate software_arch.rst page with section subheadings when categorized entries exist."""
has_categories = bool(
feature_req_refs or static_refs or dynamic_refs or
public_api_refs or internal_api_refs,
)

if not has_categories and not unclassified_refs and not dependability_refs:
return None

title = "Software Architectural Level"
underline = "-" * len(title)
lines = [title, underline, ""]

if has_categories:
if feature_req_refs:
lines.extend([
".. toctree::",
" :maxdepth: 1",
"",
" " + "\n ".join(feature_req_refs),
"",
])
if static_refs:
lines.extend([
"Static Design",
"~~~~~~~~~~~~~",
"",
".. toctree::",
" :maxdepth: 1",
"",
" " + "\n ".join(static_refs),
"",
])
if dynamic_refs:
lines.extend([
"Dynamic Design",
"~~~~~~~~~~~~~~",
"",
".. toctree::",
" :maxdepth: 1",
"",
" " + "\n ".join(dynamic_refs),
"",
])
if public_api_refs:
lines.extend([
"Public API",
"~~~~~~~~~~",
"",
".. toctree::",
" :maxdepth: 1",
"",
" " + "\n ".join(public_api_refs),
"",
])
if internal_api_refs:
lines.extend([
"Internal API",
"~~~~~~~~~~~~",
"",
".. toctree::",
" :maxdepth: 1",
"",
" " + "\n ".join(internal_api_refs),
"",
])
if unclassified_refs:
lines.extend([
"Other Architectural Design",
"~~~~~~~~~~~~~~~~~~~~~~~~~~",
"",
".. toctree::",
" :maxdepth: 1",
"",
" " + "\n ".join(unclassified_refs),
"",
])
if dependability_refs:
lines.extend([
"Dependability Analysis",
"~~~~~~~~~~~~~~~~~~~~~~",
"",
".. toctree::",
" :maxdepth: 1",
"",
" " + "\n ".join(dependability_refs),
"",
])
else:
all_entries = unclassified_refs + dependability_refs
lines.extend([
".. toctree::",
" :maxdepth: 1",
"",
" " + "\n ".join(all_entries),
"",
])

page = ctx.actions.declare_file(ctx.label.name + "/software_arch.rst")
ctx.actions.write(
output = page,
content = "\n".join(lines),
)
output_files.append(page)
return "software_arch"

def _process_artifact_type(ctx, artifact_name):
"""Process all labels for a given artifact type.

Expand Down Expand Up @@ -787,7 +985,6 @@ def _dependable_element_index_impl(ctx):
# toctree references for the index template.
artifact_types = [
"assumptions_of_use",
"architectural_design",
"dependability_analysis",
"checklists",
"glossary",
Expand All @@ -799,6 +996,22 @@ def _dependable_element_index_impl(ctx):
output_files.extend(files)
artifacts_by_type[artifact_name] = refs

arch_static_refs = []
arch_dynamic_refs = []
arch_public_api_refs = []
arch_internal_api_refs = []
arch_unclassified_refs = []

if ctx.attr.architectural_design:
for ad_target in ctx.attr.architectural_design:
ad_files, s_refs, d_refs, p_refs, i_refs, u_refs = _process_architectural_design_files(ctx, ad_target)
output_files.extend(ad_files)
arch_static_refs.extend(s_refs)
arch_dynamic_refs.extend(d_refs)
arch_public_api_refs.extend(p_refs)
arch_internal_api_refs.extend(i_refs)
arch_unclassified_refs.extend(u_refs)

# Collect feature_requirements refs from requirements targets that
# carry FeatureRequirementsInfo.
feature_req_refs = []
Expand Down Expand Up @@ -900,10 +1113,16 @@ def _dependable_element_index_impl(ctx):
"Assumed System",
assumed_system_req_refs + artifacts_by_type["assumptions_of_use"],
)
software_arch_ref = _section_page(
"software_arch",
"Software Architectural Level",
feature_req_refs + artifacts_by_type["architectural_design"] + artifacts_by_type["dependability_analysis"],
software_arch_ref = _generate_software_arch_page(
ctx,
feature_req_refs = feature_req_refs,
static_refs = arch_static_refs,
dynamic_refs = arch_dynamic_refs,
public_api_refs = arch_public_api_refs,
internal_api_refs = arch_internal_api_refs,
unclassified_refs = arch_unclassified_refs,
dependability_refs = artifacts_by_type["dependability_analysis"],
output_files = output_files,
)
components_ref = _section_page(
"components",
Expand Down
13 changes: 10 additions & 3 deletions bazel/rules/rules_score/private/puml_utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,23 @@ def make_puml_rst_wrappers(ctx, puml_files, output_dir, template, strip_prefix =
List of declared ``.rst`` output Files, one per input diagram.
"""
wrappers = []
pkg_prefix = ctx.label.package + "/" if ctx.label.package else ""
for f in puml_files:
if f.extension not in ("puml", "plantuml"):
continue
stem = f.basename[:-(len(f.extension) + 1)]
if strip_prefix and stem.startswith(strip_prefix):
stem = stem[len(strip_prefix):]
title = stem.replace("_", " ").title()
wrapper = ctx.actions.declare_file(
"{}/{}{}.rst".format(output_dir, filename_prefix, stem),
)

rel_dir = ""
if pkg_prefix and f.short_path.startswith(pkg_prefix):
rel_path = f.short_path[len(pkg_prefix):]
if "/" in rel_path:
rel_dir = rel_path.rsplit("/", 1)[0]

out_file_path = "{}/{}/{}{}.rst".format(output_dir, rel_dir, filename_prefix, stem) if rel_dir else "{}/{}{}.rst".format(output_dir, filename_prefix, stem)
wrapper = ctx.actions.declare_file(out_file_path)
ctx.actions.expand_template(
template = template,
output = wrapper,
Expand Down
4 changes: 4 additions & 0 deletions bazel/rules/rules_score/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ ArchitecturalDesignInfo = provider(
"dynamic": "Depset of FlatBuffers binaries for dynamic architecture diagrams (sequence diagrams, activity diagrams, etc.)",
"public_api": "Depset of FlatBuffers binaries for public API diagrams (class diagrams, etc.)",
"internal_api": "Depset of FlatBuffers binaries for internal API diagrams (class diagrams, etc.)",
"static_doc_files": "Depset of Sphinx doc File objects for static architecture views.",
"dynamic_doc_files": "Depset of Sphinx doc File objects for dynamic architecture views.",
"public_api_doc_files": "Depset of Sphinx doc File objects for public API views.",
"internal_api_doc_files": "Depset of Sphinx doc File objects for internal API views.",
"name": "Name of the architectural design target",
"public_api_lobster_files": "Depset of .lobster traceability files generated from public_api diagrams.",
"validation_logs": "List of validation log entries produced by this architectural design target. Each entry has file and name fields.",
Expand Down
Loading