diff --git a/MODULE.bazel b/MODULE.bazel index 8aaee15a..de491973 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -260,17 +260,24 @@ deb( ) ############################################################################### -# Graphviz deb package (cmake release; bundles all graphviz .so files so -# dot_builtins runs without system graphviz installation) -# Uses download_deb from @download_utils at a commit that includes -# data.tar.gz support in download/deb/repository.bzl. +# Hermetic doc-tool sysroot (docs_runtime) +# +# Distroless rootfs providing graphviz + fakechroot for hermetic dot execution +# via //third_party/docs_runtime:dot (exec_in_sysroot). ############################################################################### -deb( - name = "graphviz_deb", - build = "//third_party/graphviz:graphviz.BUILD", - integrity = "sha256-Jk5gSqo8l0INoY+kr1ZAsi2WhZY8LlAFlEag54H3Q2Q=", - urls = ["https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/12.2.1/ubuntu_24.04_graphviz-12.2.1-cmake.deb"], +bazel_dep(name = "rules_distroless", version = "0.6.2") + +# bsdtar (used by //bazel/rules/exec_in_sysroot to extract sysroot archives). +bazel_dep(name = "tar.bzl", version = "0.6.0") + +apt = use_extension("@rules_distroless//apt:extensions.bzl", "apt") +apt.install( + name = "docs_runtime", + lock = "//third_party/docs_runtime:docs_runtime.lock.json", + manifest = "//third_party/docs_runtime:docs_runtime.yaml", + mergedusr = True, ) +use_repo(apt, "docs_runtime") register_toolchains( "//bazel/rules/rules_score:sphinx_default_toolchain", diff --git a/third_party/graphviz/BUILD b/bazel/rules/exec_in_sysroot/BUILD similarity index 64% rename from third_party/graphviz/BUILD rename to bazel/rules/exec_in_sysroot/BUILD index e9f4f198..9e0f07ff 100644 --- a/third_party/graphviz/BUILD +++ b/bazel/rules/exec_in_sysroot/BUILD @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -11,6 +11,9 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -# This package hosts the BUILD file used by the @graphviz_deb external repository. -# The download_deb rule from @download_utils extracts the Graphviz cmake -# release .deb and uses graphviz.BUILD as its top-level BUILD file. +load("@rules_shell//shell:sh_binary.bzl", "sh_binary") + +sh_binary( + name = "exec_in_sysroot", + srcs = ["exec_in_sysroot.sh"], +) diff --git a/bazel/rules/exec_in_sysroot/exec_in_sysroot.bzl b/bazel/rules/exec_in_sysroot/exec_in_sysroot.bzl new file mode 100644 index 00000000..450f75ba --- /dev/null +++ b/bazel/rules/exec_in_sysroot/exec_in_sysroot.bzl @@ -0,0 +1,327 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +_TAR_TOOLCHAIN_TYPE = "@tar.bzl//tar/toolchain:type" + +def _merge_default_and_data_runfiles(target, runfiles): + default_info = target[DefaultInfo] + if default_info.default_runfiles: + runfiles = runfiles.merge(default_info.default_runfiles) + if default_info.data_runfiles: + runfiles = runfiles.merge(default_info.data_runfiles) + return runfiles + +def _extract_and_clean(tar_bin, src, dest): + """POSIX-sh snippet: extract `src` into `dest`, then drop the symlinks that + break Bazel TreeArtifact validation: + * self-referential links (e.g. Debian x11-common's `usr/bin/X11 -> .`), + which make validation recurse infinitely; and + * now-dangling links, which validation also rejects. + """ + return ( + "mkdir -p \"" + dest + "\"\n" + + tar_bin + " -xf " + src + " -C \"" + dest + "\"\n" + + "find \"" + dest + "\" -type l -lname '.' -delete\n" + + "find \"" + dest + "\" -xtype l -delete\n" + ) + +def _setup_block(sysroot_dir, host_setup_commands, sysroot_setup_commands): + """POSIX-sh snippet running the optional post-extract setup against + `sysroot_dir` (an unquoted shell path expression) while it is still writable. + + host_setup_commands run in the outer shell with $SYSROOT set to sysroot_dir. + + sysroot_setup_commands are invoked directly via the sysroot's own ELF + interpreter (ld-linux.so) with --library-path pointing at the sysroot's + /usr/lib tree. This gives each command a fully consistent single-libc + environment (all of the binary's dependencies — including libc.so.6 itself — + come from the sysroot). LD_PRELOAD=libfakechroot.so + FAKECHROOT_BASE are + still set so that glibc-level filesystem calls inside the command (e.g. + writing the graphviz config6 file to /usr/lib/…) are transparently + redirected into the sysroot. + + Each entry in sysroot_setup_commands must be a space-separated ELF binary + invocation starting with an absolute sysroot path, e.g. "/usr/bin/dot -c". + Shell metacharacters (pipes, redirects, etc.) are not supported. + """ + block = "" + if host_setup_commands: + block += "SYSROOT=\"" + sysroot_dir + "\"\n" + block += "\n".join(host_setup_commands) + "\n" + if sysroot_setup_commands: + block += ( + "_FC_LIB=\"$(find \"" + sysroot_dir + "/usr/lib\" -path '*/fakechroot/libfakechroot.so' -type f 2>/dev/null | head -1 || true)\"\n" + + "if [ -z \"$_FC_LIB\" ]; then\n" + + " echo \"ERROR: sysroot_setup_commands require fakechroot, but libfakechroot.so was not found under " + sysroot_dir + "/usr/lib\" >&2\n" + + " exit 1\n" + + "fi\n" + + # Use the sysroot's own ELF interpreter with an explicit --library-path + # so each command loads all dependencies from the sysroot's /usr/lib tree. + # Use well-known Debian multiarch paths instead of a fragile glob search. + # Running via the HOST ld-linux.so + LD_LIBRARY_PATH would load the + # sysroot's libc.so.6 alongside the host's already-loaded libc (two libc + # instances in one process → segfault). The sysroot's own interpreter + # gives a single coherent libc. + "if [ -f \"" + sysroot_dir + "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2\" ]; then\n" + + " _SYSROOT_INTERP=\"" + sysroot_dir + "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2\"\n" + + " _SYSROOT_LIBPATH=\"" + sysroot_dir + "/usr/lib/x86_64-linux-gnu:" + sysroot_dir + "/usr/lib\"\n" + + "elif [ -f \"" + sysroot_dir + "/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1\" ]; then\n" + + " _SYSROOT_INTERP=\"" + sysroot_dir + "/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1\"\n" + + " _SYSROOT_LIBPATH=\"" + sysroot_dir + "/usr/lib/aarch64-linux-gnu:" + sysroot_dir + "/usr/lib\"\n" + + "else\n" + + " echo \"ERROR: sysroot ELF interpreter not found (tried x86_64 and aarch64 paths)\" >&2\n" + + " exit 1\n" + + "fi\n" + ) + for cmd in sysroot_setup_commands: + parts = cmd.split(" ") + binary = parts[0] + args = parts[1:] + args_shell = " ".join(['"' + a + '"' for a in args]) + block += ( + "LD_PRELOAD=\"$_FC_LIB\" " + + "FAKECHROOT_BASE=\"" + sysroot_dir + "\" " + + "\"$_SYSROOT_INTERP\" --library-path \"$_SYSROOT_LIBPATH\" " + + "\"" + sysroot_dir + binary + "\"" + + (" " + args_shell if args_shell else "") + + "\n" + ) + return block + +def _prepare_sysroot_impl(ctx): + if len(ctx.files.sysroot) != 1: + fail("sysroot '{}' must provide exactly one archive file".format(ctx.attr.sysroot.label)) + + sysroot_archive = ctx.files.sysroot[0] + bsdtar = ctx.toolchains[_TAR_TOOLCHAIN_TYPE] + out_archive = ctx.actions.declare_file(ctx.label.name + ".tar") + work = out_archive.path + ".work" + tar_bin = bsdtar.tarinfo.binary.path + + command = ( + "set -eu\n" + + "rm -rf \"" + work + "\"\n" + + _extract_and_clean(tar_bin, sysroot_archive.path, work) + + _setup_block(work, ctx.attr.host_setup_commands, ctx.attr.sysroot_setup_commands) + + tar_bin + " -cf " + out_archive.path + " -C \"" + work + "\" .\n" + + "rm -rf \"" + work + "\"\n" + ) + + ctx.actions.run_shell( + inputs = [sysroot_archive], + outputs = [out_archive], + tools = [bsdtar.default.files], + command = command, + mnemonic = "PrepareSysroot", + progress_message = "Preparing sysroot archive %s" % ctx.label.name, + ) + return [DefaultInfo(files = depset([out_archive]))] + +prepare_sysroot = rule( + implementation = _prepare_sysroot_impl, + attrs = { + "sysroot": attr.label( + mandatory = True, + allow_single_file = True, + doc = "Input sysroot archive (e.g. a rules_distroless `:flat` tar).", + ), + "host_setup_commands": attr.string_list( + default = [], + doc = "Shell lines run in the outer (host) shell after extraction while " + + "the sysroot is still writable. $SYSROOT is set to the sysroot " + + "directory. Use this for filesystem operations that only need the " + + "host shell (e.g. removing unwanted plugins with find/rm).", + ), + "sysroot_setup_commands": attr.string_list( + default = [], + doc = "Shell lines run inside the sysroot after host_setup_commands " + + "complete. Each entry must be a space-separated ELF binary " + + "invocation with an absolute sysroot path (e.g. '/usr/bin/dot -c'). " + + "Shell metacharacters (pipes, redirects) are not supported. " + + "The binary is executed via the sysroot's own ld-linux.so with " + + "--library-path pointing at the sysroot's /usr/lib tree, giving a " + + "fully consistent single-libc environment. LD_PRELOAD=libfakechroot " + + "+ FAKECHROOT_BASE are still active so glibc-level filesystem calls " + + "(e.g. writing config6) are transparently redirected into the sysroot. " + + "Requires fakechroot and ld-linux.so to be present in the sysroot.", + ), + }, + toolchains = [_TAR_TOOLCHAIN_TYPE], + doc = """ + Unpacks a sysroot archive, removes symlinks that break Bazel TreeArtifact + validation, runs optional host/sysroot setup commands while the tree is + writable, and repackages the result into a single `.tar` archive. + + """, +) + +def _exec_in_sysroot_impl(ctx): + if len(ctx.files.sysroot) != 1: + fail("sysroot '{}' must provide exactly one archive file".format(ctx.attr.sysroot.label)) + + sysroot_archive = ctx.files.sysroot[0] + bsdtar = ctx.toolchains[_TAR_TOOLCHAIN_TYPE] + sysroot = ctx.actions.declare_directory(ctx.label.name + "_sysroot") + + # Extract the sysroot archive into a TreeArtifact so the wrapped executable + # can reference it at action time via fakechroot. Any filesystem preparation + # (plugin pruning, post-install commands, …) should be done upfront in a + # prepare_sysroot rule; the symlink cleanup from _extract_and_clean still + # runs here because Bazel rejects TreeArtifacts with broken symlinks. + ctx.actions.run_shell( + inputs = [sysroot_archive], + outputs = [sysroot], + tools = [bsdtar.default.files], + command = "set -eu\n" + _extract_and_clean( + bsdtar.tarinfo.binary.path, + sysroot_archive.path, + sysroot.path, + ), + mnemonic = "ExecInSysrootExtract", + progress_message = "Extracting sysroot %s" % ctx.label.name, + ) + + sysroot_short_path = sysroot.short_path + if sysroot_short_path.startswith("../"): + sysroot_runfiles_path = sysroot_short_path[3:] + else: + sysroot_runfiles_path = ctx.workspace_name + "/" + sysroot_short_path + + executable_file = ctx.executable.executable + if executable_file == None: + fail("executable must provide a runnable target") + executable_short_path = executable_file.short_path + if executable_short_path.startswith("../"): + executable_runfiles_path = executable_short_path[3:] + else: + executable_runfiles_path = ctx.workspace_name + "/" + executable_short_path + + out = ctx.actions.declare_file(ctx.label.name) + + # Build exclude paths string - colon-separated list + exclude_paths = ":".join(ctx.attr.exclude_paths) if ctx.attr.exclude_paths else "" + + wrapper_script = """#!/usr/bin/env bash +set -euo pipefail + +# --- begin runfiles.bash initialization --- +if [[ ! -d "${{RUNFILES_DIR:-/dev/null}}" && ! -f "${{RUNFILES_MANIFEST_FILE:-/dev/null}}" ]]; then + if [[ -f "$0.runfiles_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" + elif [[ -f "$0.runfiles/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" + elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + export RUNFILES_DIR="$0.runfiles" + fi +fi +if [[ -f "${{RUNFILES_DIR:-/dev/null}}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + source "${{RUNFILES_DIR}}/bazel_tools/tools/bash/runfiles/runfiles.bash" +elif [[ -f "${{RUNFILES_MANIFEST_FILE:-/dev/null}}" ]]; then + source "$(grep -m1 '^bazel_tools/tools/bash/runfiles/runfiles.bash ' "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)" +else + echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash" + exit 1 +fi +# --- end runfiles.bash initialization --- + +FAKECHROOT_WRAPPER="$(rlocation '{wrapper_short_path}')" +SYSROOT_DIR="$(rlocation '{sysroot_short_path}')" +EXECUTABLE_FILE="$(rlocation '{executable_runfiles_path}')" + +if [[ -z "${{FAKECHROOT_WRAPPER}}" || ! -x "${{FAKECHROOT_WRAPPER}}" ]]; then + echo "ERROR: could not resolve fakechroot wrapper: {wrapper_short_path}" >&2 + exit 1 +fi + +if [[ -z "${{SYSROOT_DIR}}" || ! -d "${{SYSROOT_DIR}}" ]]; then + echo "ERROR: could not resolve sysroot directory: {sysroot_short_path}" >&2 + exit 1 +fi + +if [[ ! -x "${{SYSROOT_DIR}}/usr/bin/fakechroot" ]]; then + echo "ERROR: sysroot does not provide /usr/bin/fakechroot: ${{SYSROOT_DIR}}" >&2 + exit 1 +fi + +if [[ -z "${{EXECUTABLE_FILE}}" || ! -f "${{EXECUTABLE_FILE}}" ]]; then + echo "ERROR: could not resolve executable target: {executable_runfiles_path}" >&2 + exit 1 +fi + +export SYSROOT_DIR +if [[ -n "{exclude_paths}" ]]; then + export FAKECHROOT_EXCLUDE_PATH="{exclude_paths}" +fi + +# The executable lives in host runfiles, not in the sysroot. Exclude its path +# so fakechroot does not redirect accesses to it into the sysroot. +EXECUTABLE_DIR="$(dirname "${{EXECUTABLE_FILE}}")" +if [[ -n "${{FAKECHROOT_EXCLUDE_PATH:-}}" ]]; then + export FAKECHROOT_EXCLUDE_PATH="${{EXECUTABLE_DIR}}:${{EXECUTABLE_FILE}}:${{FAKECHROOT_EXCLUDE_PATH}}" +else + export FAKECHROOT_EXCLUDE_PATH="${{EXECUTABLE_DIR}}:${{EXECUTABLE_FILE}}" +fi + +exec "${{FAKECHROOT_WRAPPER}}" "${{EXECUTABLE_FILE}}" "$@" +""".format( + wrapper_short_path = ctx.workspace_name + "/" + ctx.executable._fakechroot_wrapper.short_path, + sysroot_short_path = sysroot_runfiles_path, + executable_runfiles_path = executable_runfiles_path, + exclude_paths = exclude_paths, + ) + ctx.actions.write(output = out, content = wrapper_script, is_executable = True) + + runfiles = ctx.runfiles( + files = [out, ctx.executable._fakechroot_wrapper, sysroot, executable_file] + ctx.files._bash_runfiles, + ) + runfiles = _merge_default_and_data_runfiles(ctx.attr.executable, runfiles) + runfiles = _merge_default_and_data_runfiles(ctx.attr._fakechroot_wrapper, runfiles) + runfiles = _merge_default_and_data_runfiles(ctx.attr._bash_runfiles, runfiles) + runfiles = _merge_default_and_data_runfiles(ctx.attr.sysroot, runfiles) + + return [DefaultInfo( + executable = out, + files = depset([out]), + runfiles = runfiles, + )] + +exec_in_sysroot = rule( + implementation = _exec_in_sysroot_impl, + executable = True, + attrs = { + "executable": attr.label(mandatory = True, executable = True, cfg = "exec"), + "sysroot": attr.label(mandatory = True, allow_single_file = True), + "exclude_paths": attr.string_list( + default = [], + doc = "Paths to exclude from fakechroot path-redirection (colon-separated).", + ), + "_bash_runfiles": attr.label( + default = Label("@bazel_tools//tools/bash/runfiles"), + allow_files = True, + ), + "_fakechroot_wrapper": attr.label( + default = Label("//bazel/rules/exec_in_sysroot"), + executable = True, + cfg = "exec", + ), + }, + toolchains = [_TAR_TOOLCHAIN_TYPE], + doc = """ + Produces an executable wrapper that runs a given executable target using the + supplied sysroot archive. The archive is unpacked in-rule and the wrapped + executable runs within fakechroot via LD_PRELOAD, allowing access to sysroot + tools and libraries hermetically. + + The archive is expected to be a reworked sysroot (see prepare_sysroot), which + performs plugin pruning / post-install setup once and caches the result. + """, +) diff --git a/bazel/rules/exec_in_sysroot/exec_in_sysroot.sh b/bazel/rules/exec_in_sysroot/exec_in_sysroot.sh new file mode 100755 index 00000000..f4918dcd --- /dev/null +++ b/bazel/rules/exec_in_sysroot/exec_in_sysroot.sh @@ -0,0 +1,116 @@ +#!/bin/sh +# ******************************************************************************* +# 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 +# ******************************************************************************* +# Generic wrapper script that executes a command in fakechroot using the provided sysroot. +# +# Rather than using `fakechroot -- chroot SYSROOT COMMAND` (which requires COMMAND +# to exist inside the chroot), this script uses fakechroot's LD_PRELOAD mechanism +# directly: libfakechroot.so intercepts all file-system calls and redirects absolute +# paths to the sysroot, while FAKECHROOT_EXCLUDE_PATH allows the host-side launcher +# script to be found at its real location. +# +# Usage: +# exec_in_sysroot.sh [arguments...] +# +# Environment variables (set by the generated exec_in_sysroot wrapper): +# SYSROOT_DIR - Path to the extracted sysroot directory (required) +# FAKECHROOT_EXCLUDE_PATH - Colon-separated paths NOT to redirect (the host +# executable and its runfiles dir are pre-excluded) +# BUILD_WORKSPACE_DIRECTORY - Workspace root, automatically excluded (optional) + +set -eu + +COMMAND_IN_SYSROOT="${1:?Command must be provided as first argument}" +shift || true + +SYSROOT_DIR="${SYSROOT_DIR:?SYSROOT_DIR must be set}" + +FAKECHROOT_LIB="" +if [ -f "${SYSROOT_DIR}/usr/lib/x86_64-linux-gnu/fakechroot/libfakechroot.so" ]; then + FAKECHROOT_LIB="${SYSROOT_DIR}/usr/lib/x86_64-linux-gnu/fakechroot/libfakechroot.so" +elif [ -f "${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu/fakechroot/libfakechroot.so" ]; then + FAKECHROOT_LIB="${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu/fakechroot/libfakechroot.so" +fi +if [ -z "${FAKECHROOT_LIB}" ]; then + FAKECHROOT_LIB="$(find "${SYSROOT_DIR}/usr/lib" -path '*/fakechroot/libfakechroot.so' -type f | head -1 || true)" +fi +if [ -z "${FAKECHROOT_LIB}" ]; then + echo "ERROR: libfakechroot.so not found in sysroot" >&2 + exit 1 +fi + +FAKECHROOT_LIB_DIR="$(dirname "${FAKECHROOT_LIB}")" + +# Determine the sysroot's ELF interpreter (ld-linux.so) and the arch-specific +# library search path using well-known Debian multiarch paths rather than a +# fragile glob search. These are exported so that the wrapped executable can +# invoke sysroot ELF binaries via the sysroot's own dynamic linker — avoiding +# loading the sysroot's libc.so.6 alongside the host's already-loaded libc +# (two libc instances → segfault on systems without host graphviz installed). +# See third_party/docs_runtime/dot.sh for the usage pattern. +if [ -f "${SYSROOT_DIR}/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2" ]; then + SYSROOT_INTERP="${SYSROOT_DIR}/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2" + SYSROOT_LIBPATH="${SYSROOT_DIR}/usr/lib/x86_64-linux-gnu:${SYSROOT_DIR}/usr/lib" +elif [ -f "${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1" ]; then + SYSROOT_INTERP="${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1" + SYSROOT_LIBPATH="${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu:${SYSROOT_DIR}/usr/lib" +else + echo "ERROR: sysroot ELF interpreter not found (tried x86_64 and aarch64 paths)" >&2 + exit 1 +fi +export SYSROOT_INTERP SYSROOT_LIBPATH + +# Build the exclude paths list. +# Always exclude BUILD_WORKSPACE_DIRECTORY so workspace operations stay on host. +EXCLUDE_PATHS="" +if [ -n "${BUILD_WORKSPACE_DIRECTORY:-}" ]; then + EXCLUDE_PATHS="${BUILD_WORKSPACE_DIRECTORY}" +fi + +# Append any user-specified exclude paths. +if [ -n "${FAKECHROOT_EXCLUDE_PATH:-}" ]; then + if [ -n "${EXCLUDE_PATHS}" ]; then + EXCLUDE_PATHS="${EXCLUDE_PATHS}:${FAKECHROOT_EXCLUDE_PATH}" + else + EXCLUDE_PATHS="${FAKECHROOT_EXCLUDE_PATH}" + fi +fi + +# Exclude SYSROOT_INTERP (the sysroot's ld-linux.so) so fakechroot does not +# intercept its execution. When a sysroot binary is launched via +# exec "$SYSROOT_INTERP" --library-path "$SYSROOT_LIBPATH" "$SYSROOT_DIR/bin" +# the path "$SYSROOT_DIR/…/ld-linux.so.2" starts with FAKECHROOT_BASE. +# Fakechroot would translate it to a sysroot-relative path and fail to exec it +# (the kernel reports ENOENT). Listing the full path here tells fakechroot to +# pass the execve through unchanged. +if [ -n "${SYSROOT_INTERP}" ]; then + if [ -n "${EXCLUDE_PATHS}" ]; then + EXCLUDE_PATHS="${EXCLUDE_PATHS}:${SYSROOT_INTERP}" + else + EXCLUDE_PATHS="${SYSROOT_INTERP}" + fi +fi + +if [ -n "${EXCLUDE_PATHS}" ]; then + export FAKECHROOT_EXCLUDE_PATH="${EXCLUDE_PATHS}" +fi + +# Wire libfakechroot.so as LD_PRELOAD and point FAKECHROOT_BASE at the sysroot +# directory. All absolute file-system calls (open, stat, execve, …) inside the +# launched process are transparently redirected to SYSROOT_DIR/ unless the +# path is listed in FAKECHROOT_EXCLUDE_PATH. +export FAKECHROOT_BASE="${SYSROOT_DIR}" +export LD_PRELOAD="${FAKECHROOT_LIB}${LD_PRELOAD:+:${LD_PRELOAD}}" +export LD_LIBRARY_PATH="${FAKECHROOT_LIB_DIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" + +exec "${COMMAND_IN_SYSROOT}" "$@" diff --git a/bazel/rules/rules_score/BUILD b/bazel/rules/rules_score/BUILD index 8f958492..ee9f360c 100644 --- a/bazel/rules/rules_score/BUILD +++ b/bazel/rules/rules_score/BUILD @@ -185,9 +185,6 @@ sphinx_module( py_binary( name = "raw_build", srcs = ["src/sphinx_wrapper.py"], - data = [ - "//tools/sphinx:plantuml", - ], env = { "SOURCE_DIRECTORY": "", "DATA": "", @@ -198,7 +195,6 @@ py_binary( deps = [ ":sphinx_module_ext", "@lobster//sphinx_lobster:sphinx_lobster_builder", - "@rules_python//python/runfiles", "@score_tooling//plantuml/sphinx/clickable_plantuml", "@trlc//tools/sphinx/extensions/trlc", requirement("sphinx"), diff --git a/bazel/rules/rules_score/docs/integration_guide.rst b/bazel/rules/rules_score/docs/integration_guide.rst index 313df2f1..313f7b7e 100644 --- a/bazel/rules/rules_score/docs/integration_guide.rst +++ b/bazel/rules/rules_score/docs/integration_guide.rst @@ -175,3 +175,120 @@ Design Rationale 6. **Build System Integration** — Bazel ensures reproducible, cacheable documentation builds Reference implementation: `examples/seooc `_ in the score-tooling repository. + +--- + +.. _sphinx-hermetic-tool-setup: + +Hermetic Diagram Tools (Graphviz and PlantUML) +---------------------------------------------- + +The Sphinx HTML action shells out to two diagram tools at **runtime** (inside +Bazel actions): ``dot`` from Graphviz and PlantUML. Both are hermetic — +i.e.\ no host installation required. The two tools use different +delivery mechanisms, described below. + +Graphviz / ``dot`` +~~~~~~~~~~~~~~~~~~ + +**Source and packaging** + +Graphviz now comes directly from the docs runtime sysroot +(``@docs_runtime//:flat``), built with ``rules_distroless`` from +``//third_party/docs_runtime/docs_runtime.yaml``. The Sphinx action does not +call ``dot`` directly; it uses ``//third_party/docs_runtime:dot`` — an +``exec_in_sysroot`` wrapper that unpacks the sysroot archive and runs +``/usr/bin/dot`` inside it through ``fakechroot``. + +**Where the files land (execroot-relative paths)** + +.. code-block:: text + + bazel-bin/third_party/docs_runtime/dot ← GRAPHVIZ_DOT env var + bazel-bin/third_party/docs_runtime/dot_sysroot/ ← unpacked docs_runtime rootfs + usr/bin/dot + usr/lib/graphviz/... + usr/bin/fakechroot + +**Wiring into the Sphinx action** + +The Bazel rule sets one variable: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Env var + - Content + * - ``GRAPHVIZ_DOT`` + - Path to the ``dot`` binary +The value points to the hermetic wrapper executable. The wrapper resolves and +executes graphviz from the sysroot itself, so no custom ``LD_LIBRARY_PATH`` / +``GVBINDIR`` wiring is required in the Sphinx action. + +**Resolving paths in conf.py** + +``GRAPHVIZ_DOT`` is set as an *execroot-relative* path. Because Sphinx changes +the process working directory during the build, these paths would break if +used as-is. ``conf.template.py`` therefore: + +1. Captures ``_EXECROOT = Path.cwd()`` at **module import time** (cwd is still + the execroot at that point). +2. Calls ``_resolve_execroot_path()`` on the value to prepend ``_EXECROOT`` + and produce absolute paths. + +PlantUML +~~~~~~~~ + +**Source and packaging** + +PlantUML is fetched from **Maven Central** via ``rules_jvm_external`` +(declared in ``MODULE.bazel``). It is wrapped as a ``java_binary`` at +``//tools/sphinx:plantuml`` in ``tools/sphinx/BUILD``. + +The PlantUML target is added to the sphinx-build binary (``raw_build``) as a +``data`` dependency, making it a **runfile** of that binary — not an +independent action input. + +**Where the file lands (runfiles-relative path)** + +.. code-block:: text + + {sphinx_build_binary}.runfiles/ + {repo_name}/tools/sphinx/plantuml ← wrapper script (absolute path via Runfiles API) + +The ``{repo_name}`` prefix depends on the Bzlmod configuration: + +- ``_main`` — when score_tooling is the root module (e.g.\ building within + the score-tooling repo itself) +- ``score_tooling`` / ``score_tooling+`` / ``score_tooling~`` — when + score_tooling is an external dependency of another project + +**Discovering the binary in conf.py** + +Because the repo name varies, ``conf.template.py`` uses two-stage discovery: + +1. **Manifest scan (primary):** Read ``RUNFILES_MANIFEST_FILE`` and search + for any entry whose runfiles path ends in ``/tools/sphinx/plantuml``. This + requires no knowledge of the repo name prefix. +2. **Runfiles API fallback:** If no manifest file is available (directory-based + runfiles trees on some platforms), fall back to + ``Runfiles.Create().Rlocation()`` with a list of known repo-name candidates + (``_main``, ``score_tooling``, ``score_tooling+``, …). + +The Runfiles API returns an **absolute path** directly, so no +``_resolve_execroot_path()`` is required for PlantUML. + +**Connecting PlantUML to Graphviz** + +Once both paths are resolved, ``conf.template.py`` assembles the PlantUML +command: + +.. code-block:: python + + plantuml = f"{plantuml_path} -graphvizdot {graphviz_dot}" + +The ``-graphvizdot`` flag makes PlantUML use the hermetic ``dot`` binary for +diagram layout instead of its bundled Java port (Smetana). This ensures that +the graphviz version is identical for both ``sphinx.ext.graphviz`` directives +and PlantUML diagrams. diff --git a/bazel/rules/rules_score/docs/tooling_architecture.rst b/bazel/rules/rules_score/docs/tooling_architecture.rst index 4838664d..319850b6 100644 --- a/bazel/rules/rules_score/docs/tooling_architecture.rst +++ b/bazel/rules/rules_score/docs/tooling_architecture.rst @@ -290,3 +290,142 @@ directory consumed by Sphinx. to the outer index toctree. ``fmea`` uses this for the ``detail_*.rst`` sub-pages, which are referenced from the inner ``.. toctree::`` inside ``fmea.rst`` rather than from the section index. + +.. _hermetic-tool-path-resolution: + +Hermetic tool path resolution +---------------- + +Background: Bazel action environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Paths available at **analysis time** (Starlark ``ctx.executable.foo.path``, +``ctx.executable.foo.short_path``) are always relative to the *execroot* — +the per-action working directory Bazel creates under the output base. Two +variants exist: + +- ``file.path`` — ``bazel-out//bin/third_party/docs_runtime/dot``. + Contains the exec-configuration hash; valid only at action run-time as a + path relative to ``cwd``. +- ``file.short_path`` — ``third_party/docs_runtime/dot`` (or + ``../external_repo/…``). Hash-free; stable across rebuilds; the canonical + **rlocation key** after stripping a leading ``../``. + +What the rule passes +~~~~~~~~~~~~~~~~~~~~~ + +For each tool ``sphinx_module.bzl`` computes both variants and injects them as +environment variables: + +.. code-block:: text + + PLANTUML_BIN = ctx.executable._plantuml.path (execroot-relative) + PLANTUML_BIN_RLOC = ctx.executable._plantuml.short_path (rlocation key) + GRAPHVIZ_DOT = ctx.executable._graphviz.path (execroot-relative) + GRAPHVIZ_DOT_RLOC = ctx.executable._graphviz.short_path (rlocation key) + +The rlocation keys (``*_RLOC``) are computed once at analysis time: + +.. code-block:: python + + _gv_short = ctx.executable._graphviz.short_path + _graphviz_rloc = ( + _gv_short[3:] # strip "../" + if _gv_short.startswith("../") + else ctx.workspace_name + "/" + _gv_short + ) + +This matches the Bazel bash-runfiles convention used internally by the +``exec_in_sysroot`` wrapper itself (``rlocation +'/third_party/docs_runtime/dot'``). + +How conf.template.py resolves the paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``conf.py`` is loaded during **Sphinx initialisation**, before Sphinx performs +any ``os.chdir()``. Bazel guarantees that the action's ``cwd`` equals the +execroot at process start. Therefore a single ``os.path.abspath()`` call +converts the execroot-relative ``*_BIN`` / ``*_DOT`` value to a stable +absolute path for the entire action lifetime: + +.. code-block:: python + + plantuml_path = os.path.abspath(os.environ["PLANTUML_BIN"]) + graphviz_dot = os.path.abspath(os.environ["GRAPHVIZ_DOT"]) + + +Why rlocation alone cannot resolve the binary +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A natural question is: why not call +``python.runfiles.Runfiles.Create().Rlocation(graphviz_rloc_key)`` directly and +skip the ``abspath`` step? + +Two reasons make this impractical: + +1. **Wrong ``RUNFILES_DIR``.** The Sphinx Python process inherits + ``RUNFILES_DIR`` pointing to the *sphinx tool's* own runfiles tree (set by + the ``rules_python`` ``py_binary`` launcher). The Graphviz and PlantUML + tools are in ``tools=`` of the action, which makes their files available in + the sandbox but does **not** merge them into the sphinx binary's runfiles. + Calling ``Runfiles.Create()`` without an override therefore searches the + wrong tree and returns ``None`` for both tool keys. + +2. **The symlink-path problem.** As a workaround one could construct + ``Runfiles.Create({"RUNFILES_DIR": abspath_dot + ".runfiles"})`` (the + companion runfiles directory Bazel creates for every executable). The dot + wrapper *is* a member of its own runfiles tree (``exec_in_sysroot`` adds + the generated script to ``ctx.runfiles(files=[out, …])`` so the smoke test + can resolve it via ``rlocation``). However, ``Runfiles.Rlocation()`` + returns the path **inside the symlink forest**, not the real binary path. + Passing that symlink path to a subprocess means ``$0`` is the symlink, so + ``$0.runfiles/`` does not exist and the wrapper's runfiles bootstrap falls + back to ``RUNFILES_DIR`` — which still points to the sphinx binary's + runfiles. The wrapper fails to find ``SYSROOT_DIR``, ``FAKECHROOT_WRAPPER``, + and ``EXECUTABLE_FILE`` and exits with an error. + +The ``os.path.abspath()`` approach avoids both issues: it yields the real +binary path (not a symlink in a runfiles forest), so ``$0.runfiles/`` +bootstrapping in the wrapper works correctly. + +The exec_in_sysroot wrapper's own runfiles bootstrap +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The generated wrapper produced by ``exec_in_sysroot`` contains the standard +Bazel bash-runfiles initialisation block: + +.. code-block:: bash + + if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && \ + ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ -f "$0.runfiles_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" + elif [[ -f "$0.runfiles/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" + elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + export RUNFILES_DIR="$0.runfiles" + fi + fi + +Because ``graphviz_dot`` is the **absolute** path to the real binary (not a +symlink), ``$0`` equals that absolute path, and ``$0.runfiles/`` is the actual +companion runfiles directory Bazel created for the binary. The block finds +``bazel_tools/tools/bash/runfiles/runfiles.bash`` there and correctly +bootstraps ``RUNFILES_DIR`` to the dot wrapper's own runfiles — regardless of +what ``RUNFILES_DIR`` the parent process (Sphinx/Python) has set in its +environment. + +PlantUML and the Smetana fallback +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``sphinxcontrib.plantuml`` invokes PlantUML via +``shlex.split(app.config.plantuml)``. When the hermetic dot is available the +``plantuml`` config value is: + +.. code-block:: text + + -graphvizdot + +This tells PlantUML to use the hermetic dot for its internal layout calls +(PlantUML generates a ``.dot`` intermediate for class/component/sequence +diagrams and hands it to graphviz for layout). diff --git a/bazel/rules/rules_score/private/sphinx_module.bzl b/bazel/rules/rules_score/private/sphinx_module.bzl index ac230727..06c3123c 100644 --- a/bazel/rules/rules_score/private/sphinx_module.bzl +++ b/bazel/rules/rules_score/private/sphinx_module.bzl @@ -69,6 +69,16 @@ sphinx_rule_attrs = dict( "deps": attr.label_list( doc = "List of other sphinx_module targets this module depends on for intersphinx.", ), + "_plantuml": attr.label( + default = Label("//third_party/plantuml:plantuml"), + executable = True, + cfg = "exec", + ), + "_graphviz": attr.label( + default = Label("//third_party/docs_runtime:dot"), + executable = True, + cfg = "exec", + ), }, **VERBOSITY_ATTR ) @@ -106,14 +116,31 @@ def _score_needs_impl(ctx): "--log-level", get_log_level(ctx), ] + + # Compute analysis-time-stable rlocation keys from short_path (no exec-config + # hash, no parent-directory walking). These are passed to conf.py for + # diagnostic logging and as the canonical Bazel identity of each tool. + # See docs/tooling_architecture.rst §"Hermetic tool path resolution". + _gv_short = ctx.executable._graphviz.short_path + _graphviz_rloc = _gv_short[3:] if _gv_short.startswith("../") else ctx.workspace_name + "/" + _gv_short + _pl_short = ctx.executable._plantuml.short_path + _plantuml_rloc = _pl_short[3:] if _pl_short.startswith("../") else ctx.workspace_name + "/" + _pl_short ctx.actions.run( inputs = needs_inputs, outputs = [needs_output], arguments = needs_args, + env = { + "PLANTUML_BIN": ctx.executable._plantuml.path, + "PLANTUML_BIN_RLOC": _plantuml_rloc, + "GRAPHVIZ_DOT": ctx.executable._graphviz.path, + "GRAPHVIZ_DOT_RLOC": _graphviz_rloc, + }, progress_message = "Generating needs.json for: %s" % ctx.label.name, executable = sphinx_toolchain.sphinx.files_to_run.executable, tools = [ sphinx_toolchain.sphinx.files_to_run, + ctx.attr._plantuml.files_to_run, + ctx.attr._graphviz.files_to_run, ], ) transitive_needs = [dep[SphinxNeedsInfo].needs_json_files for dep in ctx.attr.deps if SphinxNeedsInfo in dep] @@ -238,39 +265,33 @@ def _score_html_impl(ctx): get_log_level(ctx), ] - # Wire in the hermetic graphviz deb (dot_builtins + bundled shared libs) if provided. - # conf.template.py resolves all three env vars (GRAPHVIZ_DOT, - # LD_LIBRARY_PATH, LTDL_LIBRARY_PATH) from execroot-relative to absolute - # paths so dot_builtins can load its plugins without a system installation. - graphviz_env = {} - graphviz_files = ctx.files.graphviz - if graphviz_files: - _dot_suffix = "/usr/bin/dot_builtins" - dot_binary = None - for f in graphviz_files: - if f.path.endswith(_dot_suffix): - dot_binary = f - break - if not dot_binary: - fail("graphviz target {} must provide usr/bin/dot_builtins".format(ctx.attr.graphviz)) - - graphviz_prefix = dot_binary.path[:-len(_dot_suffix)] - graphviz_env = { - "GRAPHVIZ_DOT": dot_binary.path, - "LD_LIBRARY_PATH": graphviz_prefix + "/usr/lib", - "LTDL_LIBRARY_PATH": graphviz_prefix + "/usr/lib/graphviz", - } - html_inputs = html_inputs + graphviz_files + # Use the hermetic graphviz wrapper that executes `/usr/bin/dot` inside the + # docs_runtime sysroot via exec_in_sysroot. + # Compute analysis-time-stable rlocation keys from short_path (no exec-config + # hash, no parent-directory walking). See docs/tooling_architecture.rst + # §"Hermetic tool path resolution". + _gv_short = ctx.executable._graphviz.short_path + _graphviz_rloc = _gv_short[3:] if _gv_short.startswith("../") else ctx.workspace_name + "/" + _gv_short + _pl_short = ctx.executable._plantuml.short_path + _plantuml_rloc = _pl_short[3:] if _pl_short.startswith("../") else ctx.workspace_name + "/" + _pl_short + action_env = { + "PLANTUML_BIN": ctx.executable._plantuml.path, + "PLANTUML_BIN_RLOC": _plantuml_rloc, + "GRAPHVIZ_DOT": ctx.executable._graphviz.path, + "GRAPHVIZ_DOT_RLOC": _graphviz_rloc, + } ctx.actions.run( inputs = html_inputs, outputs = [sphinx_html_output], arguments = html_args + [args], - env = graphviz_env, + env = action_env, progress_message = "Building HTML: %s" % ctx.label.name, executable = sphinx_toolchain.sphinx.files_to_run.executable, tools = [ sphinx_toolchain.sphinx.files_to_run, + ctx.attr._plantuml.files_to_run, + ctx.attr._graphviz.files_to_run, ], ) @@ -358,13 +379,6 @@ _score_html = rule( "destination paths relative to the Sphinx source root. Exactly one " + "file per label. Mirrors sphinx_docs.renamed_srcs from rules_python.", ), - graphviz = attr.label( - default = None, - allow_files = True, - doc = "Graphviz cmake-release deb files (dot_builtins binary + bundled libs). " + - "Only available on Linux x86_64; provides a hermetic 'dot' binary without requiring a system graphviz installation. " + - "Defaults to @graphviz_deb//:all on Linux x86_64.", - ), ), toolchains = ["//bazel/rules/rules_score:toolchain_type"], ) @@ -383,7 +397,6 @@ def sphinx_module( strip_prefix = "", extra_opts = [], extra_opts_targets = [], - graphviz = None, testonly = False, **kwargs): """Build a Sphinx module with transitive HTML dependencies. @@ -408,10 +421,6 @@ def sphinx_module( extra_opts_targets: {type}`list[label]` Label targets that resolve to extra Sphinx arguments at analysis time. Each target must provide FilteredExecpathInfo (e.g. filter_execpath targets). - graphviz: Graphviz cmake-release deb files (dot_builtins + bundled libs). On Linux x86_64, - defaults to @graphviz_deb//:all for hermetic graphviz support. On other platforms - or if explicitly set to None, no graphviz support is provided (the sphinx.ext.graphviz - extension will not be available). visibility: Bazel visibility """ _score_needs( @@ -432,7 +441,6 @@ def sphinx_module( needs = [d + "_needs" for d in deps], extra_opts = extra_opts, extra_opts_targets = extra_opts_targets, - graphviz = graphviz, testonly = testonly, **kwargs ) diff --git a/bazel/rules/rules_score/templates/conf.template.py b/bazel/rules/rules_score/templates/conf.template.py index df5964d1..41259225 100644 --- a/bazel/rules/rules_score/templates/conf.template.py +++ b/bazel/rules/rules_score/templates/conf.template.py @@ -22,58 +22,12 @@ import os import sys from pathlib import Path -from typing import List -from python.runfiles import Runfiles from sphinx.util import logging # Create a logger with the Sphinx namespace logger = logging.getLogger(__name__) -# --------------------------------------------------------------------------- -# Helpers: Bazel execroot path resolution -# --------------------------------------------------------------------------- - - -# Capture the current working directory at module import time. -# In Bazel action context, cwd == execroot. In IDE/non-Bazel runs, cwd is -# the current directory. This is captured once to avoid repeated resolution. -_EXECROOT = Path.cwd() - - -def _resolve_execroot_path(path_value: str) -> str: - """Resolve an execroot-relative path to an absolute filesystem path. - - Bazel passes action inputs as paths relative to the execroot (e.g. - ``external/+_repo_rules2+graphviz_deb/usr/bin/dot_builtins``). Those - paths are only valid when the process' cwd is the execroot — which is - not guaranteed once Sphinx changes directories during the build. - - This function makes them absolute so they work regardless of cwd. - Absolute paths and plain command names (e.g. ``dot``) are returned - unchanged. - """ - p = Path(path_value) - if p.is_absolute(): - return str(p) - if path_value.startswith("external/") or path_value.startswith("bazel-out/"): - # First try cwd-as-execroot (fast path). - candidate = (_EXECROOT / p).resolve() - if candidate.exists(): - return str(candidate) - - # If cwd is nested under bazel-out, walk upward and locate the first - # parent that contains the requested relative path. - for parent in [_EXECROOT, *_EXECROOT.parents]: - candidate = (parent / p).resolve() - if candidate.exists(): - return str(candidate) - - # Fallback: preserve previous behavior even if the file does not exist - # yet (keeps logging/debug output deterministic). - return str((_EXECROOT / p).resolve()) - return path_value - logger.debug("#" * 80) logger.debug("# READING CONF.PY") @@ -169,75 +123,101 @@ def _resolve_execroot_path(path_value: str) -> str: needs_id_regex = "^[A-Za-z0-9_-]{6,}" -# Use the runfiles to find the plantuml binary. -# Runfiles are only available when running in Bazel. -r = Runfiles.Create() -if r is None: - raise ValueError("Could not initialize Bazel runfiles.") - -plantuml_repo_candidates: List[str] = [] - -for repo_name in [ - os.environ.get("TEST_WORKSPACE"), - "_main", - "score_tooling", - "score_tooling~", - "score_tooling+", -]: - if repo_name and repo_name not in plantuml_repo_candidates: - plantuml_repo_candidates.append(repo_name) - -plantuml_runfiles_candidates = [ - f"{repo_name}/tools/sphinx/plantuml" for repo_name in plantuml_repo_candidates -] - -plantuml_path = None -for runfile_path in plantuml_runfiles_candidates: - candidate = r.Rlocation(runfile_path, source_repo="") - if candidate and Path(candidate).exists(): - plantuml_path = Path(candidate) - logger.info(f"Selected PlantUML from runfiles path {runfile_path}: {candidate}") - break - -if plantuml_path is None: - searched = ", ".join(plantuml_runfiles_candidates) +# --------------------------------------------------------------------------- +# PlantUML binary discovery +# --------------------------------------------------------------------------- +# PLANTUML_BIN — execroot-relative path of //third_party/plantuml:plantuml +# (a rules_java java_binary launcher script), injected by the +# sphinx_module Bazel rule via the action env. +# PLANTUML_BIN_RLOC — analysis-time-stable Bazel rlocation key derived from the +# target's short_path (no exec-config hash); used only for +# diagnostic logging here. +# +# Path resolution rationale (applies to all hermetic tool paths in this file): +# os.path.abspath() converts the execroot-relative path to an absolute path +# using the process's current working directory. Bazel guarantees that the +# action's cwd equals the execroot at process start. conf.py is loaded during +# Sphinx initialisation — before Sphinx can perform any os.chdir() — so the +# abspath() call is stable for the entire action lifetime. This replaces the +# previous _resolve_execroot_path() which walked parent directories as a +# fallback, a pattern that is fragile and wrong when nested under bazel-out/. +# See docs/tooling_architecture.rst §"Hermetic tool path resolution". +_plantuml_bin = os.environ.get("PLANTUML_BIN") +if not _plantuml_bin: raise ValueError( - f"Could not find plantuml binary via runfiles lookup. Searched: {searched}." + "PLANTUML_BIN environment variable is not set. It must point at the " + "//third_party/plantuml:plantuml launcher and is normally provided by the " + "sphinx_module Bazel rule. If you are invoking Sphinx outside that rule, " + "set PLANTUML_BIN to the plantuml binary path." ) +plantuml_path = os.path.abspath(_plantuml_bin) +logger.debug( + f"plantuml resolved: {plantuml_path} " + f"(rloc: {os.environ.get('PLANTUML_BIN_RLOC', 'n/a')})" +) -# Use PlantUML's built-in Smetana layout engine (Java port of Graphviz). -# This avoids requiring an external dot binary in the Bazel sandbox. -plantuml = f"{plantuml_path} -Playout=smetana" plantuml_output_format = "svg_obj" +# `plantuml` command is assembled below, after graphviz_dot is resolved, so +# PlantUML can use the same hermetic Graphviz dot (see Graphviz section). # --------------------------------------------------------------------------- # Graphviz (sphinx.ext.graphviz) # --------------------------------------------------------------------------- -# GRAPHVIZ_DOT is set by the Bazel sphinx_module rule to point at the hermetic -# dot_builtins binary from @graphviz_deb. The path is execroot-relative, so -# we resolve it to an absolute path here so it remains valid after any cwd -# change that Sphinx may perform during the build. -# If GRAPHVIZ_DOT is absent, force a known-invalid dot path so Sphinx fails -# clearly on graphviz directives instead of silently using host-installed dot. -if "GRAPHVIZ_DOT" in os.environ: - graphviz_dot = _resolve_execroot_path(os.environ["GRAPHVIZ_DOT"]) +# GRAPHVIZ_DOT — execroot-relative path of //third_party/docs_runtime:dot +# (the exec_in_sysroot bash wrapper), injected by the +# sphinx_module Bazel rule. +# GRAPHVIZ_DOT_RLOC — analysis-time-stable rlocation key; logged only. +# +# Path resolution: same os.path.abspath() rationale as PLANTUML_BIN above. +# +# The exec_in_sysroot wrapper is a runfiles-aware bash script that bootstraps +# its own runfiles via the standard $0.runfiles/ Bazel convention (identical +# to the runfiles.bash init block). Passing the ABSOLUTE path ensures $0 is +# absolute, so $0.runfiles/ resolves to the correct companion directory even +# when the wrapper is called as a subprocess from inside the Sphinx Python +# process (which carries its own RUNFILES_DIR pointing at the sphinx tool's +# runfiles, not the dot wrapper's runfiles). +# +# If GRAPHVIZ_DOT is absent a known-invalid sentinel is used so that +# sphinx.ext.graphviz fails loudly on any .. graphviz:: directive rather than +# silently using a host-installed dot binary. +_graphviz_dot_path = os.environ.get("GRAPHVIZ_DOT", "") +_graphviz_dot_rloc = os.environ.get("GRAPHVIZ_DOT_RLOC", "") +if _graphviz_dot_path: + graphviz_dot = os.path.abspath(_graphviz_dot_path) graphviz_output_format = "svg" - - # LD_LIBRARY_PATH and LTDL_LIBRARY_PATH are set by the Bazel rule as - # execroot-relative paths. We mutate os.environ (not just a local) because - # sphinx.ext.graphviz spawns `dot` as a child process that inherits these - # variables to locate the bundled shared libraries and plugins. Each - # component is resolved to absolute so it stays valid if Sphinx changes cwd - # before spawning the dot subprocess. - for _env_var in ("LD_LIBRARY_PATH", "LTDL_LIBRARY_PATH"): - _env_val = os.environ.get(_env_var, "") - if _env_val: - os.environ[_env_var] = ":".join( - _resolve_execroot_path(p) for p in _env_val.split(":") - ) + logger.debug( + f"graphviz dot resolved: {graphviz_dot} (rloc: {_graphviz_dot_rloc or 'n/a'})" + ) else: graphviz_dot = "/__hermetic_graphviz_not_configured__/dot" +# --------------------------------------------------------------------------- +# PlantUML layout engine: hermetic dot or Smetana fallback +# --------------------------------------------------------------------------- +# Wire PlantUML to the hermetic dot via -graphvizdot when the wrapper is +# accessible; otherwise fall back to PlantUML's built-in Smetana layout +# engine (a pure-Java re-implementation of Graphviz) with a warning. +# The hermetic dot is the reference rendering path; Smetana output may differ +# visually (different edge routing, node spacing). +# Note: the smetana fallback only affects PlantUML diagrams. sphinx.ext.graphviz +# directives always use graphviz_dot; they will fail if it is the sentinel. +_dot_available = ( + graphviz_dot != "/__hermetic_graphviz_not_configured__/dot" + and Path(graphviz_dot).is_file() +) +if _dot_available: + plantuml = f"{plantuml_path} -graphvizdot {graphviz_dot}" +else: + logger.warning( + f"Hermetic Graphviz dot is not available at {graphviz_dot!r}. " + "PlantUML is falling back to the built-in Smetana layout engine. " + "Diagrams may differ from the reference output produced by the " + "hermetic Bazel build. Ensure GRAPHVIZ_DOT / GRAPHVIZ_DOT_RLOC are " + "set correctly when invoking the sphinx_module rule." + ) + plantuml = f"{plantuml_path} -Playout=smetana" + # HTML theme html_theme = "sphinx_rtd_theme" diff --git a/bazel/rules/rules_score/test/MODULE.bazel b/bazel/rules/rules_score/test/MODULE.bazel index 81e40c0c..2b915d74 100644 --- a/bazel/rules/rules_score/test/MODULE.bazel +++ b/bazel/rules/rules_score/test/MODULE.bazel @@ -20,6 +20,13 @@ module( ############################################################################### # Core Dependencies ############################################################################### +bazel_dep(name = "download_utils", version = "1.2.2") +git_override( + module_name = "download_utils", + commit = "3b96912fb6622dda83f25efd1f8ae596fc4a63a6", + remote = "https://gitlab.arm.com/bazel/download_utils.git", +) + bazel_dep(name = "rules_python", version = "1.4.1") bazel_dep(name = "rules_cc", version = "0.2.17") bazel_dep(name = "aspect_rules_py", version = "1.4.0") diff --git a/third_party/docs_runtime/BUILD b/third_party/docs_runtime/BUILD new file mode 100644 index 00000000..3a747b52 --- /dev/null +++ b/third_party/docs_runtime/BUILD @@ -0,0 +1,81 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +# Hermetic doc-tool runtime rootfs. The @docs_runtime repo (rules_distroless +# apt.install) is defined in //MODULE.bazel; its manifest + lock live here. + +load("@rules_shell//shell:sh_binary.bzl", "sh_binary") +load("//bazel/rules/exec_in_sysroot:exec_in_sysroot.bzl", "exec_in_sysroot", "prepare_sysroot") + +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "docs_runtime.yaml", + "docs_runtime.lock.json", +]) + +# Host-side launcher that resolves `/usr/bin/dot` inside the fakechroot sysroot. +sh_binary( + name = "dot_in_sysroot", + srcs = ["dot.sh"], +) + +# Rework the distroless rootfs once into a cached `dot`-ready sysroot archive. +# The apt graphviz package ships /usr/bin/dot as a symlink to +# libgvc6-config-update; its postinst (`dot -c`) generates config6a so dot can +# locate and load its plugins at runtime. rules_distroless never runs postinst, +# so we generate it here while the sysroot is still writable. Keeping this in a +# separate rule lets Bazel cache the result (the expensive `dot -c` step is not +# re-run when only the wrapped executable / exclude_paths change). +prepare_sysroot( + name = "dot_sysroot", + # fakechroot + the x86_64 sysroot binaries only run on a linux/x86_64 host. + exec_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], + # Prune plugins whose X11/pango/GD dependencies are absent from this minimal + # sysroot (they cannot be dlopened, causing dot -c to crash). Use find (not + # a hardcoded arch path) so the rule works on both amd64 and aarch64. + host_setup_commands = [ + "find \"$SYSROOT/usr/lib\" -name 'libgvplugin_pango*' -delete", + "find \"$SYSROOT/usr/lib\" -name 'libgvplugin_gd*' -delete", + "find \"$SYSROOT/usr/lib\" -name 'libgvplugin_xlib*' -delete", + "find \"$SYSROOT/usr/lib\" -name 'libgvplugin_webp*' -delete", + "find \"$SYSROOT/usr/lib\" -name 'libgvplugin_visio*' -delete", + ], + sysroot = "@docs_runtime//:flat", + sysroot_setup_commands = [ + # Generate config6a for the remaining plugins. Runs inside the sysroot + # context so FAKECHROOT_BASE redirects /usr/bin/dot to the sysroot's dot + # and the resulting config6a is written into the sysroot. + "/usr/bin/dot -c", + ], +) + +# Hermetic graphviz dot executable used by doc actions. +exec_in_sysroot( + name = "dot", + # /tmp must not be chrooted so that PlantUML's per-diagram .dot / .svg temp + # files (written to the real /tmp) are accessible to dot without being + # redirected into the sysroot. Graphviz-internal paths (/usr/lib/…/graphviz, + # /usr/share/fonts, …) are still correctly redirected by fakechroot. + exclude_paths = ["/tmp"], + # fakechroot + the x86_64 sysroot binaries only run on a linux/x86_64 host. + exec_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], + executable = ":dot_in_sysroot", + sysroot = ":dot_sysroot", +) diff --git a/third_party/docs_runtime/README.md b/third_party/docs_runtime/README.md new file mode 100644 index 00000000..6db55cdf --- /dev/null +++ b/third_party/docs_runtime/README.md @@ -0,0 +1,65 @@ + + +# Hermetic graphviz for docs (`docs_runtime` + `exec_in_sysroot`) + +The docs build needs Graphviz `dot` at action runtime (Sphinx graphviz extension +and PlantUML `-graphvizdot`). This package makes that use hermetic: + +1. `@docs_runtime//:flat` provides a distroless rootfs tar (from + `docs_runtime.yaml` + `docs_runtime.lock.json`) containing `graphviz` and + `fakechroot`. +2. `//third_party/docs_runtime:dot_sysroot` (a `prepare_sysroot` rule) unpacks + that tar, prunes plugins with missing host dependencies, runs `dot -c` to + generate the plugin manifest, and repackages the result as a single cached + archive. +3. `//third_party/docs_runtime:dot` (an `exec_in_sysroot` rule) extracts the + prepared archive at build time and wraps `dot.sh` so that `/usr/bin/dot` + inside the sysroot is invoked via `LD_PRELOAD=libfakechroot.so`. + +Rules that need graphviz use `//third_party/docs_runtime:dot` as the executable +and receive its path via the `GRAPHVIZ_DOT` environment variable. + +## Caveats + +This setup is reproducible but not *fully* hermetic — two host dependencies +remain: + +1. **Host glibc / ld.so ABI compatibility.** `exec_in_sysroot` runs `dot` via + `LD_PRELOAD=libfakechroot.so` rather than a real chroot, so the kernel still + launches the sysroot's `dot` with the *host* ELF interpreter (`ld.so`), which + then loads the sysroot's glibc. The sysroot is pinned to Ubuntu 24.04 (glibc + 2.39); on a build host whose glibc is older than the sysroot's, `dot` can + fail to start with `GLIBC_2.xx not found`. Build hosts therefore need a glibc + at least as new as the pinned sysroot. +2. **Host shell tools.** The sysroot-rework and extraction actions run under a + POSIX `sh` and use standard coreutils (`find`, `mktemp`, `chmod`, `rm`), + assumed present in the build environment. The generated `dot` launcher itself + requires `bash`, because it sources Bazel's `runfiles.bash` library (there is + no POSIX-`sh` runfiles equivalent in `@bazel_tools`). + +## Updating packages + +Edit `docs_runtime.yaml`, then regenerate and commit the lock: + +```bash +bazel run @docs_runtime//:lock +``` + +## Targets in this package + +- `//third_party/docs_runtime:dot` - hermetic executable used by doc actions. +- `//third_party/docs_runtime/tests:dot_smoke_test` - renders a tiny SVG through + the wrapper to verify runtime wiring. diff --git a/third_party/docs_runtime/docs_runtime.lock.json b/third_party/docs_runtime/docs_runtime.lock.json new file mode 100644 index 00000000..849ff117 --- /dev/null +++ b/third_party/docs_runtime/docs_runtime.lock.json @@ -0,0 +1,1387 @@ +{ + "packages": [ + { + "arch": "amd64", + "dependencies": [ + { + "key": "libxt6_1-1.2.1-1.1_amd64", + "name": "libxt6", + "version": "1:1.2.1-1.1" + }, + { + "key": "libx11-6_2-1.8.7-1_amd64", + "name": "libx11-6", + "version": "2:1.8.7-1" + }, + { + "key": "libx11-data_2-1.8.7-1_amd64", + "name": "libx11-data", + "version": "2:1.8.7-1" + }, + { + "key": "libxcb1_1.15-1_amd64", + "name": "libxcb1", + "version": "1.15-1" + }, + { + "key": "libxdmcp6_1-1.1.3-0ubuntu5_amd64", + "name": "libxdmcp6", + "version": "1:1.1.3-0ubuntu5" + }, + { + "key": "libc6_2.39-0ubuntu2_amd64", + "name": "libc6", + "version": "2.39-0ubuntu2" + }, + { + "key": "libgcc-s1_14-20240221-2.1ubuntu1_amd64", + "name": "libgcc-s1", + "version": "14-20240221-2.1ubuntu1" + }, + { + "key": "gcc-14-base_14-20240221-2.1ubuntu1_amd64", + "name": "gcc-14-base", + "version": "14-20240221-2.1ubuntu1" + }, + { + "key": "libbsd0_0.11.8-1_amd64", + "name": "libbsd0", + "version": "0.11.8-1" + }, + { + "key": "libmd0_1.1.0-2_amd64", + "name": "libmd0", + "version": "1.1.0-2" + }, + { + "key": "libxau6_1-1.0.9-1build5_amd64", + "name": "libxau6", + "version": "1:1.0.9-1build5" + }, + { + "key": "libsm6_2-1.2.3-1build2_amd64", + "name": "libsm6", + "version": "2:1.2.3-1build2" + }, + { + "key": "libuuid1_2.39.3-6ubuntu2_amd64", + "name": "libuuid1", + "version": "2.39.3-6ubuntu2" + }, + { + "key": "libice6_2-1.0.10-1build2_amd64", + "name": "libice6", + "version": "2:1.0.10-1build2" + }, + { + "key": "x11-common_1-7.7-p-23ubuntu2_amd64", + "name": "x11-common", + "version": "1:7.7+23ubuntu2" + }, + { + "key": "lsb-base_11.6_amd64", + "name": "lsb-base", + "version": "11.6" + }, + { + "key": "sysvinit-utils_3.08-6ubuntu2_amd64", + "name": "sysvinit-utils", + "version": "3.08-6ubuntu2" + }, + { + "key": "libxmu6_2-1.1.3-3_amd64", + "name": "libxmu6", + "version": "2:1.1.3-3" + }, + { + "key": "libxext6_2-1.3.4-1build1_amd64", + "name": "libxext6", + "version": "2:1.3.4-1build1" + }, + { + "key": "libxaw7_2-1.0.14-1_amd64", + "name": "libxaw7", + "version": "2:1.0.14-1" + }, + { + "key": "libxpm4_1-3.5.17-1_amd64", + "name": "libxpm4", + "version": "1:3.5.17-1" + }, + { + "key": "libstdc-p--p-6_14-20240221-2.1ubuntu1_amd64", + "name": "libstdc++6", + "version": "14-20240221-2.1ubuntu1" + }, + { + "key": "liblab-gamut1_2.42.2-8build1_amd64", + "name": "liblab-gamut1", + "version": "2.42.2-8build1" + }, + { + "key": "libgvpr2_2.42.2-8build1_amd64", + "name": "libgvpr2", + "version": "2.42.2-8build1" + }, + { + "key": "libcgraph6_2.42.2-8build1_amd64", + "name": "libcgraph6", + "version": "2.42.2-8build1" + }, + { + "key": "libcdt5_2.42.2-8build1_amd64", + "name": "libcdt5", + "version": "2.42.2-8build1" + }, + { + "key": "libgvc6_2.42.2-8build1_amd64", + "name": "libgvc6", + "version": "2.42.2-8build1" + }, + { + "key": "zlib1g_1-1.3.dfsg-3ubuntu1_amd64", + "name": "zlib1g", + "version": "1:1.3.dfsg-3ubuntu1" + }, + { + "key": "libwebp7_1.3.2-0.4_amd64", + "name": "libwebp7", + "version": "1.3.2-0.4" + }, + { + "key": "libsharpyuv0_1.3.2-0.4_amd64", + "name": "libsharpyuv0", + "version": "1.3.2-0.4" + }, + { + "key": "libpathplan4_2.42.2-8build1_amd64", + "name": "libpathplan4", + "version": "2.42.2-8build1" + }, + { + "key": "libpangoft2-1.0-0_1.51.0-p-ds-4_amd64", + "name": "libpangoft2-1.0-0", + "version": "1.51.0+ds-4" + }, + { + "key": "libpango-1.0-0_1.51.0-p-ds-4_amd64", + "name": "libpango-1.0-0", + "version": "1.51.0+ds-4" + }, + { + "key": "libthai0_0.1.29-2_amd64", + "name": "libthai0", + "version": "0.1.29-2" + }, + { + "key": "libdatrie1_0.2.13-3_amd64", + "name": "libdatrie1", + "version": "0.2.13-3" + }, + { + "key": "libthai-data_0.1.29-2_amd64", + "name": "libthai-data", + "version": "0.1.29-2" + }, + { + "key": "libharfbuzz0b_8.3.0-2_amd64", + "name": "libharfbuzz0b", + "version": "8.3.0-2" + }, + { + "key": "libgraphite2-3_1.3.14-2_amd64", + "name": "libgraphite2-3", + "version": "1.3.14-2" + }, + { + "key": "libglib2.0-0_2.79.2-1_ubuntu1_amd64", + "name": "libglib2.0-0", + "version": "2.79.2-1~ubuntu1" + }, + { + "key": "libselinux1_3.5-2build1_amd64", + "name": "libselinux1", + "version": "3.5-2build1" + }, + { + "key": "libpcre2-8-0_10.42-4ubuntu1_amd64", + "name": "libpcre2-8-0", + "version": "10.42-4ubuntu1" + }, + { + "key": "libmount1_2.39.3-6ubuntu2_amd64", + "name": "libmount1", + "version": "2.39.3-6ubuntu2" + }, + { + "key": "libblkid1_2.39.3-6ubuntu2_amd64", + "name": "libblkid1", + "version": "2.39.3-6ubuntu2" + }, + { + "key": "libffi8_3.4.6-1_amd64", + "name": "libffi8", + "version": "3.4.6-1" + }, + { + "key": "libfreetype6_2.13.2-p-dfsg-1_amd64", + "name": "libfreetype6", + "version": "2.13.2+dfsg-1" + }, + { + "key": "libpng16-16_1.6.43-1_amd64", + "name": "libpng16-16", + "version": "1.6.43-1" + }, + { + "key": "libbz2-1.0_1.0.8-5ubuntu1_amd64", + "name": "libbz2-1.0", + "version": "1.0.8-5ubuntu1" + }, + { + "key": "libbrotli1_1.1.0-2_amd64", + "name": "libbrotli1", + "version": "1.1.0-2" + }, + { + "key": "libfribidi0_1.0.13-3_amd64", + "name": "libfribidi0", + "version": "1.0.13-3" + }, + { + "key": "fontconfig_2.15.0-1ubuntu1_amd64", + "name": "fontconfig", + "version": "2.15.0-1ubuntu1" + }, + { + "key": "fontconfig-config_2.15.0-1ubuntu1_amd64", + "name": "fontconfig-config", + "version": "2.15.0-1ubuntu1" + }, + { + "key": "fonts-dejavu-core_2.37-8_amd64", + "name": "fonts-dejavu-core", + "version": "2.37-8" + }, + { + "key": "fonts-dejavu-mono_2.37-8_amd64", + "name": "fonts-dejavu-mono", + "version": "2.37-8" + }, + { + "key": "libfontconfig1_2.15.0-1ubuntu1_amd64", + "name": "libfontconfig1", + "version": "2.15.0-1ubuntu1" + }, + { + "key": "libexpat1_2.6.0-1_amd64", + "name": "libexpat1", + "version": "2.6.0-1" + }, + { + "key": "libpangocairo-1.0-0_1.51.0-p-ds-4_amd64", + "name": "libpangocairo-1.0-0", + "version": "1.51.0+ds-4" + }, + { + "key": "libcairo2_1.18.0-1_amd64", + "name": "libcairo2", + "version": "1.18.0-1" + }, + { + "key": "libxrender1_1-0.9.10-1.1_amd64", + "name": "libxrender1", + "version": "1:0.9.10-1.1" + }, + { + "key": "libxcb-shm0_1.15-1_amd64", + "name": "libxcb-shm0", + "version": "1.15-1" + }, + { + "key": "libxcb-render0_1.15-1_amd64", + "name": "libxcb-render0", + "version": "1.15-1" + }, + { + "key": "libpixman-1-0_0.42.2-1_amd64", + "name": "libpixman-1-0", + "version": "0.42.2-1" + }, + { + "key": "libltdl7_2.4.7-7_amd64", + "name": "libltdl7", + "version": "2.4.7-7" + }, + { + "key": "libgts-0.7-5_0.7.6-p-darcs121130-5_amd64", + "name": "libgts-0.7-5", + "version": "0.7.6+darcs121130-5" + }, + { + "key": "libgd3_2.3.3-9ubuntu1_amd64", + "name": "libgd3", + "version": "2.3.3-9ubuntu1" + }, + { + "key": "libtiff6_4.5.1-p-git230720-3ubuntu1_amd64", + "name": "libtiff6", + "version": "4.5.1+git230720-3ubuntu1" + }, + { + "key": "libzstd1_1.5.5-p-dfsg2-2_amd64", + "name": "libzstd1", + "version": "1.5.5+dfsg2-2" + }, + { + "key": "liblzma5_5.4.5-0.3_amd64", + "name": "liblzma5", + "version": "5.4.5-0.3" + }, + { + "key": "liblerc4_4.0.0-p-ds-4ubuntu1_amd64", + "name": "liblerc4", + "version": "4.0.0+ds-4ubuntu1" + }, + { + "key": "libjpeg8_8c-2ubuntu11_amd64", + "name": "libjpeg8", + "version": "8c-2ubuntu11" + }, + { + "key": "libjpeg-turbo8_2.1.5-2ubuntu1_amd64", + "name": "libjpeg-turbo8", + "version": "2.1.5-2ubuntu1" + }, + { + "key": "libjbig0_2.1-6.1ubuntu1_amd64", + "name": "libjbig0", + "version": "2.1-6.1ubuntu1" + }, + { + "key": "libdeflate0_1.19-1_amd64", + "name": "libdeflate0", + "version": "1.19-1" + }, + { + "key": "libann0_1.1.2-p-doc-9_amd64", + "name": "libann0", + "version": "1.1.2+doc-9" + } + ], + "key": "graphviz_2.42.2-8build1_amd64", + "name": "graphviz", + "sha256": "374d49111008ae01c6084d867d619127fe5288d95e02cd34e7cbb5b41f1ebf68", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/g/graphviz/graphviz_2.42.2-8build1_amd64.deb" + ], + "version": "2.42.2-8build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxt6_1-1.2.1-1.1_amd64", + "name": "libxt6", + "sha256": "d986c63413ad679d9d90e54febe368626cc6dc02379fd33f41e62b8c88cee762", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxt/libxt6_1.2.1-1.1_amd64.deb" + ], + "version": "1:1.2.1-1.1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libx11-6_2-1.8.7-1_amd64", + "name": "libx11-6", + "sha256": "7fa366fcb328ec857795055c7023b2a7c86ee7edd30d970317d7301f98aca464", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libx11/libx11-6_1.8.7-1_amd64.deb" + ], + "version": "2:1.8.7-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libx11-data_2-1.8.7-1_amd64", + "name": "libx11-data", + "sha256": "c5a338c5a95da05a7efc8d1f6fbff9ee23c2b3c9ef3e2c4e602a72f91083fff6", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libx11/libx11-data_1.8.7-1_all.deb" + ], + "version": "2:1.8.7-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxcb1_1.15-1_amd64", + "name": "libxcb1", + "sha256": "b0520c13009a66323747692b8d05b2b253d20748109cb4e7606e83a644fd2669", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxcb/libxcb1_1.15-1_amd64.deb" + ], + "version": "1.15-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxdmcp6_1-1.1.3-0ubuntu5_amd64", + "name": "libxdmcp6", + "sha256": "6f6c869dcfc072fa77d24b72ba127bdf15d606bdffbeba4314fb39f4f324363d", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.3-0ubuntu5_amd64.deb" + ], + "version": "1:1.1.3-0ubuntu5" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libc6_2.39-0ubuntu2_amd64", + "name": "libc6", + "sha256": "4bd128b75db38b7e9147c0333908e2c7fbc41631f284360f95118fe1c6c162f3", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/g/glibc/libc6_2.39-0ubuntu2_amd64.deb" + ], + "version": "2.39-0ubuntu2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libgcc-s1_14-20240221-2.1ubuntu1_amd64", + "name": "libgcc-s1", + "sha256": "ffc195df7e897aaec468e8f62b08660cc711c7449113102491fdd6baa6901f6d", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/g/gcc-14/libgcc-s1_14-20240221-2.1ubuntu1_amd64.deb" + ], + "version": "14-20240221-2.1ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "gcc-14-base_14-20240221-2.1ubuntu1_amd64", + "name": "gcc-14-base", + "sha256": "2e1ae2c2ccf2d1b6d09c657af1492a8b7a348e899f9ad25d4925b170571a0887", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/g/gcc-14/gcc-14-base_14-20240221-2.1ubuntu1_amd64.deb" + ], + "version": "14-20240221-2.1ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libbsd0_0.11.8-1_amd64", + "name": "libbsd0", + "sha256": "7b6d144fa736de5572ba8558a1c72ff1cd5c3ff08aa462d861858ece75fb1cf3", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libb/libbsd/libbsd0_0.11.8-1_amd64.deb" + ], + "version": "0.11.8-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libmd0_1.1.0-2_amd64", + "name": "libmd0", + "sha256": "128be9909c4ce8f2126e5f3d1a04fc11510c519409d64d324d724aae8347cd13", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libm/libmd/libmd0_1.1.0-2_amd64.deb" + ], + "version": "1.1.0-2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxau6_1-1.0.9-1build5_amd64", + "name": "libxau6", + "sha256": "551fa4101394af894f5f401285eeca9756da94bb31ecb08ed36a549ec580cd10", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxau/libxau6_1.0.9-1build5_amd64.deb" + ], + "version": "1:1.0.9-1build5" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libsm6_2-1.2.3-1build2_amd64", + "name": "libsm6", + "sha256": "e652e286a79d9e8e15189d79b290bdce20aca83651f3dfed1b9b7dc0bbf0702f", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libs/libsm/libsm6_1.2.3-1build2_amd64.deb" + ], + "version": "2:1.2.3-1build2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libuuid1_2.39.3-6ubuntu2_amd64", + "name": "libuuid1", + "sha256": "eec85f07cf7a65483d953b4dbdd857b9b34d33f69b317580d18b8a6c90f628dc", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/u/util-linux/libuuid1_2.39.3-6ubuntu2_amd64.deb" + ], + "version": "2.39.3-6ubuntu2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libice6_2-1.0.10-1build2_amd64", + "name": "libice6", + "sha256": "eea6d52d12ad610d70b0f70c825ed3c560dafb09f4c272e3c6b0b493d984d13f", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libi/libice/libice6_1.0.10-1build2_amd64.deb" + ], + "version": "2:1.0.10-1build2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "x11-common_1-7.7-p-23ubuntu2_amd64", + "name": "x11-common", + "sha256": "c0a20d0b7ba899f00d22b6f18e49a336aace0514f9ef7f3858c1ab689011c559", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/x/xorg/x11-common_7.7+23ubuntu2_all.deb" + ], + "version": "1:7.7+23ubuntu2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "lsb-base_11.6_amd64", + "name": "lsb-base", + "sha256": "08a563e3c1984acdb295f12b77b4ec18d2ccf52fd03280a5bea29e506a3350ee", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/l/lsb/lsb-base_11.6_all.deb" + ], + "version": "11.6" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "sysvinit-utils_3.08-6ubuntu2_amd64", + "name": "sysvinit-utils", + "sha256": "5e716fef226555264232a284cbd95f17dda7020989fb57d3e0bf5a82f3e5e031", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/s/sysvinit/sysvinit-utils_3.08-6ubuntu2_amd64.deb" + ], + "version": "3.08-6ubuntu2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxmu6_2-1.1.3-3_amd64", + "name": "libxmu6", + "sha256": "4f820d1ce1171fc67f62172874b3380a45757222c96f8c60b154118893f2c46c", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxmu/libxmu6_1.1.3-3_amd64.deb" + ], + "version": "2:1.1.3-3" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxext6_2-1.3.4-1build1_amd64", + "name": "libxext6", + "sha256": "875ec849749511dff61c8cc5a775f6f93427b9c3f392fdcec6925f14428e89cd", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxext/libxext6_1.3.4-1build1_amd64.deb" + ], + "version": "2:1.3.4-1build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxaw7_2-1.0.14-1_amd64", + "name": "libxaw7", + "sha256": "b3cd73ad59824f72193194e984ff433eabc2e4618047ca91e9d66011b5e0b78c", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxaw/libxaw7_1.0.14-1_amd64.deb" + ], + "version": "2:1.0.14-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxpm4_1-3.5.17-1_amd64", + "name": "libxpm4", + "sha256": "768bf1c25aaa8e457b837a054a596143a3896227e539043fcfdf2f76b34598e8", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxpm/libxpm4_3.5.17-1_amd64.deb" + ], + "version": "1:3.5.17-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libstdc-p--p-6_14-20240221-2.1ubuntu1_amd64", + "name": "libstdc++6", + "sha256": "3311c13f2e26c20369e937051c78f07c495f6112a0d6c32d3285b47021457ec2", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/g/gcc-14/libstdc++6_14-20240221-2.1ubuntu1_amd64.deb" + ], + "version": "14-20240221-2.1ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "liblab-gamut1_2.42.2-8build1_amd64", + "name": "liblab-gamut1", + "sha256": "8bc2b25cf9ce39d0d9418d74db1356e5689eda1e17b83683524538515c0e8cd3", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/g/graphviz/liblab-gamut1_2.42.2-8build1_amd64.deb" + ], + "version": "2.42.2-8build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libgvpr2_2.42.2-8build1_amd64", + "name": "libgvpr2", + "sha256": "bea31611f89412c79c37d64c33eaff5b683b7d7a38976424fdc97ab1f623387e", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/g/graphviz/libgvpr2_2.42.2-8build1_amd64.deb" + ], + "version": "2.42.2-8build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libcgraph6_2.42.2-8build1_amd64", + "name": "libcgraph6", + "sha256": "d7d7beaaa6e12d58731dbffbf6eedd26a0696a226fb9947f4ded38a6a47cf3c6", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/g/graphviz/libcgraph6_2.42.2-8build1_amd64.deb" + ], + "version": "2.42.2-8build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libcdt5_2.42.2-8build1_amd64", + "name": "libcdt5", + "sha256": "b5ab71098443817ef224824642d5c7baf0d44238099c981c05e407fffaa2365c", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/g/graphviz/libcdt5_2.42.2-8build1_amd64.deb" + ], + "version": "2.42.2-8build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libgvc6_2.42.2-8build1_amd64", + "name": "libgvc6", + "sha256": "c9b14f9cd6d4d4fbd7460dcc209700b30b9c250a2ca61e7a501d9ad65f1128d8", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/g/graphviz/libgvc6_2.42.2-8build1_amd64.deb" + ], + "version": "2.42.2-8build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "zlib1g_1-1.3.dfsg-3ubuntu1_amd64", + "name": "zlib1g", + "sha256": "35cfe44912765862374112e83c178c095448f247785772147c42c0c843b67c97", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/z/zlib/zlib1g_1.3.dfsg-3ubuntu1_amd64.deb" + ], + "version": "1:1.3.dfsg-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libwebp7_1.3.2-0.4_amd64", + "name": "libwebp7", + "sha256": "606fcfa98061d56b7d9b4ebd3020a42a06e2284792d0f047db6b1e53cc460b54", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libw/libwebp/libwebp7_1.3.2-0.4_amd64.deb" + ], + "version": "1.3.2-0.4" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libsharpyuv0_1.3.2-0.4_amd64", + "name": "libsharpyuv0", + "sha256": "9aaaa112b6af98d954721df6fcde87f55123730616e4a79ba7d83ce2d0c06beb", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libw/libwebp/libsharpyuv0_1.3.2-0.4_amd64.deb" + ], + "version": "1.3.2-0.4" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libpathplan4_2.42.2-8build1_amd64", + "name": "libpathplan4", + "sha256": "5070946c9436ae1d9e2f88d74ef2b31d8eabfcdf0f236eb62eaff0ad20c4d6d1", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/g/graphviz/libpathplan4_2.42.2-8build1_amd64.deb" + ], + "version": "2.42.2-8build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libpangoft2-1.0-0_1.51.0-p-ds-4_amd64", + "name": "libpangoft2-1.0-0", + "sha256": "cc28d62a7d28f1628de46c9245957e450101ad75ebf935d5c71851f4db663aa2", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.51.0+ds-4_amd64.deb" + ], + "version": "1.51.0+ds-4" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libpango-1.0-0_1.51.0-p-ds-4_amd64", + "name": "libpango-1.0-0", + "sha256": "a1269198f67c0e65ca048a5bdeab271580ab47b439b4681b890423ced9081854", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/p/pango1.0/libpango-1.0-0_1.51.0+ds-4_amd64.deb" + ], + "version": "1.51.0+ds-4" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libthai0_0.1.29-2_amd64", + "name": "libthai0", + "sha256": "44b44cea90bc3ce86f5939484ec4e2c1f15374f7c5cdeb0a633f49925f1b4a57", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libt/libthai/libthai0_0.1.29-2_amd64.deb" + ], + "version": "0.1.29-2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libdatrie1_0.2.13-3_amd64", + "name": "libdatrie1", + "sha256": "3e42fdb17441dd98e2774ecd54f2f700debfaead777c34947288381f113ccb5c", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libd/libdatrie/libdatrie1_0.2.13-3_amd64.deb" + ], + "version": "0.2.13-3" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libthai-data_0.1.29-2_amd64", + "name": "libthai-data", + "sha256": "2616af1ff9f29f9bc76b65060808621fb028721adee7e0ccf15f7b06f55fe4c5", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libt/libthai/libthai-data_0.1.29-2_all.deb" + ], + "version": "0.1.29-2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libharfbuzz0b_8.3.0-2_amd64", + "name": "libharfbuzz0b", + "sha256": "b70b342b10796bd5dccd21ebec8aaef0d01520939d0991dd5a354db6f26aad74", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/h/harfbuzz/libharfbuzz0b_8.3.0-2_amd64.deb" + ], + "version": "8.3.0-2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libgraphite2-3_1.3.14-2_amd64", + "name": "libgraphite2-3", + "sha256": "7ba5cdb08364ac065c945a5338adc199fe80086c2fc32116262d9647287fd65f", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/g/graphite2/libgraphite2-3_1.3.14-2_amd64.deb" + ], + "version": "1.3.14-2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libglib2.0-0_2.79.2-1_ubuntu1_amd64", + "name": "libglib2.0-0", + "sha256": "1aa724b05316bea1cbeae71681aae81f8c3b3d29002ef3ab9eca9b156e0a4b41", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/g/glib2.0/libglib2.0-0_2.79.2-1~ubuntu1_amd64.deb" + ], + "version": "2.79.2-1~ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libselinux1_3.5-2build1_amd64", + "name": "libselinux1", + "sha256": "139f29430e3d265fc8d9b9da7dd3f704ee3f1838c37a5d512cf265ec0b4eba28", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libs/libselinux/libselinux1_3.5-2build1_amd64.deb" + ], + "version": "3.5-2build1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libpcre2-8-0_10.42-4ubuntu1_amd64", + "name": "libpcre2-8-0", + "sha256": "3fbf30adf862c4e510a9260c7666a1a5326bc5fed8021090bc75a4ecbaa52fa4", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/p/pcre2/libpcre2-8-0_10.42-4ubuntu1_amd64.deb" + ], + "version": "10.42-4ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libmount1_2.39.3-6ubuntu2_amd64", + "name": "libmount1", + "sha256": "00ea5406ad8d934883a52d531c95e9faeb88e2e77fd0da59129eac0e126fec1b", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/u/util-linux/libmount1_2.39.3-6ubuntu2_amd64.deb" + ], + "version": "2.39.3-6ubuntu2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libblkid1_2.39.3-6ubuntu2_amd64", + "name": "libblkid1", + "sha256": "9a16eb730c6e3bffd852c062a972942fdc653b994b8d3c9cadd6aec3c393d284", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/u/util-linux/libblkid1_2.39.3-6ubuntu2_amd64.deb" + ], + "version": "2.39.3-6ubuntu2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libffi8_3.4.6-1_amd64", + "name": "libffi8", + "sha256": "bd30f638a82381979c4c07b3acabb7fccaeed7f9b094e27c9a676d2e94572b14", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libf/libffi/libffi8_3.4.6-1_amd64.deb" + ], + "version": "3.4.6-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libfreetype6_2.13.2-p-dfsg-1_amd64", + "name": "libfreetype6", + "sha256": "1b7e8083626fd0b2426c6f0099239da2cdc4f19abb2360ba981a2b67a0742d81", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/f/freetype/libfreetype6_2.13.2+dfsg-1_amd64.deb" + ], + "version": "2.13.2+dfsg-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libpng16-16_1.6.43-1_amd64", + "name": "libpng16-16", + "sha256": "472bbb169d929d85a420723a7921b90e72c5e295cfdd3954135eb0f1b09a94fa", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libp/libpng1.6/libpng16-16_1.6.43-1_amd64.deb" + ], + "version": "1.6.43-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libbz2-1.0_1.0.8-5ubuntu1_amd64", + "name": "libbz2-1.0", + "sha256": "8925b88fac7e8162a5c9dfcb078bb33932cb8aee51bb33db209ca97840f65369", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/bzip2/libbz2-1.0_1.0.8-5ubuntu1_amd64.deb" + ], + "version": "1.0.8-5ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libbrotli1_1.1.0-2_amd64", + "name": "libbrotli1", + "sha256": "a46004c3521a4ee502a6bc2d48e1e71d0f1bb0e0eac57538da323a271b0feb23", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/brotli/libbrotli1_1.1.0-2_amd64.deb" + ], + "version": "1.1.0-2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libfribidi0_1.0.13-3_amd64", + "name": "libfribidi0", + "sha256": "65ae08f9f61a5b03fe54a0aa2f6bc2743bd8400d0868eaeca33f393702e6175b", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/f/fribidi/libfribidi0_1.0.13-3_amd64.deb" + ], + "version": "1.0.13-3" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "fontconfig_2.15.0-1ubuntu1_amd64", + "name": "fontconfig", + "sha256": "9adc8083ecb8219778e3b55c3508b173f33acef8fd11f06961f9f6e78335409b", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/f/fontconfig/fontconfig_2.15.0-1ubuntu1_amd64.deb" + ], + "version": "2.15.0-1ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "fontconfig-config_2.15.0-1ubuntu1_amd64", + "name": "fontconfig-config", + "sha256": "616ecdbdc07ac946a51cdd79cb9ab2cb62d7a3dd2c5b31f678b88e3e3fba3112", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/f/fontconfig/fontconfig-config_2.15.0-1ubuntu1_amd64.deb" + ], + "version": "2.15.0-1ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "fonts-dejavu-core_2.37-8_amd64", + "name": "fonts-dejavu-core", + "sha256": "40049660c194f3b8a2541fc7369efebb10e9f94bdac836a2f38fafedd10fa73a", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/f/fonts-dejavu/fonts-dejavu-core_2.37-8_all.deb" + ], + "version": "2.37-8" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "fonts-dejavu-mono_2.37-8_amd64", + "name": "fonts-dejavu-mono", + "sha256": "8a599d6553307db7ecb795d2f0e5a301e03234afc75c7358b0ba43466454c89a", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/f/fonts-dejavu/fonts-dejavu-mono_2.37-8_all.deb" + ], + "version": "2.37-8" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libfontconfig1_2.15.0-1ubuntu1_amd64", + "name": "libfontconfig1", + "sha256": "db339d0cc32e96db73f54d5b0fc5b1caf19fcedbb9b9383cc0f4c0596ef8175a", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/f/fontconfig/libfontconfig1_2.15.0-1ubuntu1_amd64.deb" + ], + "version": "2.15.0-1ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libexpat1_2.6.0-1_amd64", + "name": "libexpat1", + "sha256": "3951af935e90fd2148c05c727c0b014f59af70c8ab534b8270d73fa4e6f7136c", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/e/expat/libexpat1_2.6.0-1_amd64.deb" + ], + "version": "2.6.0-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libpangocairo-1.0-0_1.51.0-p-ds-4_amd64", + "name": "libpangocairo-1.0-0", + "sha256": "abaab59fef4291a42661342bd3f70f0ea2077cbd712babb7d3fb14ba95583767", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/p/pango1.0/libpangocairo-1.0-0_1.51.0+ds-4_amd64.deb" + ], + "version": "1.51.0+ds-4" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libcairo2_1.18.0-1_amd64", + "name": "libcairo2", + "sha256": "7455c38efbe1ea351c0527b8d5baa2a47cbf70a602ecbfec930e903367a04e3c", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/c/cairo/libcairo2_1.18.0-1_amd64.deb" + ], + "version": "1.18.0-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxrender1_1-0.9.10-1.1_amd64", + "name": "libxrender1", + "sha256": "28029f0f1ce6d1ae411a2e4b923d089f7db783d783c8d9d71dec70c0e4e97055", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxrender/libxrender1_0.9.10-1.1_amd64.deb" + ], + "version": "1:0.9.10-1.1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxcb-shm0_1.15-1_amd64", + "name": "libxcb-shm0", + "sha256": "fc5d86790eb2d9a354724aa1495d590f378ec6529833547c22e1bed492805775", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxcb/libxcb-shm0_1.15-1_amd64.deb" + ], + "version": "1.15-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libxcb-render0_1.15-1_amd64", + "name": "libxcb-render0", + "sha256": "02dc666363085db34369a80c7e184eab3ab44f185f023137fa36643fb4df13c1", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libx/libxcb/libxcb-render0_1.15-1_amd64.deb" + ], + "version": "1.15-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libpixman-1-0_0.42.2-1_amd64", + "name": "libpixman-1-0", + "sha256": "a1de5be8a592907bdddea465c895d6a10a9b09087abbee06f3ab015d011e8d65", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/p/pixman/libpixman-1-0_0.42.2-1_amd64.deb" + ], + "version": "0.42.2-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libltdl7_2.4.7-7_amd64", + "name": "libltdl7", + "sha256": "4bfabbbaafbdaf912bf60bcb29aff8ee16016e792443523a1cba7d35b09b7bb1", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libt/libtool/libltdl7_2.4.7-7_amd64.deb" + ], + "version": "2.4.7-7" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libgts-0.7-5_0.7.6-p-darcs121130-5_amd64", + "name": "libgts-0.7-5", + "sha256": "9f0d6b143b4f602dd5b78dbef49491c7e0e50154fdcfa5da6e1261e763d9cd32", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/g/gts/libgts-0.7-5_0.7.6+darcs121130-5_amd64.deb" + ], + "version": "0.7.6+darcs121130-5" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libgd3_2.3.3-9ubuntu1_amd64", + "name": "libgd3", + "sha256": "97a8bedd02f36c1a2116378e25a034a08a174232ecd4a22ab172fa671a257e03", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libg/libgd2/libgd3_2.3.3-9ubuntu1_amd64.deb" + ], + "version": "2.3.3-9ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libtiff6_4.5.1-p-git230720-3ubuntu1_amd64", + "name": "libtiff6", + "sha256": "8e64d80b612d8b0a0b8010fb923a2512f9a364cfea52879df54cad967a742daa", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/t/tiff/libtiff6_4.5.1+git230720-3ubuntu1_amd64.deb" + ], + "version": "4.5.1+git230720-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libzstd1_1.5.5-p-dfsg2-2_amd64", + "name": "libzstd1", + "sha256": "7926bb8267652dd7df2c78c5e7541df6e62dbc10ed2efd4c2b869c75538b2ff1", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libz/libzstd/libzstd1_1.5.5+dfsg2-2_amd64.deb" + ], + "version": "1.5.5+dfsg2-2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "liblzma5_5.4.5-0.3_amd64", + "name": "liblzma5", + "sha256": "02bb3148ccfa7408b3f12833aa483c2dd4e3a6ee647fe8bbc3bc60ef50761ead", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/x/xz-utils/liblzma5_5.4.5-0.3_amd64.deb" + ], + "version": "5.4.5-0.3" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "liblerc4_4.0.0-p-ds-4ubuntu1_amd64", + "name": "liblerc4", + "sha256": "496372cd2853f3d8e2d92980fa3b602f7a1acd7528bd3d7e9a41046d8286cc8b", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/l/lerc/liblerc4_4.0.0+ds-4ubuntu1_amd64.deb" + ], + "version": "4.0.0+ds-4ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libjpeg8_8c-2ubuntu11_amd64", + "name": "libjpeg8", + "sha256": "4d6ed682972bddf62af158a06d68a0dda497e421423b7ca92fb450b3d9ceaac9", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libj/libjpeg8-empty/libjpeg8_8c-2ubuntu11_amd64.deb" + ], + "version": "8c-2ubuntu11" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libjpeg-turbo8_2.1.5-2ubuntu1_amd64", + "name": "libjpeg-turbo8", + "sha256": "448bb8a52b1f223833f02cb08f7f03c68579cd989027ccf783ef1c37f4cef946", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libj/libjpeg-turbo/libjpeg-turbo8_2.1.5-2ubuntu1_amd64.deb" + ], + "version": "2.1.5-2ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libjbig0_2.1-6.1ubuntu1_amd64", + "name": "libjbig0", + "sha256": "b7630bdc99393411011cb71c7504f6366cf269deba9c58862b2580e3e4f8be1f", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/j/jbigkit/libjbig0_2.1-6.1ubuntu1_amd64.deb" + ], + "version": "2.1-6.1ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libdeflate0_1.19-1_amd64", + "name": "libdeflate0", + "sha256": "635aea936792f65506256967ce957ed66a50cdb363264d1c5cea174c4705cedb", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/libd/libdeflate/libdeflate0_1.19-1_amd64.deb" + ], + "version": "1.19-1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libann0_1.1.2-p-doc-9_amd64", + "name": "libann0", + "sha256": "06be66eac231276f29fdb5d6f8e71fbc4ad714efe96ed8fbfaeecb73696e94ac", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/a/ann/libann0_1.1.2+doc-9_amd64.deb" + ], + "version": "1.1.2+doc-9" + }, + { + "arch": "amd64", + "dependencies": [ + { + "key": "libfakechroot_2.20.1-p-ds-15_amd64", + "name": "libfakechroot", + "version": "2.20.1+ds-15" + }, + { + "key": "libc6_2.39-0ubuntu2_amd64", + "name": "libc6", + "version": "2.39-0ubuntu2" + }, + { + "key": "libgcc-s1_14-20240221-2.1ubuntu1_amd64", + "name": "libgcc-s1", + "version": "14-20240221-2.1ubuntu1" + }, + { + "key": "gcc-14-base_14-20240221-2.1ubuntu1_amd64", + "name": "gcc-14-base", + "version": "14-20240221-2.1ubuntu1" + }, + { + "key": "binutils_2.42-3ubuntu1_amd64", + "name": "binutils", + "version": "2.42-3ubuntu1" + }, + { + "key": "binutils-x86-64-linux-gnu_2.42-3ubuntu1_amd64", + "name": "binutils-x86-64-linux-gnu", + "version": "2.42-3ubuntu1" + }, + { + "key": "zlib1g_1-1.3.dfsg-3ubuntu1_amd64", + "name": "zlib1g", + "version": "1:1.3.dfsg-3ubuntu1" + }, + { + "key": "libzstd1_1.5.5-p-dfsg2-2_amd64", + "name": "libzstd1", + "version": "1.5.5+dfsg2-2" + }, + { + "key": "libstdc-p--p-6_14-20240221-2.1ubuntu1_amd64", + "name": "libstdc++6", + "version": "14-20240221-2.1ubuntu1" + }, + { + "key": "libsframe1_2.42-3ubuntu1_amd64", + "name": "libsframe1", + "version": "2.42-3ubuntu1" + }, + { + "key": "libjansson4_2.14-2_amd64", + "name": "libjansson4", + "version": "2.14-2" + }, + { + "key": "libgprofng0_2.42-3ubuntu1_amd64", + "name": "libgprofng0", + "version": "2.42-3ubuntu1" + }, + { + "key": "libbinutils_2.42-3ubuntu1_amd64", + "name": "libbinutils", + "version": "2.42-3ubuntu1" + }, + { + "key": "binutils-common_2.42-3ubuntu1_amd64", + "name": "binutils-common", + "version": "2.42-3ubuntu1" + }, + { + "key": "libctf0_2.42-3ubuntu1_amd64", + "name": "libctf0", + "version": "2.42-3ubuntu1" + }, + { + "key": "libctf-nobfd0_2.42-3ubuntu1_amd64", + "name": "libctf-nobfd0", + "version": "2.42-3ubuntu1" + } + ], + "key": "fakechroot_2.20.1-p-ds-15_amd64", + "name": "fakechroot", + "sha256": "f311e5c449314e8e66427277e901a415d3d77d4e3e8c91817a4a831e47467675", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/f/fakechroot/fakechroot_2.20.1+ds-15_all.deb" + ], + "version": "2.20.1+ds-15" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libfakechroot_2.20.1-p-ds-15_amd64", + "name": "libfakechroot", + "sha256": "6a9dd5907de0b94ab9e79cedd39b91a5cc783708675869dd588b0f5bebb97d70", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/universe/f/fakechroot/libfakechroot_2.20.1+ds-15_amd64.deb" + ], + "version": "2.20.1+ds-15" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "binutils_2.42-3ubuntu1_amd64", + "name": "binutils", + "sha256": "f047bb9c3db90cb28d334e2c5bcc0914e1688e6e14d37afeb52100917e87c107", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/binutils/binutils_2.42-3ubuntu1_amd64.deb" + ], + "version": "2.42-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "binutils-x86-64-linux-gnu_2.42-3ubuntu1_amd64", + "name": "binutils-x86-64-linux-gnu", + "sha256": "f68656486334d2d6e16c9ca296d99d175aa083979d7de03e1e62046f7774b117", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/binutils/binutils-x86-64-linux-gnu_2.42-3ubuntu1_amd64.deb" + ], + "version": "2.42-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libsframe1_2.42-3ubuntu1_amd64", + "name": "libsframe1", + "sha256": "58d05ca5a4fd1790835c5c136d2a4a499d7d2513150b708cf8e12603e8e17add", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/binutils/libsframe1_2.42-3ubuntu1_amd64.deb" + ], + "version": "2.42-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libjansson4_2.14-2_amd64", + "name": "libjansson4", + "sha256": "55703f7b04f0235f6b86d67a3c4263a41e6cf30c2b5d5e1e00312368ebd49971", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/j/jansson/libjansson4_2.14-2_amd64.deb" + ], + "version": "2.14-2" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libgprofng0_2.42-3ubuntu1_amd64", + "name": "libgprofng0", + "sha256": "0db398e08c159645833edad7b536f0afd67064cc2d1a9d9d3e9055d059760044", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/binutils/libgprofng0_2.42-3ubuntu1_amd64.deb" + ], + "version": "2.42-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libbinutils_2.42-3ubuntu1_amd64", + "name": "libbinutils", + "sha256": "fa69181cf5034e7b136e666100963eba6fce7a835d4223b12b6c03f749420a0f", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/binutils/libbinutils_2.42-3ubuntu1_amd64.deb" + ], + "version": "2.42-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "binutils-common_2.42-3ubuntu1_amd64", + "name": "binutils-common", + "sha256": "26129a7c67435a2f6107f05112596b59be66dd90a8349f13e8a7602587a7374c", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/binutils/binutils-common_2.42-3ubuntu1_amd64.deb" + ], + "version": "2.42-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libctf0_2.42-3ubuntu1_amd64", + "name": "libctf0", + "sha256": "16b279d18d5d8223c248d20ded7e8983e4dc577378d4af903e7d2b450251811b", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/binutils/libctf0_2.42-3ubuntu1_amd64.deb" + ], + "version": "2.42-3ubuntu1" + }, + { + "arch": "amd64", + "dependencies": [], + "key": "libctf-nobfd0_2.42-3ubuntu1_amd64", + "name": "libctf-nobfd0", + "sha256": "455bd3996749dc3b3b74c9e0fea6865d1806f2ae650d3b30c505ca3643ed3c18", + "urls": [ + "https://snapshot.ubuntu.com/ubuntu/20240301T030400Z/pool/main/b/binutils/libctf-nobfd0_2.42-3ubuntu1_amd64.deb" + ], + "version": "2.42-3ubuntu1" + } + ], + "version": 1 +} diff --git a/third_party/docs_runtime/docs_runtime.yaml b/third_party/docs_runtime/docs_runtime.yaml new file mode 100644 index 00000000..f7ccce1a --- /dev/null +++ b/third_party/docs_runtime/docs_runtime.yaml @@ -0,0 +1,32 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +# Sysroot for hermetic docs tooling. It provides graphviz `dot` plus fakechroot, +# so //third_party/docs_runtime:dot can execute /usr/bin/dot fully from sysroot. +# +# Run `bazel run @docs_runtime//:lock` whenever you change this file, then commit +# the regenerated docs_runtime.lock.json. +# +# Pinned to the Ubuntu 24.04 (noble) snapshot so package closure is reproducible. +version: 1 +sources: + - channel: noble main universe + url: https://snapshot.ubuntu.com/ubuntu/20260401T000000Z + - channel: noble-security main universe + url: https://snapshot.ubuntu.com/ubuntu/20260401T000000Z + - channel: noble-updates main universe + url: https://snapshot.ubuntu.com/ubuntu/20260401T000000Z +archs: + - "amd64" +packages: + - "graphviz" # /usr/bin/dot and plugins used by Sphinx + PlantUML + - "fakechroot" # /usr/bin/fakechroot + libfakechroot.so for exec_in_sysroot diff --git a/third_party/docs_runtime/dot.sh b/third_party/docs_runtime/dot.sh new file mode 100755 index 00000000..dc695147 --- /dev/null +++ b/third_party/docs_runtime/dot.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# ******************************************************************************* +# 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 +# ******************************************************************************* + +set -eu + +# Use the sysroot's own ELF interpreter (exported by exec_in_sysroot.sh as +# SYSROOT_INTERP) with an explicit --library-path so that dot and all of its +# shared-library dependencies (libgvc.so.6, libcgraph.so.6, …) are loaded +# entirely from the sysroot. +# +# Running dot through the HOST's ld-linux.so and a wide LD_LIBRARY_PATH would +# cause the sysroot's libc.so.6 to load alongside the host's already-resident +# libc (two libc instances → segfault on systems without a host graphviz). +# The sysroot's own interpreter gives a single coherent libc. +# +# LD_PRELOAD=libfakechroot.so + FAKECHROOT_BASE are still active (set by +# exec_in_sysroot.sh) so glibc-level filesystem calls inside dot (e.g. +# opening the graphviz plugin directory, reading config6) are transparently +# redirected into the sysroot. +exec "${SYSROOT_INTERP}" --library-path "${SYSROOT_LIBPATH}" "${SYSROOT_DIR}/usr/bin/dot" "$@" diff --git a/third_party/docs_runtime/tests/BUILD b/third_party/docs_runtime/tests/BUILD new file mode 100644 index 00000000..9138a91d --- /dev/null +++ b/third_party/docs_runtime/tests/BUILD @@ -0,0 +1,29 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +load("@rules_shell//shell:sh_test.bzl", "sh_test") + +# Smoke test: verify that the hermetic exec_in_sysroot graphviz wrapper can +# render a dot graph to SVG without a system graphviz installation. +sh_test( + name = "dot_smoke_test", + srcs = ["dot_smoke_test.sh"], + data = [ + "//third_party/docs_runtime:dot", + ], + # The sysroot is Ubuntu 24.04 amd64; the dot binary is x86_64 ELF. + target_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], +) diff --git a/third_party/docs_runtime/tests/dot_smoke_test.sh b/third_party/docs_runtime/tests/dot_smoke_test.sh new file mode 100755 index 00000000..bdd60d7c --- /dev/null +++ b/third_party/docs_runtime/tests/dot_smoke_test.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# ******************************************************************************* +# 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 +# ******************************************************************************* +# +# Smoke test for the hermetic graphviz setup: +# //third_party/docs_runtime:dot — exec_in_sysroot wrapper executing /usr/bin/dot +# inside the docs_runtime sysroot. +# If this wrapper/sysroot pairing breaks, doc builds lose graphviz rendering. + +set -euo pipefail + +# --- begin runfiles.bash initialization --- +if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ -f "$0.runfiles_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" + elif [[ -f "$0.runfiles/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" + elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + export RUNFILES_DIR="$0.runfiles" + fi +fi +if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash" +elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + source "$(grep -m1 '^bazel_tools/tools/bash/runfiles/runfiles.bash ' "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)" +else + echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash" + exit 1 +fi +# --- end runfiles.bash initialization --- + +dot_wrapper="$(rlocation "${TEST_WORKSPACE}/third_party/docs_runtime/dot")" +if [[ -z "${dot_wrapper}" || ! -x "${dot_wrapper}" ]]; then + echo "ERROR: could not resolve //third_party/docs_runtime:dot from runfiles" >&2 + exit 1 +fi + +# --------------------------------------------------------------------------- +# 1. Sanity: version string. +# --------------------------------------------------------------------------- +echo "=== dot -V ===" +"${dot_wrapper}" -V 2>&1 + +# --------------------------------------------------------------------------- +# 2. Render a minimal digraph to SVG and verify the output contains valid SVG. +# --------------------------------------------------------------------------- +echo "=== rendering digraph to SVG ===" +svg="$(printf 'digraph G { A -> B }' | "${dot_wrapper}" -Tsvg 2>&1)" + +if [[ "${svg}" != *"&2 + echo "--- output ---" >&2 + echo "${svg}" >&2 + exit 1 +fi + +echo "ok: hermetic dot works" diff --git a/third_party/graphviz/graphviz.BUILD b/third_party/graphviz/graphviz.BUILD deleted file mode 100644 index bbc54ccb..00000000 --- a/third_party/graphviz/graphviz.BUILD +++ /dev/null @@ -1,49 +0,0 @@ -# ******************************************************************************* -# 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 -# ******************************************************************************* - -# This BUILD file is injected into the @graphviz_deb external repository by the -# graphviz_deb rule. It exposes dot_builtins and required bundled shared -# libraries for Sphinx graphviz rendering in a hermetic way. - -package(default_visibility = ["//visibility:public"]) - -# The actual graphviz rendering binary (not the dot wrapper/launcher). -# Uses RUNPATH $ORIGIN/../lib to find bundled shared libraries. -filegroup( - name = "dot_binary", - srcs = ["usr/bin/dot_builtins"], -) - -# Bundled graphviz shared libraries (libgvc, libcgraph, libcdt, libpathplan, libxdot). -# These are found automatically by dot_builtins via RUNPATH $ORIGIN/../lib. -filegroup( - name = "core_libs", - srcs = glob(["usr/lib/*.so*"]), -) - -# Graphviz plugin shared libraries (libgvplugin_core, libgvplugin_dot_layout, etc.). -# Loaded at runtime via libltdl; requires LTDL_LIBRARY_PATH=usr/lib/graphviz. -filegroup( - name = "plugin_libs", - srcs = glob(["usr/lib/graphviz/*.so*"]), -) - -# All graphviz files needed to run dot_builtins. -filegroup( - name = "all", - srcs = [ - ":core_libs", - ":dot_binary", - ":plugin_libs", - ], -) diff --git a/third_party/plantuml/BUILD b/third_party/plantuml/BUILD new file mode 100644 index 00000000..f0f61ebd --- /dev/null +++ b/third_party/plantuml/BUILD @@ -0,0 +1,23 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +load("@rules_java//java:defs.bzl", "java_binary") + +java_binary( + name = "plantuml", + main_class = "net.sourceforge.plantuml.Run", + visibility = ["//visibility:public"], + runtime_deps = [ + "@blueprint_maven_dependencies//:net_sourceforge_plantuml_plantuml", + ], +) diff --git a/tools/lobster_rst_report/lobster_rst_report.bzl b/tools/lobster_rst_report/lobster_rst_report.bzl index b9319934..b5d45e7e 100644 --- a/tools/lobster_rst_report/lobster_rst_report.bzl +++ b/tools/lobster_rst_report/lobster_rst_report.bzl @@ -54,11 +54,22 @@ def _lobster_report_rst_impl(ctx): rst_args.add_all(["--out-dir", rst_dir.path]) rst_args.add_all(["--source-root", source_root]) + # Wire in the hermetic graphviz dot wrapper (exec_in_sysroot + docs_runtime) so + # the generated RST emits the `.. graphviz::` policy-diagram directive (the + # tool checks dot availability via GRAPHVIZ_DOT) instead of the host-dependent + # fallback note. + rst_inputs = [lobster_report_json] + rst_env = { + "GRAPHVIZ_DOT": ctx.executable._graphviz.path, + } + ctx.actions.run( executable = ctx.executable._lobster_rst_report, - inputs = [lobster_report_json], + inputs = rst_inputs, outputs = [rst_dir], arguments = [rst_args], + env = rst_env, + tools = [ctx.attr._graphviz.files_to_run], progress_message = "lobster-rst-report (pages) %s" % ctx.label.name, ) @@ -87,5 +98,10 @@ lobster_report_rst = rule( executable = True, cfg = "exec", ), + "_graphviz": attr.label( + default = Label("//third_party/docs_runtime:dot"), + executable = True, + cfg = "exec", + ), }, ) diff --git a/tools/lobster_rst_report/rst_report.py b/tools/lobster_rst_report/rst_report.py index 309c3ae3..578f9277 100644 --- a/tools/lobster_rst_report/rst_report.py +++ b/tools/lobster_rst_report/rst_report.py @@ -362,7 +362,10 @@ def _run_impl(self, options: argparse.Namespace) -> int: err.dump() return 1 - if not is_dot_available(): + # Honor GRAPHVIZ_DOT (set by the Bazel rule to the hermetic dot) the same + # way PolicyDiagramBuilder does, so the warning reflects what is actually + # emitted rather than only checking for a system `dot` on PATH. + if not is_dot_available(os.environ.get("GRAPHVIZ_DOT") or None): print( "warning: dot utility not found, report will not include " "the tracing policy visualisation" diff --git a/tools/sphinx/BUILD b/tools/sphinx/BUILD index d1a64d03..86f4b637 100644 --- a/tools/sphinx/BUILD +++ b/tools/sphinx/BUILD @@ -12,18 +12,8 @@ # ******************************************************************************* load("@pip_rules_score//:requirements.bzl", "requirement") -load("@rules_java//java:defs.bzl", "java_binary") load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_build_binary") -java_binary( - name = "plantuml", - main_class = "net.sourceforge.plantuml.Run", - visibility = ["//visibility:public"], - runtime_deps = [ - "@blueprint_maven_dependencies//:net_sourceforge_plantuml_plantuml", - ], -) - sphinx_build_binary( name = "sphinx-build", visibility = ["//visibility:public"], @@ -39,6 +29,5 @@ sphinx_build_binary( requirement("sphinxcontrib-umlet"), requirement("svglib"), requirement("sphinxcontrib-plantuml"), - "@rules_python//python/runfiles", ], )