diff --git a/MODULE.bazel b/MODULE.bazel index afa42287..df843b21 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -34,11 +34,6 @@ bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") bazel_dep(name = "flatbuffers", version = "25.9.23") 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", -) # flatbuffers depends on this transitively, but older grpc-java version # The main problem is that there the command `bazel mod deps` is broken, which @@ -260,17 +255,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/bazel/rules/exec_in_sysroot/BUILD b/bazel/rules/exec_in_sysroot/BUILD new file mode 100644 index 00000000..5562bf60 --- /dev/null +++ b/bazel/rules/exec_in_sysroot/BUILD @@ -0,0 +1,31 @@ +# ******************************************************************************* +# 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_binary.bzl", "sh_binary") + +package(default_visibility = ["//visibility:public"]) + +sh_binary( + name = "exec_in_sysroot", + srcs = ["exec_in_sysroot.sh"], +) + +# Referenced as implicit deps by the exec_in_sysroot / prepare_sysroot rules: +# exec_in_sysroot.sh - shared sysroot launcher (also reused by +# prepare_sysroot for sysroot_setup_commands). +# exec_in_sysroot_launcher.sh.tpl - template for the generated runfiles-aware +# launcher emitted by exec_in_sysroot. +exports_files([ + "exec_in_sysroot.sh", + "exec_in_sysroot_launcher.sh.tpl", +]) 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..b7f202fe --- /dev/null +++ b/bazel/rules/exec_in_sysroot/exec_in_sysroot.bzl @@ -0,0 +1,244 @@ +# ******************************************************************************* +# 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, exec_wrapper, 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. + Use them for pure filesystem edits that only need the host shell. + + sysroot_setup_commands are each executed inside the sysroot by delegating to + the shared `exec_in_sysroot.sh` launcher (`exec_wrapper`) with SYSROOT_DIR + set to sysroot_dir. This is the *same* launcher exec_in_sysroot uses at + runtime, so build-time setup and runtime execution share a single + implementation of "run a binary inside the sysroot" (the sysroot's own + ld-linux.so + --library-path + LD_PRELOAD=libfakechroot.so + FAKECHROOT_BASE, + giving a fully consistent single-libc environment). + + Each entry in sysroot_setup_commands must be a space-separated ELF binary + invocation starting with an absolute sysroot path, e.g. "/usr/bin/tool --flag". + 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" + for cmd in sysroot_setup_commands: + # Delegate to the shared launcher. `cmd` is intentionally left unquoted + # so the shell word-splits " " into the launcher's + # positional parameters (the space-separated contract documented above). + block += ( + "SYSROOT_DIR=\"" + sysroot_dir + "\" " + + "sh \"" + exec_wrapper + "\" " + cmd + "\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") + tar_bin = bsdtar.tarinfo.binary.path + + command = ( + "set -eu\n" + + "work=\"$(mktemp -d)\"\n" + + "trap 'rm -rf \"$work\"' EXIT\n" + + _extract_and_clean(tar_bin, sysroot_archive.path, "$work") + + _setup_block("$work", ctx.file._exec_wrapper.path, ctx.attr.host_setup_commands, ctx.attr.sysroot_setup_commands) + + "\"" + tar_bin + "\" -cf \"" + out_archive.path + "\" -C \"$work\" .\n" + ) + + ctx.actions.run_shell( + inputs = [sysroot_archive, ctx.file._exec_wrapper], + 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 files or creating symlinks with standard " + + "shell tools such as find/rm/ln).", + ), + "sysroot_setup_commands": attr.string_list( + default = [], + doc = "ELF binary invocations run inside the sysroot after " + + "host_setup_commands complete. Each entry is word-split by the " + + "shell to separate the binary path from its arguments; entries must " + + "not contain arguments with embedded spaces, and shell metacharacters " + + "(pipes, redirects) are not supported. The binary path must be " + + "absolute within the sysroot (e.g. '/usr/bin/tool --flag'). 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 absolute filesystem calls are transparently " + + "redirected into the sysroot. Requires fakechroot and " + + "ld-linux.so to be present in the sysroot. NOTE: sysroot binaries are " + + "executed via the sysroot's ELF interpreter on the host kernel, so the " + + "sysroot architecture must match the host architecture.", + ), + "_exec_wrapper": attr.label( + default = Label("//bazel/rules/exec_in_sysroot:exec_in_sysroot.sh"), + allow_single_file = True, + doc = "Shared sysroot launcher reused to run sysroot_setup_commands, " + + "so build-time setup and runtime execution use one implementation.", + ), + }, + 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 + + 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 "" + + ctx.actions.expand_template( + template = ctx.file._launcher_template, + output = out, + is_executable = True, + substitutions = { + "{{WRAPPER_SHORT_PATH}}": ctx.workspace_name + "/" + ctx.executable._fakechroot_wrapper.short_path, + "{{SYSROOT_SHORT_PATH}}": sysroot_runfiles_path, + "{{SYSROOT_BINARY}}": ctx.attr.sysroot_binary, + "{{EXCLUDE_PATHS}}": exclude_paths, + }, + ) + + runfiles = ctx.runfiles( + files = [ctx.executable._fakechroot_wrapper, sysroot], + ) + runfiles = _merge_default_and_data_runfiles(ctx.attr._fakechroot_wrapper, 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 = { + "sysroot_binary": attr.string( + mandatory = True, + doc = "Sysroot-relative path of the ELF binary to execute, e.g. '/usr/bin/tool'.", + ), + "sysroot": attr.label( + mandatory = True, + allow_single_file = True, + doc = "Prepared sysroot archive, typically the output of a prepare_sysroot rule.", + ), + "exclude_paths": attr.string_list( + default = [], + doc = "List of paths to exclude from fakechroot path-redirection.", + ), + "_fakechroot_wrapper": attr.label( + default = Label("//bazel/rules/exec_in_sysroot"), + executable = True, + cfg = "exec", + doc = "Shared sysroot launcher (exec_in_sysroot.sh) invoked at runtime.", + ), + "_launcher_template": attr.label( + default = Label("//bazel/rules/exec_in_sysroot:exec_in_sysroot_launcher.sh.tpl"), + allow_single_file = True, + ), + }, + toolchains = [_TAR_TOOLCHAIN_TYPE], + doc = """ + Produces an executable wrapper that runs a sysroot ELF binary via the + sysroot's own ld-linux.so (SYSROOT_INTERP) and fakechroot (LD_PRELOAD), + giving the binary a hermetic single-libc environment backed by the sysroot. + + The sysroot archive is expected to be prepared by a prepare_sysroot rule, + which performs filesystem preparation / 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..1adbe886 --- /dev/null +++ b/bazel/rules/exec_in_sysroot/exec_in_sysroot.sh @@ -0,0 +1,138 @@ +#!/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 inside a sysroot using fakechroot. +# +# Rather than using `fakechroot -- chroot SYSROOT COMMAND` (which relies on the +# host's dynamic linker and would cause the host libc and the sysroot libc to be +# loaded simultaneously, resulting in a segfault), this script directly invokes the +# sysroot's own ELF interpreter (ld-linux.so) with --library-path pointing at the +# sysroot's library tree. This guarantees a single-libc environment: all shared +# libraries, including libc, come exclusively from the sysroot. fakechroot is still +# loaded via LD_PRELOAD so that absolute file-system calls are transparently +# redirected into the sysroot, but the chroot simulation itself is bypassed entirely. +# +# WHY NOT `fakechroot -- chroot SYSROOT $SYSROOT_INTERP --library-path ...`? +# +# One might try to preserve the fakechroot binary invocation (in case it does more +# than just set LD_PRELOAD) while still using the sysroot's own ld-linux.so. This +# does not work cleanly for the following reason: +# +# ld-linux.so.2 is the dynamic linker itself. When invoked as a standalone +# executable it bootstraps before glibc exists and performs its own file I/O +# via raw kernel syscalls, NOT via glibc wrappers. Because fakechroot works +# exclusively through LD_PRELOAD (hooking glibc wrappers), it cannot intercept +# ld-linux.so.2's internal library lookups. The --library-path argument passed +# to ld-linux.so.2 is therefore resolved against the real host filesystem. If +# relative (chroot-relative) paths are used, ld-linux.so.2 loads host libraries +# → dual libc → segfault. If absolute sysroot paths are used instead, fakechroot +# would double-translate them (prepend SYSROOT_DIR a second time) unless every +# such path is individually listed in FAKECHROOT_EXCLUDE_PATH. +# +# The fakechroot binary itself adds little beyond setting LD_PRELOAD and +# FAKECHROOT_BASE plus substituting package-management commands (ldconfig, chown, +# etc.) that are irrelevant when running a plain ELF binary. Setting those two +# environment variables directly (as this script does) is fully equivalent for +# this use case. +# +# 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 (optional) +# BUILD_WORKSPACE_DIRECTORY - Workspace root, automatically excluded (optional) + +set -eu + +COMMAND_IN_SYSROOT="${1:?Command must be provided as first argument}" +shift + +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 + echo "ERROR: libfakechroot.so not found in sysroot (tried x86_64 and aarch64 multiarch paths)" >&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. The sysroot binary is always executed via SYSROOT_INTERP +# so all of its dependencies (including libc.so.6) come from the sysroot — +# avoiding loading the sysroot's libc alongside the host's (two libc instances +# → segfault). +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 + +# 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}}" +# Add the fakechroot library directory to LD_LIBRARY_PATH so fakechroot's own +# shared dependencies are resolved without falling back to host libraries. +export LD_LIBRARY_PATH="${FAKECHROOT_LIB_DIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" + +exec "${SYSROOT_INTERP}" --library-path "${SYSROOT_LIBPATH}" "${SYSROOT_DIR}${COMMAND_IN_SYSROOT}" "$@" diff --git a/bazel/rules/exec_in_sysroot/exec_in_sysroot_launcher.sh.tpl b/bazel/rules/exec_in_sysroot/exec_in_sysroot_launcher.sh.tpl new file mode 100755 index 00000000..c526c625 --- /dev/null +++ b/bazel/rules/exec_in_sysroot/exec_in_sysroot_launcher.sh.tpl @@ -0,0 +1,86 @@ +#!/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 +# ******************************************************************************* +# +# Runfiles-aware launcher generated by the exec_in_sysroot rule. +# +# It resolves the extracted sysroot TreeArtifact and the shared +# exec_in_sysroot.sh wrapper from its own runfiles (via the $0.runfiles/ +# convention, independent of any RUNFILES_DIR the parent process may carry), +# then execs the wrapper with the sysroot-relative binary path. +# +# The following tokens are substituted at analysis time by ctx.actions.expand_template: +# {{WRAPPER_SHORT_PATH}} - rlocation key of exec_in_sysroot.sh (_fakechroot_wrapper) +# {{SYSROOT_SHORT_PATH}} - rlocation key of the extracted sysroot TreeArtifact +# {{SYSROOT_BINARY}} - sysroot-absolute path of the ELF binary to run +# {{EXCLUDE_PATHS}} - colon-separated FAKECHROOT_EXCLUDE_PATH entries (may be empty) +set -eu + +# --- begin POSIX sh runfiles init --- +# Prefer $0.runfiles/ (the companion directory Bazel creates for this specific +# binary) over any inherited RUNFILES_DIR from the parent process. When this +# launcher is invoked as a subprocess from another tool (e.g. Sphinx via +# sphinx.ext.graphviz or PlantUML's -graphvizdot), the parent process carries +# its own RUNFILES_DIR pointing at a different binary's runfiles tree. The +# standard bootstrap guard (if [ ! -d "${RUNFILES_DIR:-/dev/null}" ]) would +# accept the inherited value and cause rlocation to search the wrong tree, +# failing to find the sysroot. +# +# By probing $0.runfiles/ first we always use this binary's own companion +# runfiles directory when it exists (Bazel creates it whenever this binary is +# used as an action tool or via bazel run). We fall through to the inherited +# RUNFILES_DIR/RUNFILES_MANIFEST_FILE only when $0.runfiles/ does not exist, +# which happens when this binary is accessed via another target's data dep (e.g. +# the smoke test resolves it via rlocation and the path is inside the test's +# runfiles tree, where no companion .runfiles/ is created). +if [ -d "$0.runfiles" ]; then + RUNFILES_DIR="$0.runfiles"; export RUNFILES_DIR + unset RUNFILES_MANIFEST_FILE 2>/dev/null || true +elif [ -f "$0.runfiles_manifest" ]; then + RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"; export RUNFILES_MANIFEST_FILE + unset RUNFILES_DIR 2>/dev/null || true +elif [ -f "$0.runfiles/MANIFEST" ]; then + RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"; export RUNFILES_MANIFEST_FILE + unset RUNFILES_DIR 2>/dev/null || true +elif [ ! -d "${RUNFILES_DIR:-/dev/null}" ] && [ ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]; then + echo >&2 "ERROR: cannot find runfiles for $0" + exit 1 +fi +rlocation() { + if [ -n "${RUNFILES_DIR:-}" ] && [ -e "${RUNFILES_DIR}/$1" ]; then + printf '%s' "${RUNFILES_DIR}/$1" + elif [ -n "${RUNFILES_MANIFEST_FILE:-}" ]; then + grep -m1 "^$1 " "${RUNFILES_MANIFEST_FILE}" | cut -d' ' -f2- + fi +} +# --- end POSIX sh runfiles init --- + +FAKECHROOT_WRAPPER="$(rlocation '{{WRAPPER_SHORT_PATH}}')" +SYSROOT_DIR="$(rlocation '{{SYSROOT_SHORT_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 + +export SYSROOT_DIR +if [ -n "{{EXCLUDE_PATHS}}" ]; then + export FAKECHROOT_EXCLUDE_PATH="{{EXCLUDE_PATHS}}" +fi + +exec "${FAKECHROOT_WRAPPER}" "{{SYSROOT_BINARY}}" "$@" diff --git a/bazel/rules/rules_score/BUILD b/bazel/rules/rules_score/BUILD index 970b4a7b..c67c552d 100644 --- a/bazel/rules/rules_score/BUILD +++ b/bazel/rules/rules_score/BUILD @@ -189,13 +189,6 @@ sphinx_module( py_binary( name = "raw_build", srcs = ["src/sphinx_wrapper.py"], - data = [ - "//tools/sphinx:plantuml", - # Ship the FTA metamodel in the docs-build runfiles so conf.py can put it - # on PlantUML's include path; FTA diagrams keep ``!include - # fta_metamodel.puml`` and resolve it even under -pipe rendering. - "//plantuml:fta_metamodel", - ], env = { "SOURCE_DIRECTORY": "", "DATA": "", @@ -206,7 +199,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..ddee0901 100644 --- a/bazel/rules/rules_score/docs/integration_guide.rst +++ b/bazel/rules/rules_score/docs/integration_guide.rst @@ -175,3 +175,115 @@ 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, it would break if used as-is. +``conf.template.py`` converts it to a stable absolute path with a single +``os.path.abspath()`` call at **module import time**, when Bazel guarantees the +action's cwd still equals the execroot (before Sphinx performs any +``os.chdir()``). See :doc:`tooling_architecture` §"Hermetic tool path +resolution" for the full rationale. + +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 +``//third_party/plantuml:plantuml`` in ``third_party/plantuml/BUILD``. + +The ``sphinx_module`` rule passes the target as an action **tool** +(``attr.label(executable = True, cfg = "exec")``), exactly like the hermetic +graphviz dot. It is not a runfile of the sphinx-build binary. + +**Wiring into the Sphinx action** + +The Bazel rule sets one variable (mirroring ``GRAPHVIZ_DOT``): + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Env var + - Content + * - ``PLANTUML_BIN`` + - Execroot-relative path to the ``plantuml`` ``java_binary`` launcher + +``PLANTUML_BIN_RLOC`` (the ``short_path`` rlocation key) is also set, but is +used only for diagnostic logging. + +**Resolving the path in conf.py** + +``PLANTUML_BIN`` is an *execroot-relative* path. As with ``GRAPHVIZ_DOT``, +``conf.template.py`` converts it to an absolute path with a single +``os.path.abspath()`` call — Bazel guarantees the action's cwd equals the +execroot when ``conf.py`` is imported, before Sphinx performs any +``os.chdir()``. + +**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 the +graphviz version is identical for both ``sphinx.ext.graphviz`` directives and +PlantUML diagrams. There is no Smetana fallback: the hermetic dot is the +single rendering path. diff --git a/bazel/rules/rules_score/docs/tooling_architecture.rst b/bazel/rules/rules_score/docs/tooling_architecture.rst index a5ce61e1..d1eb04df 100644 --- a/bazel/rules/rules_score/docs/tooling_architecture.rst +++ b/bazel/rules/rules_score/docs/tooling_architecture.rst @@ -334,3 +334,146 @@ The ``fmea`` rule drives three actions, all reading the input artifacts above: The lobster outputs travel separately on ``AnalysisInfo.lobster_files`` (``failuremodes.lobster``, ``controlmeasures.lobster``, ``root_causes.lobster``) into the ``dependability_analysis`` traceability report. + 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 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`` and ``FAKECHROOT_WRAPPER`` + 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`` is a POSIX-``sh`` script +that contains an inline runfiles initialisation block (no ``bash`` and no +``runfiles.bash`` dependency): + +.. code-block:: sh + + if [ ! -d "${RUNFILES_DIR:-/dev/null}" ] && \ + [ ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]; then + if [ -f "$0.runfiles_manifest" ]; then + RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"; export RUNFILES_MANIFEST_FILE + elif [ -f "$0.runfiles/MANIFEST" ]; then + RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"; export RUNFILES_MANIFEST_FILE + elif [ -d "$0.runfiles" ]; then + RUNFILES_DIR="$0.runfiles"; export RUNFILES_DIR + 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 resolves +``RUNFILES_DIR`` (or ``RUNFILES_MANIFEST_FILE``) from ``$0.runfiles/``, and a +small inline ``rlocation`` helper then looks up ``SYSROOT_DIR`` and the +fakechroot wrapper — regardless of what ``RUNFILES_DIR`` the parent process +(Sphinx/Python) has set in its environment. + +PlantUML and the hermetic dot +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``sphinxcontrib.plantuml`` invokes PlantUML via +``shlex.split(app.config.plantuml)``. The ``plantuml`` config value always +points at the hermetic dot (there is no fallback): + +.. 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..345ac9ab 100644 --- a/bazel/rules/rules_score/private/sphinx_module.bzl +++ b/bazel/rules/rules_score/private/sphinx_module.bzl @@ -69,6 +69,22 @@ 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", + ), + "_fta_metamodel": attr.label( + default = Label("//plantuml:fta_metamodel"), + allow_files = True, + doc = "Directory containing fta_metamodel.puml, passed to PlantUML via " + + "-Dplantuml.include.path so FTA diagrams can resolve !include fta_metamodel.puml.", + ), }, **VERBOSITY_ATTR ) @@ -106,14 +122,34 @@ 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 + fta_metamodel_files = ctx.files._fta_metamodel + fta_metamodel_dir = fta_metamodel_files[0].dirname if fta_metamodel_files else "" ctx.actions.run( - inputs = needs_inputs, + inputs = needs_inputs + fta_metamodel_files, 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, + "FTA_METAMODEL_DIR": fta_metamodel_dir, + }, 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 +274,36 @@ 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 + fta_metamodel_files = ctx.files._fta_metamodel + fta_metamodel_dir = fta_metamodel_files[0].dirname if fta_metamodel_files else "" + action_env = { + "PLANTUML_BIN": ctx.executable._plantuml.path, + "PLANTUML_BIN_RLOC": _plantuml_rloc, + "GRAPHVIZ_DOT": ctx.executable._graphviz.path, + "GRAPHVIZ_DOT_RLOC": _graphviz_rloc, + "FTA_METAMODEL_DIR": fta_metamodel_dir, + } ctx.actions.run( - inputs = html_inputs, + inputs = html_inputs + fta_metamodel_files, 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 +391,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 +409,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 +433,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 +453,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/src/sphinx_wrapper.py b/bazel/rules/rules_score/src/sphinx_wrapper.py index 760c7c39..5cefacb6 100644 --- a/bazel/rules/rules_score/src/sphinx_wrapper.py +++ b/bazel/rules/rules_score/src/sphinx_wrapper.py @@ -289,6 +289,16 @@ def main() -> int: level=_LEVEL_MAP[args.log_level], format="%(levelname)s: %(message)s" ) validate_arguments(args) + # Resolve execroot-relative tool paths to absolute paths NOW, while cwd + # is still the execroot (Bazel guarantees cwd = execroot at action start). + # Sphinx changes its working directory to the source/staging directory + # before evaluating conf.py, so os.path.abspath() inside conf.py would + # resolve against the wrong base. Converting here is safe and means + # conf.py receives already-absolute values via the environment. + for _tool_var in ("GRAPHVIZ_DOT", "PLANTUML_BIN", "FTA_METAMODEL_DIR"): + _tool_path = os.environ.get(_tool_var) + if _tool_path and not os.path.isabs(_tool_path): + os.environ[_tool_var] = os.path.abspath(_tool_path) # Create processor instance stdout_processor = StdoutProcessor() stderr_processor = StderrProcessor() diff --git a/bazel/rules/rules_score/templates/conf.template.py b/bazel/rules/rules_score/templates/conf.template.py index a694e406..998e5509 100644 --- a/bazel/rules/rules_score/templates/conf.template.py +++ b/bazel/rules/rules_score/templates/conf.template.py @@ -21,59 +21,12 @@ import json 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,103 +122,106 @@ 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}." - ) - -# Locate the FTA metamodel in the docs-build runfiles (shipped via -# //tools/sphinx:sphinx-build data) so it can be put on PlantUML's include path. -# FTA diagrams keep their ``!include fta_metamodel.puml``; sphinxcontrib-plantuml -# renders via ``-pipe`` (no source-file dir), so a relative include only resolves -# through ``plantuml.include.path``. -fta_metamodel_dir = "" -for repo_name in plantuml_repo_candidates: - candidate = r.Rlocation(f"{repo_name}/plantuml/fta_metamodel.puml", source_repo="") - if candidate and Path(candidate).exists(): - fta_metamodel_dir = str(Path(candidate).parent) - logger.info(f"Found fta_metamodel.puml on include path: {candidate}") - break - -if not fta_metamodel_dir: - logger.warning( - "fta_metamodel.puml not found in runfiles — FTA diagrams using " - "!include fta_metamodel.puml will fail to render. " - f"Searched repo candidates: {plantuml_repo_candidates}" + "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." ) - -# Use PlantUML's built-in Smetana layout engine (Java port of Graphviz). -# This avoids requiring an external dot binary in the Bazel sandbox. -# ``--jvm_flag`` is consumed by the java_binary launcher and sets the JVM system -# property PlantUML reads for its include search path. It must precede the -# program args (``-Playout`` etc.), or the launcher forwards it to PlantUML -# (which ignores it) instead of the JVM. -_include_flag = ( - f" --jvm_flag=-Dplantuml.include.path={fta_metamodel_dir}" - if fta_metamodel_dir - else "" +plantuml_path = os.path.abspath(_plantuml_bin) +logger.debug( + f"plantuml resolved: {plantuml_path} " + f"(rloc: {os.environ.get('PLANTUML_BIN_RLOC', 'n/a')})" ) -plantuml = f"{plantuml_path}{_include_flag} -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_output_format = "svg" +# GRAPHVIZ_DOT — execroot-relative path of //third_party/docs_runtime:dot +# (the exec_in_sysroot POSIX-sh 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 POSIX-sh script that +# bootstraps its own runfiles via the standard $0.runfiles/ Bazel convention. +# 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). +# +# GRAPHVIZ_DOT is mandatory: the sphinx_module rule always provides the hermetic +# wrapper, so if it is missing conf.py fails loudly rather than silently using a +# host-installed dot binary. +_graphviz_dot_path = os.environ.get("GRAPHVIZ_DOT") +if not _graphviz_dot_path: + raise ValueError( + "GRAPHVIZ_DOT environment variable is not set. It must point at the " + "//third_party/docs_runtime:dot hermetic wrapper and is normally provided " + "by the sphinx_module Bazel rule. If you are invoking Sphinx outside that " + "rule, set GRAPHVIZ_DOT to the hermetic dot wrapper path." + ) +_graphviz_dot_rloc = os.environ.get("GRAPHVIZ_DOT_RLOC", "") +graphviz_dot = os.path.abspath(_graphviz_dot_path) +graphviz_output_format = "svg" +logger.debug( + f"graphviz dot resolved: {graphviz_dot} (rloc: {_graphviz_dot_rloc or 'n/a'})" +) - # 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(":") - ) +# --------------------------------------------------------------------------- +# PlantUML layout engine: hermetic dot + FTA metamodel include path +# --------------------------------------------------------------------------- +# FTA_METAMODEL_DIR — directory containing fta_metamodel.puml, set by the +# sphinx_module rule from //plantuml:fta_metamodel. +# FTA diagrams keep ``!include fta_metamodel.puml``; +# sphinxcontrib-plantuml renders via -pipe (no source-file +# dir on the include search path) so the file must be +# listed on plantuml.include.path. The JVM flag must +# precede the program args or the java_binary launcher +# passes it to PlantUML instead of the JVM. +_fta_metamodel_dir = os.environ.get("FTA_METAMODEL_DIR", "") +if _fta_metamodel_dir: + _fta_metamodel_dir = os.path.abspath(_fta_metamodel_dir) + logger.debug(f"fta_metamodel include path: {_fta_metamodel_dir}") + _include_flag = f" --jvm_flag=-Dplantuml.include.path={_fta_metamodel_dir}" else: - graphviz_dot = "/__hermetic_graphviz_not_configured__/dot" + logger.warning( + "FTA_METAMODEL_DIR is not set; FTA diagrams using " + "!include fta_metamodel.puml will fail to render." + ) + _include_flag = "" + +# PlantUML uses the same hermetic Graphviz dot as sphinx.ext.graphviz for its +# internal layout calls, via the -graphvizdot flag. There is no fallback: the +# hermetic dot is the single reference rendering path for both. +plantuml = f"{plantuml_path}{_include_flag} -graphvizdot {graphviz_dot}" # 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 040ca638..21c70974 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..4b936137 --- /dev/null +++ b/third_party/docs_runtime/BUILD @@ -0,0 +1,72 @@ +# ******************************************************************************* +# 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("//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", +]) + +# 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", + 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"], + exec_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], + sysroot = ":dot_sysroot", + sysroot_binary = "/usr/bin/dot", +) diff --git a/third_party/docs_runtime/README.md b/third_party/docs_runtime/README.md new file mode 100644 index 00000000..2949fa6c --- /dev/null +++ b/third_party/docs_runtime/README.md @@ -0,0 +1,64 @@ + + +# 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 `exec_in_sysroot.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 kernel ABI.** `exec_in_sysroot` runs `dot` through the sysroot's own + ELF interpreter (`ld-linux.so`) with `LD_PRELOAD=libfakechroot.so` rather + than a real chroot. All shared libraries — including `libc.so.6` — are loaded + from the sysroot, so the *host* glibc version does not matter. The one host + dependency that remains is the Linux kernel: the sysroot's glibc (Ubuntu + 24.04, glibc 2.39) requires a kernel at least as new as its minimum supported + version, so build hosts need a sufficiently recent kernel. +2. **Host shell tools.** The sysroot-rework and extraction actions run under a + POSIX `sh` and use standard coreutils (`mkdir`, `find`, `rm`) plus `tar`, + assumed present in the build environment. The generated `dot` launcher and + the smoke test are plain POSIX `sh` scripts with an inline runfiles lookup + +## 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/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..a37785db --- /dev/null +++ b/third_party/docs_runtime/tests/dot_smoke_test.sh @@ -0,0 +1,73 @@ +#!/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 +# ******************************************************************************* +# +# 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 -eu + +# --- begin POSIX sh runfiles init --- +if [ ! -d "${RUNFILES_DIR:-/dev/null}" ] && [ ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]; then + if [ -f "$0.runfiles_manifest" ]; then + RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"; export RUNFILES_MANIFEST_FILE + elif [ -f "$0.runfiles/MANIFEST" ]; then + RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"; export RUNFILES_MANIFEST_FILE + elif [ -d "$0.runfiles" ]; then + RUNFILES_DIR="$0.runfiles"; export RUNFILES_DIR + else + echo >&2 "ERROR: cannot find runfiles" + exit 1 + fi +fi +rlocation() { + if [ -n "${RUNFILES_DIR:-}" ] && [ -e "${RUNFILES_DIR}/$1" ]; then + printf '%s' "${RUNFILES_DIR}/$1" + elif [ -n "${RUNFILES_MANIFEST_FILE:-}" ]; then + grep -m1 "^$1 " "${RUNFILES_MANIFEST_FILE}" | cut -d' ' -f2- + fi +} +# --- end POSIX sh runfiles init --- + +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)" + +case "${svg}" in + *"&2 + echo "--- output ---" >&2 + echo "${svg}" >&2 + exit 1 + ;; +esac + +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/graphviz/BUILD b/third_party/plantuml/BUILD similarity index 59% rename from third_party/graphviz/BUILD rename to third_party/plantuml/BUILD index e9f4f198..f0f61ebd 100644 --- a/third_party/graphviz/BUILD +++ b/third_party/plantuml/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,13 @@ # 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_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", ], )