Files
darling-lol/tools/llvm_tools.py
T
MintMachineHQ fa18b2d04f feat(setup): check host versions before the build and the runtime install
setup failures reported in #137, #138 and #143 happened only after the long
fetch/build or at Riot startup. scripts/preflight.py now runs from
scripts/build.sh (before configure.py, so an old interpreter gets this message
and not a traceback) and from scripts/install-runtime.sh, and checks the host
once:

- python 3.10+, cmake 3.20+ (the bundled indium CMakeLists requires it),
  git-lfs 2.13+, kernel 5.14+ and LLVM 16+ exit nonzero with one actionable
  line each. RCLOL_SKIP_PREFLIGHT=1 bypasses the gate.
- llvm-objdump and llvm-nm are required, and unresolved names are explained.
- Vulkan 1.3 and timeline semaphores are required when vulkaninfo is
  installed; they only warn when it is not, because a missing tool does not
  mean a missing driver.
- the X display is probed with xdpyinfo in the environment Darling inherits.
  It only warns: a build does not need a display.
- a prepared build/darling-src is scanned for small .dylib/.a/.so/.o files
  that still contain Git LFS pointer text, which links but crashes the guest.
- --json prints the same report for the diagnostic tools.

The installers no longer call hashlib.file_digest (Python 3.11+), so Ubuntu
22.04's Python 3.10 works instead of failing after the overlays are built
(#137). A streaming tools/file_digest.py replaces it.

LLVM command line tools are resolved by tools/llvm_tools.py: an unversioned
name on PATH first, then the highest llvm-<tool>-<N>, then llvm-config
--bindir. Debian/Ubuntu install llvm-objdump and llvm-nm only under
/usr/lib/llvm-<N>/bin, which broke the runtime validation (#138). Six tools
now use it. The LLVM 16 floor also stops the LLVM 15 runtime abort from #139
before anything is built.

Two test helpers used APIs added after Python 3.10; they are shimmed so the
same suite runs on 3.10, 3.11 and 3.14.

Adds tests/test_preflight.py (24 tests, fake host tools only).
2026-10-25 13:58:25 -06:00

94 lines
3.3 KiB
Python

"""Find LLVM command line tools, including versioned Debian/Ubuntu names.
Ubuntu's llvm-<N>-dev installs llvm-objdump and llvm-nm in /usr/lib/llvm-<N>/bin
without an unversioned name in PATH, so a bare ['llvm-objdump'] run fails on an
otherwise complete host (issue #138). Resolution order: an unversioned tool on
PATH, then the highest versioned name on PATH, then the directory llvm-config
reports. Nothing here runs a guest process.
"""
import os
import subprocess
def _collect(directory, name, found):
"""Add every `name` and `name-<digits>` executable in the directory to found."""
try:
entries = os.listdir(directory)
except OSError:
return
for entry in entries:
suffix = entry[len(name) + 1:]
if entry == name:
version = 0 # An unversioned name always wins.
elif entry.startswith(name + '-') and suffix.isdigit():
version = int(suffix)
else:
continue
path = os.path.join(directory or '.', entry)
if os.path.isfile(path) and os.access(path, os.X_OK):
found.setdefault(version, path)
def _executables(name):
"""{version: path} for `name` and for every `name-<digits>` on PATH."""
found = {}
for folder in os.environ.get('PATH', '').split(os.pathsep):
_collect(folder or '.', name, found)
return found
def _preferred(candidates):
return candidates[0 if 0 in candidates else max(candidates)] if candidates else None
def _bindir():
"""`llvm-config --bindir` from the preferred llvm-config, or None."""
configuration = _preferred(_executables('llvm-config'))
if not configuration:
return None
result = subprocess.run([configuration, '--bindir'],
capture_output=True, text=True, errors='replace')
directory = result.stdout.strip()
if result.returncode or not os.path.isdir(directory):
return None
return directory
def find(name):
"""Path of an LLVM tool, or None when this host does not have it."""
candidates = _executables(name)
if candidates:
return _preferred(candidates)
directory = _bindir()
if directory:
in_bindir = {}
_collect(directory, name, in_bindir)
if in_bindir:
return _preferred(in_bindir)
return None
def require(name):
"""Path of an LLVM tool, or exit with what was tried and what to install."""
path = find(name)
if path:
return path
raise SystemExit(f'{name} not found: looked for an unversioned {name}, a versioned '
f'{name}-<N> in PATH and `llvm-config --bindir`. Install the LLVM tools '
f'package (for example llvm-dev, llvm15-tools or llvm23-tools).')
def config(*arguments):
"""Run llvm-config with the arguments, or exit with an actionable message."""
path = require('llvm-config')
result = subprocess.run([path, *arguments], capture_output=True, text=True, errors='replace')
if result.returncode:
raise SystemExit(f'{path} {" ".join(arguments)} failed: {result.stderr.strip()}')
return result.stdout.strip()
def major():
"""LLVM major version reported by llvm-config, or None when unparsable."""
head = config('--version').strip().split('.', 1)[0]
return int(head) if head.isdigit() else None