okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.petshims: find nix for jobs when the daemon started with a thin PATH32975a8

main
5.6 KiB131 linesraw
1"""Programs the kernel puts first on PATH for the model's jobs, each in its
2own directory under $ALBEDO_HOME/shims, prepended only where the program it
3needs is on the job's PATH. They are written where the kernel runs (only .py
4files travel to a remote kernel). robot-docs/shims.md has the measurements.
5
6`nix` (needs nix) runs albedo_nix.py with the kernel's Python: a flake in a jj
7workspace is read from its commit instead of copied into the store, and
8`nix develop -c` reuses a cached dev shell. ALBEDO_PLAIN_NIX=1 runs nix as is.
9
10`cargo` (needs mbx, jdx's mr-boxington) runs cargo through mbx, so every
11checkout of a project shares compiled work: a second checkout of
12native/render builds in 2 s instead of 32 s. ALBEDO_NO_MBX=1 runs plain cargo.
13For mbx's process only, SDKROOT goes, because mbx names the macOS linker with
14`xcrun --sdk $SDKROOT`, which rejects a path such as a nix shell's, and an
15unnamed linker leaves every link, build script, and proc macro uncached.
16Inside a nix shell (ALBEDO_NIX_ENV, set by albedo_nix.py) CC and CXX go too
17when they are the shell's cc and c++ anyway (a shell that picks another
18compiler keeps it), so build scripts' C compiles through mbx, which an
19explicit CC hides it from, and HOST_CFLAGS names the shell's
20compiler-wrapper flags, which no mbx key covers: without it a changed shell
21could be served another checkout's stale object.
22"""
23
24from __future__ import annotations
25
26import os
27import shutil
28import sys
29from pathlib import Path
30
31CARGO = r"""#!/bin/sh
32# albedo's cargo: mbx when it is installed (see albedo_shims.py), else the next
33# cargo on PATH.
34here=$(cd "$(dirname "$0")" && pwd)
35# whether compiler $1 is the $2 on PATH, which a build script runs without it
36default() {
37  a=$(command -v "$1" 2>/dev/null) && b=$(command -v "$2" 2>/dev/null) || return 1
38  a=$(realpath "$a" 2>/dev/null) && b=$(realpath "$b" 2>/dev/null) || return 1
39  [ "$a" = "$b" ]
40}
41if [ -z "${ALBEDO_NO_MBX:-}" ] && mbx=$(command -v mbx); then
42  if [ -n "${ALBEDO_NIX_ENV:-}" ]; then
43    HOST_CFLAGS="${HOST_CFLAGS:-${CFLAGS:-}} -DALBEDO_NIX_ENV_$ALBEDO_NIX_ENV"
44    HOST_CXXFLAGS="${HOST_CXXFLAGS:-${CXXFLAGS:-}} -DALBEDO_NIX_ENV_$ALBEDO_NIX_ENV"
45    export HOST_CFLAGS HOST_CXXFLAGS
46    if default "${CC:-}" cc; then unset CC; fi
47    if default "${CXX:-}" c++; then unset CXX; fi
48  fi
49  MBX_CARGO_SHIM_MODE=1 MBX_CARGO_SHIM_PATH="$0" exec env -u SDKROOT "$mbx" "$@"
50fi
51PATH=$(printf %s "$PATH" | tr : '\n' | grep -vxF "$here" | paste -sd: -)
52exec cargo "$@"
53"""
54
55NIX = r"""#!/bin/sh
56# albedo's nix: albedo_nix.py with the kernel's Python, else the next nix on PATH.
57here=$(cd "$(dirname "$0")" && pwd)
58if [ -z "${ALBEDO_PLAIN_NIX:-}" ] && [ -x "${ALBEDO_SHIMS_PYTHON:-}" ]; then
59  exec "$ALBEDO_SHIMS_PYTHON" -E -s "$ALBEDO_SHIMS_LIB/albedo_nix.py" "$here" "$@"
60fi
61PATH=$(printf %s "$PATH" | tr : '\n' | grep -vxF "$here" | paste -sd: -)
62exec nix "$@"
63"""
64
65# directory (named for the program a shim needs on PATH) -> (shim, script)
66SHIMS = {"nix": ("nix", NIX), "mbx": ("cargo", CARGO)}
67# where a program lives when the process that started the daemon had a thin
68# PATH (a GUI or launchd start never ran the user's profile)
69FALLBACKS = {"nix": ("/nix/var/nix/profiles/default/bin", "~/.nix-profile/bin")}
70_written: set[Path] = set()
71
72
73def _with_fallbacks(entries: list[str]) -> list[str]:
74    """`entries`, plus the directory providing each program of FALLBACKS that
75    PATH lacks. It goes last, so it never shadows what the user put on PATH, and
76    only the first directory that holds the program is added, not a whole profile."""
77    for program, directories in FALLBACKS.items():
78        if shutil.which(program, path=os.pathsep.join(entries)):
79            continue
80        for directory in map(os.path.expanduser, directories):
81            if os.access(os.path.join(directory, program), os.X_OK):
82                entries = [*entries, directory]
83                break
84    return entries
85
86
87def _directory(needs: str) -> Path:
88    """The directory of the shim that needs `needs`, written once per kernel."""
89    home = os.environ.get("ALBEDO_HOME") or os.path.expanduser("~/.albedo")
90    directory = Path(home, "shims", needs)
91    if directory not in _written:
92        name, script = SHIMS[needs]
93        directory.mkdir(parents=True, exist_ok=True)
94        shim = directory / name
95        if not shim.is_file() or shim.read_text() != script:
96            temporary = directory / f".{name}.{os.getpid()}"
97            temporary.write_text(script)
98            temporary.chmod(0o755)
99            temporary.replace(shim)
100        _written.add(directory)
101    return directory
102
103
104def prepend(environment: dict[str, str] | None) -> dict[str, str] | None:
105    """Return the job environment with only this kernel's shims before real tools.
106    Remove inherited shim generations even when their tools are absent."""
107    base = environment or os.environ
108    path = base.get("PATH", "")
109    entries = path.split(os.pathsep)
110    # Remove inherited generations before adding this kernel's shims.
111    entries = [
112        entry
113        for entry in entries
114        if not (Path(entry).name in SHIMS and Path(entry).parent.name == "shims")
115    ]
116    path = os.pathsep.join(_with_fallbacks(entries))
117    shims = [
118        str(_directory(needs))
119        for needs in SHIMS
120        if shutil.which(needs, path=path) is not None
121    ]
122    if not shims:
123        if environment is None and path == base.get("PATH", ""):
124            return None
125        return {**base, "PATH": path}
126    return {
127        **base,
128        "PATH": os.pathsep.join([*shims, path]),
129        "ALBEDO_SHIMS_PYTHON": sys.executable,
130        "ALBEDO_SHIMS_LIB": str(Path(__file__).resolve().parent),
131    }