Skip to content
Draft
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
3 changes: 3 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ docs(
data = [
"@score_process//:needs_json",
],
extra_docs = [
"//src:src_docs",
],
scan_code = [
"//scripts_bazel:sources",
"//src:all_sources",
Expand Down
109 changes: 88 additions & 21 deletions docs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,70 @@ Easy streamlined way for S-CORE docs-as-code.
load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_venv")
load("@docs_as_code_hub_env//:requirements.bzl", "all_requirements")
load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_build_binary", "sphinx_docs")
load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library")
load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo")

def _docs_source_tree_impl(ctx):
"""Materializes a sphinx_docs_library into a single directory for incremental builds."""
output_dir = ctx.actions.declare_directory(ctx.label.name)

all_inputs = []
pairs = [] # list of (src_exec_path, dest_rel_path, original_short_path)

# conf.py at its natural short_path position (e.g. "docs/conf.py")
config = ctx.file.config
all_inputs.append(config)
pairs.append((config.path, config.short_path, config.short_path))

for t in ctx.attr.lib:
info = t[SphinxDocsLibraryInfo]
for entry in info.transitive.to_list():
for f in entry.files:
dest_rel = entry.prefix + f.short_path.removeprefix(entry.strip_prefix)
all_inputs.append(f)
pairs.append((f.path, dest_rel, f.short_path))

# Build _sourcemap.json content in Starlark to avoid shell quoting issues.
sourcemap_entries = []
for _src, dest_rel, original in pairs:
sourcemap_entries.append(' "{}": "{}"'.format(dest_rel, original))
sourcemap_content = "{\n" + ",\n".join(sourcemap_entries) + "\n}"
sourcemap_template = ctx.actions.declare_file(ctx.label.name + "_sourcemap.json")
ctx.actions.write(sourcemap_template, sourcemap_content)

cmds = ["set -euo pipefail"]
for src, dest_rel, _original in pairs:
parent = dest_rel.rsplit("/", 1)[0] if "/" in dest_rel else ""
if parent:
cmds.append("mkdir -p '{}/{}'".format(output_dir.path, parent))
cmds.append("ln '{}' '{}/{}'".format(src, output_dir.path, dest_rel))

# Copy the pre-built sourcemap into the declared directory.
cmds.append("cp '{}' '{}/_sourcemap.json'".format(sourcemap_template.path, output_dir.path))

ctx.actions.run_shell(
inputs = all_inputs + [sourcemap_template],
outputs = [output_dir],
command = "\n".join(cmds),
progress_message = "Materializing docs source tree for %{label}",
)

return [DefaultInfo(files = depset([output_dir]))]

_docs_source_tree = rule(
implementation = _docs_source_tree_impl,
attrs = {
"lib": attr.label_list(
providers = [SphinxDocsLibraryInfo],
doc = "sphinx_docs_library targets whose files to merge into the source tree.",
),
"config": attr.label(
allow_single_file = True,
mandatory = True,
doc = "The conf.py file to include in the source tree.",
),
},
)

def _rewrite_needs_json_to_docs_sources(labels):
"""Replace '@repo//:needs_json' -> '@repo//:docs_sources' for every item."""
Expand Down Expand Up @@ -125,7 +189,9 @@ def _missing_requirements(deps):
fail(msg)
fail("This case should be unreachable?!")

def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = None):


def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = None, extra_docs = []):
"""Creates all targets related to documentation.

By using this function, you'll get any and all updates for documentation targets in one place.
Expand All @@ -135,6 +201,9 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
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.
extra_docs: List of sphinx_docs_library targets to merge into the source tree.
Each target controls placement via its strip_prefix/prefix attributes.
See sphinx_docs_library in rules_python for details.
"""

call_path = native.package_name()
Expand Down Expand Up @@ -163,7 +232,7 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
else:
source_prefix = source_dir + "/"

native.filegroup(
sphinx_docs_library(
name = "docs_sources",
srcs = native.glob([
source_prefix + "**/*.png",
Expand All @@ -179,6 +248,7 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
source_prefix + "**/*.csv",
source_prefix + "**/*.inc",
], allow_empty = True),
deps = extra_docs,
visibility = ["//visibility:public"],
)

Expand All @@ -187,11 +257,19 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
data_with_docs_sources = _rewrite_needs_json_to_docs_sources(data)
additional_combo_sourcelinks = _rewrite_needs_json_to_sourcelinks(data)
_merge_sourcelinks(name = "merged_sourcelinks", sourcelinks = [":sourcelinks_json"] + additional_combo_sourcelinks, known_good = known_good)
docs_data = data + [":sourcelinks_json"]
combo_data = data_with_docs_sources + [":merged_sourcelinks"]
_docs_source_tree(
name = "docs_src_dir",
lib = [":docs_sources"],
config = ":" + source_prefix + "conf.py",
visibility = ["//visibility:private"],
)

docs_data = data + [":sourcelinks_json", ":docs_sources", ":docs_src_dir"]
combo_data = data_with_docs_sources + [":merged_sourcelinks", ":docs_sources"]

docs_env = {
"SOURCE_DIRECTORY": source_dir,
"DOCS_SOURCE_TREE": "$(rlocationpath :docs_src_dir)",
"DATA": str(data),
"SCORE_SOURCELINKS": "$(location :sourcelinks_json)",
}
Expand Down Expand Up @@ -253,24 +331,12 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
env = docs_env
)

