Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
120 changes: 99 additions & 21 deletions bazel/rules/rules_score/private/sphinx_module.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,39 @@ load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library")
load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo")
load("//bazel/rules/rules_score:providers.bzl", "SphinxModuleInfo", "SphinxNeedsInfo")

# Delimiter used to encode filter_execpath parameters into a single extra_opts string.
_FILTER_EXECPATH_DELIM = "@@FILTER_EXECPATH@@"

def filter_execpath(flag, label, filter_pattern):
"""Construct an extra_opts entry that filters execpaths and rewrites them with the relocated target prefix.

When used in extra_opts of sphinx_module, the rule implementation will:
1. Expand $(execpaths <label>) to get all output paths
2. Filter for the path containing <filter_pattern>
3. Rewrite the path to include the target's relocated prefix
(e.g. bazel-out/.../bin/<package>/<name>/<original_path>)

Example usage in BUILD:
load("@score_tooling//bazel/rules/rules_score:rules_score.bzl", "filter_execpath", "sphinx_module")

sphinx_module(
name = "sphinx_doc",
extra_opts = [
filter_execpath("-Dbreathe_projects.com", "//docs/sphinx:doxygen_xml", "doxygen_build/xml"),
],
...
)

Args:
flag: The Sphinx -D flag prefix (e.g. "-Dbreathe_projects.com")
label: The Bazel label whose execpaths to expand (e.g. "//docs/sphinx:doxygen_xml")
filter_pattern: Substring to match when filtering the execpaths (e.g. "doxygen_build/xml")

Returns:
A specially formatted string that the sphinx_module rule will process.
"""
return _FILTER_EXECPATH_DELIM.join([flag, str(label), filter_pattern])

def _create_config_py(ctx):
"""Get or generate the conf.py configuration file.

Expand Down Expand Up @@ -113,6 +146,62 @@ def _score_html_impl(ctx):
Phase 2: Generate HTML with external needs and merge all dependency HTML
"""

run_args = [] # Copy of the args to forward along to debug runner
args = ctx.actions.args() # Args passed to the action

# Expand location references in extra_opts and collect as sphinx arguments.
# targets must include all labels referenced via $(location ...) / $(execpaths ...).
extra_opts_targets = ctx.attr.srcs + ctx.attr.docs_library_deps
source_prefix = ctx.label.name

for opt in ctx.attr.extra_opts:
if _FILTER_EXECPATH_DELIM in opt:
# Process filter_execpath() encoded strings:
# Format: flag@@FILTER_EXECPATH@@label@@FILTER_EXECPATH@@filter_pattern
parts = opt.split(_FILTER_EXECPATH_DELIM)
flag = parts[0]
label_str = parts[1]
filter_pattern = parts[2]

# Expand execpaths for the referenced label
expanded = ctx.expand_location("$(execpaths " + label_str + ")", targets = extra_opts_targets)
expanded_paths = expanded.split(" ")

# Filter for the path matching the filter pattern
matched_path = None
for p in expanded_paths:
if filter_pattern in p:
matched_path = p
break

if not matched_path:
fail("filter_execpath: no path matching '{}' found in execpaths of {}. Paths: {}".format(
filter_pattern,
label_str,
expanded,
))

# Extract the path suffix after "/bin/" — this is the original path relative
# to the output base. Since _relocate() symlinks source files under
# <source_prefix>/<original_path>, the relocated file lives at
# <source_dir>/<suffix_part> where source_dir is the Sphinx source directory.
# Breathe resolves breathe_projects paths relative to source_dir (app.srcdir),
# so we must return just the suffix_part.
bin_marker = "/bin/"
bin_idx = matched_path.find(bin_marker)
if bin_idx >= 0:
suffix_part = matched_path[bin_idx + len(bin_marker):]
else:
suffix_part = matched_path
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for /bin/ there should be a bazel equivalent ctx.bin_dir.path

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be fixed


expanded_opt = flag + "=" + suffix_part
else:
# Standard extra_opts: expand locations and pass through
expanded_opt = ctx.expand_location(opt, targets = extra_opts_targets)

args.add(expanded_opt)
run_args.append(expanded_opt)

# Collect all transitive dependencies with deduplication
modules = []
sphinx_toolchain = ctx.toolchains["//bazel/rules/rules_score:toolchain_type"].sphinxinfo
Expand All @@ -138,19 +227,6 @@ def _score_html_impl(ctx):
content = json.encode_indent(needs_external_needs, indent = " "),
)

# Read template and substitute PROJECT_NAME
config_file = ctx.actions.declare_file(ctx.label.name + "/conf.py")
template = sphinx_toolchain.conf_template.files.to_list()[0]

ctx.actions.expand_template(
template = template,
output = config_file,
substitutions = {
"{PROJECT_NAME}": ctx.label.name.replace("_", " ").title(),
},
)

source_prefix = ctx.label.name
sphinx_source_files = []

# Materialize a file under the `_sources` dir
Expand Down Expand Up @@ -182,13 +258,6 @@ def _score_html_impl(ctx):
new_path = entry.prefix + original.short_path.removeprefix(entry.strip_prefix)
_relocate(original, new_path)

