|
20 | 20 |
|
21 | 21 | import json |
22 | 22 | import os |
| 23 | +import shutil as _shutil |
23 | 24 | import sys |
24 | 25 | from pathlib import Path |
25 | 26 | from typing import Any, Dict, List |
|
30 | 31 | # Create a logger with the Sphinx namespace |
31 | 32 | logger = logging.getLogger(__name__) |
32 | 33 |
|
| 34 | +# --------------------------------------------------------------------------- |
| 35 | +# Helpers: Bazel execroot path resolution |
| 36 | +# --------------------------------------------------------------------------- |
| 37 | + |
| 38 | + |
| 39 | +def _bazel_execroot() -> Path: |
| 40 | + """Return the Bazel execroot directory inferred from this config file's path. |
| 41 | +
|
| 42 | + conf.py is generated into ``bazel-out/…/bin/…/conf.py``, so splitting on |
| 43 | + ``/bazel-out/`` gives us the execroot prefix reliably. Falls back to the |
| 44 | + current working directory when the path pattern is not recognised (e.g. |
| 45 | + during unit tests or IDE runs outside Bazel). |
| 46 | + """ |
| 47 | + parts = str(Path(__file__).resolve()).split("/bazel-out/", 1) |
| 48 | + return Path(parts[0]) if len(parts) == 2 else Path.cwd() |
| 49 | + |
| 50 | + |
| 51 | +# Computed once at import time so _resolve_execroot_path() doesn't repeat the |
| 52 | +# filesystem resolution on every call. |
| 53 | +_EXECROOT = _bazel_execroot() |
| 54 | + |
| 55 | + |
| 56 | +def _resolve_execroot_path(path_value: str) -> str: |
| 57 | + """Resolve an execroot-relative path to an absolute filesystem path. |
| 58 | +
|
| 59 | + Bazel passes action inputs as paths relative to the execroot (e.g. |
| 60 | + ``external/+_repo_rules2+graphviz_deb/usr/bin/dot_builtins``). Those |
| 61 | + paths are only valid when the process' cwd is the execroot — which is |
| 62 | + not guaranteed once Sphinx changes directories during the build. |
| 63 | +
|
| 64 | + This function makes them absolute so they work regardless of cwd. |
| 65 | + Absolute paths and plain command names (e.g. ``dot``) are returned |
| 66 | + unchanged. |
| 67 | + """ |
| 68 | + p = Path(path_value) |
| 69 | + if p.is_absolute(): |
| 70 | + return str(p) |
| 71 | + if path_value.startswith("external/") or path_value.startswith("bazel-out/"): |
| 72 | + return str((_EXECROOT / p).resolve()) |
| 73 | + return path_value |
| 74 | + |
| 75 | + |
33 | 76 | logger.debug("#" * 80) |
34 | 77 | logger.debug("# READING CONF.PY") |
35 | 78 | logger.debug("SYSPATH:" + str(sys.path)) |
|
55 | 98 | "sphinxcontrib.plantuml", |
56 | 99 | "trlc", |
57 | 100 | "clickable_plantuml", |
| 101 | + "sphinx.ext.graphviz", |
58 | 102 | ] |
59 | 103 |
|
60 | 104 | # MyST parser extensions |
|
164 | 208 | plantuml = f"{plantuml_path} -Playout=smetana" |
165 | 209 | plantuml_output_format = "svg_obj" |
166 | 210 |
|
167 | | -import shutil as _shutil |
| 211 | +# --------------------------------------------------------------------------- |
| 212 | +# Graphviz (sphinx.ext.graphviz) |
| 213 | +# --------------------------------------------------------------------------- |
| 214 | +# GRAPHVIZ_DOT is set by the Bazel sphinx_module rule to point at the hermetic |
| 215 | +# dot_builtins binary from @graphviz_deb. The path is execroot-relative, so |
| 216 | +# we resolve it to an absolute path here so it remains valid after any cwd |
| 217 | +# change that Sphinx may perform during the build. |
| 218 | +graphviz_dot = _resolve_execroot_path( |
| 219 | + os.environ.get("GRAPHVIZ_DOT") or _shutil.which("dot") or "dot" |
| 220 | +) |
168 | 221 |
|
169 | | -graphviz_dot = os.environ.get("GRAPHVIZ_DOT") or _shutil.which("dot") or "dot" |
| 222 | +# LD_LIBRARY_PATH and LTDL_LIBRARY_PATH are set by the Bazel rule as |
| 223 | +# execroot-relative paths. We mutate os.environ (not just a local) because |
| 224 | +# sphinx.ext.graphviz spawns `dot` as a child process that inherits these |
| 225 | +# variables to locate the bundled shared libraries and plugins. Each |
| 226 | +# component is resolved to absolute so it stays valid if Sphinx changes cwd |
| 227 | +# before spawning the dot subprocess. |
| 228 | +for _env_var in ("LD_LIBRARY_PATH", "LTDL_LIBRARY_PATH"): |
| 229 | + _env_val = os.environ.get(_env_var, "") |
| 230 | + if _env_val: |
| 231 | + os.environ[_env_var] = ":".join( |
| 232 | + _resolve_execroot_path(p) for p in _env_val.split(":") |
| 233 | + ) |
170 | 234 |
|
171 | 235 | # HTML theme |
172 | 236 | html_theme = "sphinx_rtd_theme" |
|
0 commit comments