docs_env["ACTION"] = "live_preview"
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 = docs_data,
deps = deps,
env = docs_env
)

docs_sources_env["ACTION"] = "live_preview"
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 = combo_data,
name = "gen_live_preview",
tags = ["cli_help=Generate ./live_preview script (run that script for a live preview):\nbazel run //:gen_live_preview"],
srcs = ["@score_docs_as_code//src:gen_live_preview.py"],
deps = deps,
env = docs_sources_env
env = {"SOURCE_DIRECTORY": source_dir},
)

py_venv(
Expand All @@ -284,8 +350,9 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =

sphinx_docs(
name = "needs_json",
srcs = [":docs_sources"],
srcs = [],
config = ":" + source_prefix + "conf.py",
deps = [":docs_sources"],
extra_opts = [
"-W",
"--keep-going",
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ It provides documentation, requirements, and traceability.
reference/index
concepts/index
internals/index
BLA/index
10 changes: 10 additions & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
load("@aspect_rules_py//py:defs.bzl", "py_library")
load("@rules_java//java:java_binary.bzl", "java_binary")
load("@rules_python//python:pip.bzl", "compile_pip_requirements")
load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library")

# These are only exported because they're passed as files to the //docs.bzl
# macros, and thus must be visible to other packages. They should only be
Expand All @@ -26,6 +27,7 @@ exports_files(
"incremental.py",
"dummy.py",
"generate_sourcelinks_cli.py",
"gen_live_preview.py",
],
visibility = ["//visibility:public"],
)
Expand Down Expand Up @@ -94,3 +96,11 @@ filegroup(
],
visibility = ["//visibility:public"],
)

sphinx_docs_library(
name = "src_docs",
srcs = glob(["docs/**/*.rst"]),
strip_prefix = "src/docs/",
prefix = "docs/BLA/", # must be under docs(source_dir=X) !
visibility = ["//visibility:public"],
)
19 changes: 19 additions & 0 deletions src/docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
..
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

Source Extensions
=================

.. toctree::

overview
requirements
18 changes: 18 additions & 0 deletions src/docs/overview.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
..
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

Overview
========

The ``src/`` directory contains the Python extensions and Bazel helpers that
make up the docs-as-code toolchain.
Each extension is a standalone Sphinx plugin loaded via ``score_sphinx_bundle``.
25 changes: 25 additions & 0 deletions src/docs/requirements.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
..
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

Requirements
============

.. tool_req:: Supports extra_docs in docs() macro
:id: tool_req__docs_extra_docs
:implemented: YES
:tags: Architecture
:version: 1

The ``docs()`` Bazel macro shall accept an ``extra_docs`` parameter
containing a list of ``sphinx_docs_library`` targets.
Their files shall be merged into the Sphinx source tree at the paths
determined by each library's ``strip_prefix`` and ``prefix`` attributes.
8 changes: 7 additions & 1 deletion src/extensions/score_metamodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,13 @@ def _run_checks(app: Sphinx, exception: Exception | None) -> None:

ws_root = os.environ.get("BUILD_WORKSPACE_DIRECTORY", None)
cwd_or_ws_root = Path(ws_root) if ws_root else Path.cwd()
prefix = str(Path(app.srcdir).relative_to(cwd_or_ws_root))
try:
prefix = str(Path(app.srcdir).relative_to(cwd_or_ws_root))
except ValueError:
# srcdir is outside the workspace (e.g. a Bazel runfiles tree).
# prefix is only used for log-message locations, which log.py already
# skips when RUNFILES_DIR is set, so any fallback is fine here.
prefix = str(app.srcdir)

log = CheckLogger(logger, prefix)

Expand Down
21 changes: 21 additions & 0 deletions src/extensions/score_sourcemap/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# *******************************************************************************
# Copyright (c) 2025 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_library")
load("@docs_as_code_hub_env//:requirements.bzl", "requirement")

py_library(
name = "score_sourcemap",
srcs = ["__init__.py"],
visibility = ["//visibility:public"],
deps = [requirement("sphinx")],
)
Loading
Loading