needs_external_needs_json = ctx.actions.declare_file(ctx.label.name + "/needs_external_needs.json")

ctx.actions.write(
output = needs_external_needs_json,
content = json.encode_indent(needs_external_needs, indent = " "),
)

config_file = _create_config_py(ctx)

# Sphinx only accepts a single directory to read its doc sources from.
Expand Down Expand Up @@ -219,7 +288,7 @@ def _score_html_impl(ctx):
ctx.actions.run(
inputs = html_inputs,
outputs = [sphinx_html_output],
arguments = html_args,
arguments = html_args + [args],
progress_message = "Building HTML: %s" % ctx.label.name,
executable = sphinx_toolchain.sphinx.files_to_run.executable,
tools = [
Expand Down Expand Up @@ -287,6 +356,10 @@ _score_html = rule(
allow_files = True,
doc = "Submodule symbols.needs targets for this module.",
),
extra_opts = attr.string_list(
doc = "Additional options to pass onto Sphinx. These are added after " +
"other options, but before the source/output args.",
),
),
toolchains = ["//bazel/rules/rules_score:toolchain_type"],
)
Expand All @@ -303,6 +376,7 @@ def sphinx_module(
docs_library_deps = [],
sphinx = Label("//bazel/rules/rules_score:score_build"),
strip_prefix = "",
extra_opts = [],
testonly = False,
visibility = ["//visibility:public"]):
"""Build a Sphinx module with transitive HTML dependencies.
Expand All @@ -323,6 +397,9 @@ def sphinx_module(
source files. e.g., given `//sphinxdocs/docs:foo.md`, stripping `docs/` makes
Sphinx see `foo.md` in its generated source directory. If not
specified, then {any}`native.package_name` is used.
extra_opts: {type}`list[str]` Additional options to pass onto Sphinx building.
On each provided option, a location expansion is performed.
See {any}`ctx.expand_location`.
visibility: Bazel visibility
"""
_score_needs(
Expand All @@ -341,6 +418,7 @@ def sphinx_module(
deps = deps,
docs_library_deps = docs_library_deps,
needs = [d + "_needs" for d in deps],
extra_opts = extra_opts,
testonly = testonly,
visibility = visibility,
)
2 changes: 2 additions & 0 deletions bazel/rules/rules_score/rules_score.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ load(
)
load(
"//bazel/rules/rules_score/private:sphinx_module.bzl",
_filter_execpath = "filter_execpath",
_sphinx_module = "sphinx_module",
)
load(
Expand All @@ -65,6 +66,7 @@ component_requirements = _component_requirements
dependability_analysis = _dependability_analysis
feature_requirements = _feature_requirements
fmea = _fmea
filter_execpath = _filter_execpath
sphinx_module = _sphinx_module
unit = _unit
unit_design = _unit_design
Expand Down
18 changes: 14 additions & 4 deletions bazel/rules/rules_score/src/sphinx_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,15 @@ def validate_arguments(args: argparse.Namespace) -> None:
raise ValueError(f"Index file does not exist: {args.index_file}")


def build_sphinx_arguments(args: argparse.Namespace) -> List[str]:
def build_sphinx_arguments(
args: argparse.Namespace, extra_args: List[str] = None
) -> List[str]:
"""
Build the argument list for Sphinx.

Args:
args: Parsed command-line arguments
extra_args: Additional arguments to forward to Sphinx (e.g., -D options from extra_opts)

Returns:
List of arguments to pass to Sphinx
Expand Down Expand Up @@ -154,6 +157,10 @@ def build_sphinx_arguments(args: argparse.Namespace) -> List[str]:

base_arguments.extend(["-b", args.builder])

# Forward extra options (e.g., -D flags) to Sphinx
if extra_args:
base_arguments.extend(extra_args)

return base_arguments


Expand Down Expand Up @@ -240,7 +247,7 @@ def parse_arguments() -> argparse.Namespace:
help=f"Port to use for live preview (default: {DEFAULT_PORT}). Use 0 for auto-detection.",
)

return parser.parse_args()
return parser.parse_known_args()


def main() -> int:
Expand All @@ -251,14 +258,17 @@ def main() -> int:
Exit code (0 for success, non-zero for failure)
"""
try:
args = parse_arguments()
args, extra_args = parse_arguments()
validate_arguments(args)
logger.info(f"[DEBUG] extra_args from parse_known_args: {extra_args}")
logger.info(f"[DEBUG] sys.argv was: {sys.argv}")
# Create processor instance
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either remove or only output on a debug flag

stdout_processor = StdoutProcessor()
stderr_processor = StderrProcessor()
# Redirect stdout and stderr
with redirect_stderr(stderr_processor), redirect_stdout(stdout_processor):
sphinx_args = build_sphinx_arguments(args)
sphinx_args = build_sphinx_arguments(args, extra_args)
logger.info(f"[DEBUG] Final sphinx_args: {sphinx_args}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

exit_code = run_sphinx_build(sphinx_args, args.builder)
return exit_code
except ValueError as e:
Expand Down
Loading