Skip to content

PlantUML SVG rendering under --remote_download_minimal: sysroot/font resolution relies on fragile runtime glob + non-hermetic tempfile, not rule-level dependency #324

Description

@simon-d-bmw

Problem

sphinx_module's PlantUML rendering crashes on CI (RBE with --remote_download_minimal) with:

RuntimeException: Fontconfig head is null, check your fonts or fonts configuration

thrown by sun.awt.FontConfiguration.readFontConfigFile(). Root cause: the hermetic JDK toolchain (remotejdk21_linux) ships no $JAVA_HOME/conf/fonts/fontconfig.properties, so it always falls into a native auto-generation path that crashes in the fontless build sandbox. This is unrelated to Linux fonts.conf/FONTCONFIG_FILE — confirmed setting that env var has zero effect on this JDK-internal mechanism.

A working fix was prototyped downstream in conf.template.py via the documented -Dsun.awt.fontconfig=<path> JVM flag, pointed at a minimal properties file referencing a DejaVu font from the same sysroot dot already uses via exec_in_sysroot/fakechroot. That prototype confirmed the approach resolves the crash and restores SVG rendering, but its implementation has known soundness gaps described below.

Why the prototype is a workaround, not a sound fix

  1. Sysroot path discovery is glob-based, not a real dependency edge. The prototype searches graphviz_dot + ".runfiles" recursively for a directory literally named dot_sysroot. This only works because of a naming convention in exec_in_sysroot.bzl ("<name>_sysroot"), not because the Sphinx build target actually declares a dependency on the sysroot. Any rename/refactor of exec_in_sysroot silently breaks font resolution.
  2. Root architectural gap: the Sphinx doc action depends on dot (the launcher) but not on dot_sysroot (the TreeArtifact) directly. Under --remote_download_minimal, dot_sysroot is therefore not staged as a bazel-out sibling and is only reachable via dot's own runfiles — an implementation detail of exec_in_sysroot's launcher, not a contract.
  3. Non-hermetic runtime side effect: the prototype calls tempfile.mkdtemp() and writes fontconfig.properties at Sphinx-build Python-execution time, outside Bazel's action graph. Not cached, not sandboxed, not reproducible as a build artifact.
  4. Hardcoded single font: assumes DejaVuSans.ttf exists at a fixed path inside the sysroot and maps it to a made-up subset name (alphabetic) rather than the standard JDK logical-font mapping (serif/sansserif/monospaced/dialog/dialoginput). Silently breaks if the graphviz apt package's font deps change.

Suggested fix

  • Add an explicit provider/attribute from exec_in_sysroot (or the dot-producing rule) exposing the sysroot's File/TreeArtifact directly to consumers — e.g. a SysrootInfo provider with a root field — instead of relying on runfiles-directory-naming conventions.
  • Wire the Sphinx/PlantUML build rule to declare an explicit data/dep on that sysroot (same pattern already used for other build-time env vars passed to conf.py), so it's staged correctly under --remote_download_minimal without glob search.
  • Generate fontconfig.properties as a genrule/build action (tracked Bazel output), not at Python runtime in conf.py. Pass its path in via an env var set by the rule.
  • Use the standard JDK logical-font mapping (serif, sansserif, monospaced, dialog, dialoginput → real font files) instead of a single custom alphabetic subset, so it degrades more gracefully if font files change.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions