Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 110 additions & 4 deletions .github/scripts/clean-machine-assert.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
# absent The toolchain really was absent for the whole run. Catches a leg that
# "passed" because masking silently failed, or because the installer
# quietly installed Xcode CLT behind our back.
# notools The trace recorded no compiler/git/brew invocation (trace mode).
# notools The trace recorded no compiler/git/brew invocation (trace mode),
# except uv's exact optional libpython self-ID operation.
# nodylibtool No install_name_tool invocation escaped the CLT-absent guard.
# dylibpatch A CLT-present control observed only exact libpython self-ID patches.
# nobuild Wheels-only: no "Building wheel" from pip, no "Building <pkg>==<ver>"
# from uv. Needs UNSLOTH_VERBOSE=1, or run_install_cmd
# (install.sh:193-243) discards uv's output on success.
# macho Every Mach-O under $MACHO_ROOT is the host architecture, and every
# Mach-O MAIN EXECUTABLE is signed. Closes the Rosetta 2 gap, the one
# divergence masking cannot reproduce.
#
# Usage: bash .github/scripts/clean-machine-assert.sh absent notools nobuild macho
# Usage: bash .github/scripts/clean-machine-assert.sh absent nodylibtool notools dylibpatch nobuild macho
set -uo pipefail

LOG="${INSTALL_LOG:-logs/install.log}"
Expand All @@ -25,6 +28,50 @@ rc=0
fail() { echo "::error::$*"; rc=1; }
ok() { echo "[assert] OK $*"; }

_decode_trace_arg() { # encoded, destination variable
_encoded=$1
case "$_encoded" in h*) _hex=${_encoded#h} ;; *) return 1 ;; esac
case "$_hex" in *[!0123456789abcdef]* ) return 1 ;; esac
[ $(( ${#_hex} % 2 )) -eq 0 ] || return 1
_decoded=""
while [ -n "$_hex" ]; do
_rest=${_hex#??}
_pair=${_hex%"$_rest"}
_hex=$_rest
printf -v _byte '%b' "\\x$_pair"
_decoded+=$_byte
done
printf -v "$2" '%s' "$_decoded"
}

_is_uv_libpython_self_id_patch() { # argc, operation, source, destination, extra
[ "$1" = "3" ] && [ "$2" = "-id" ] && [ -n "$3" ] && [ "$3" = "$4" ] \
&& [ -z "$5" ] || return 1
_patch_name=${3##*/}
case "$_patch_name" in libpython*.dylib) ;; *) return 1 ;; esac
_patch_dir=${3%/*}

if [ -n "${UV_PYTHON_INSTALL_DIR:-}" ]; then
_patch_root=${UV_PYTHON_INSTALL_DIR%/}
else
# Default uv data locations end in uv/python; this fallback keeps the assertion
# useful outside CI, where UV_PYTHON_INSTALL_DIR is normally unset.
case "$3" in */uv/python/*) ;; *) return 1 ;; esac
_patch_root=${3%%/uv/python/*}/uv/python
fi

# Resolve both directories physically before comparing them. A lexical shell glob
# would accept "$root/x/../../outside/..." (and symlink escapes) because * spans '/'.
_patch_root=$(CDPATH= cd "$_patch_root" 2>/dev/null && pwd -P) || return 1
_patch_dir=$(CDPATH= cd "$_patch_dir" 2>/dev/null && pwd -P) || return 1
case "$_patch_dir" in "$_patch_root"/*/lib) ;; *) return 1 ;; esac
_patch_install=${_patch_dir#"$_patch_root"/}
_patch_install=${_patch_install%/lib}
[ -n "$_patch_install" ] || return 1
case "$_patch_install" in */*) return 1 ;; esac
return 0
}

for check in "$@"; do
case "$check" in

Expand Down Expand Up @@ -63,6 +110,52 @@ for check in "$@"; do
fi
;;

nodylibtool)
if [ -z "$TRACE" ] || [ ! -f "$TRACE" ]; then
fail "nodylibtool requested but no trace file (\$UNSLOTH_TOOL_TRACE=$TRACE)"
else
_dylib_hits=0
while IFS=$'\t' read -r tool _rest; do
[ "$tool" = "install_name_tool" ] && _dylib_hits=$((_dylib_hits + 1))
done < "$TRACE"
if [ "$_dylib_hits" -ne 0 ]; then
fail "install_name_tool escaped the CLT-absent uv guard ($_dylib_hits invocation(s))"
grep '^install_name_tool[[:space:]]' "$TRACE" | head -20 || true
else
ok "install_name_tool was never reached on the CLT-absent path"
fi
fi
;;

dylibpatch)
if [ -z "$TRACE" ] || [ ! -f "$TRACE" ]; then
fail "dylibpatch requested but no trace file (\$UNSLOTH_TOOL_TRACE=$TRACE)"
else
_dylib_hits=0
_dylib_bad=0
while IFS=$'\t' read -r tool argc operation_encoded source_encoded destination_encoded extra; do
[ "$tool" = "install_name_tool" ] || continue
_dylib_hits=$((_dylib_hits + 1))
operation=""; source=""; destination=""
if ! _decode_trace_arg "$operation_encoded" operation \
|| ! _decode_trace_arg "$source_encoded" source \
|| ! _decode_trace_arg "$destination_encoded" destination \
|| ! _is_uv_libpython_self_id_patch "$argc" "$operation" "$source" "$destination" "$extra"; then
_dylib_bad=$((_dylib_bad + 1))
echo "::error::invalid install_name_tool trace record: $tool argc=$argc"
fi
done < "$TRACE"
if [ "$_dylib_hits" -eq 0 ]; then
fail "CLT-present control recorded no install_name_tool patch; managed Python may have been reused"
elif [ "$_dylib_bad" -ne 0 ]; then
fail "$_dylib_bad of $_dylib_hits install_name_tool invocation(s) were not exact libpython self-ID patches"
else
ok "all $_dylib_hits install_name_tool invocation(s) were exact libpython self-ID patches"
fi
fi
;;


notools)
if [ -z "$TRACE" ] || [ ! -f "$TRACE" ]; then
fail "notools requested but no trace file (\$UNSLOTH_TOOL_TRACE=$TRACE)"
Expand All @@ -71,13 +164,26 @@ for check in "$@"; do
# leg allow-lists it via UNSLOTH_ALLOW_TOOLS.
allow="${UNSLOTH_ALLOW_TOOLS:-}"
hits=""
while IFS=$'\t' read -r tool rest; do
while IFS=$'\t' read -r tool argc_or_rest arg1 arg2 arg3 extra; do
[ -n "$tool" ] || continue
# This optional uv operation is the only permitted developer-tool use. Keep it
# structural rather than name-only: arbitrary install_name_tool calls still fail.
if [ "$tool" = "install_name_tool" ]; then
operation=""; source=""; destination=""
if _decode_trace_arg "$arg1" operation \
&& _decode_trace_arg "$arg2" source \
&& _decode_trace_arg "$arg3" destination \
&& _is_uv_libpython_self_id_patch "$argc_or_rest" "$operation" "$source" "$destination" "$extra"; then
continue
fi
hits="$hits $tool"
continue
fi
case " $allow " in *" $tool "*) continue ;; esac
# `xcode-select -p` only ASKS whether a toolchain is selected and the fix is
# carrying on without one, so it is not USE. `--install` stays a hit.
if [ "$tool" = "xcode-select" ]; then
case "$rest" in
case "$argc_or_rest" in
-p|--print-path|-v|--version|"") continue ;;
esac
fi
Expand Down
35 changes: 30 additions & 5 deletions .github/scripts/clean-machine-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
#
# mask Make the toolchain genuinely ABSENT: scrub PATH to OS defaults and (with
# --remove) move the real toolchain aside so `command -v git` correctly
# FAILS. Deliberately no "poison shims": a failing shim is still FOUND by
# `command -v`, which reports the tool as present, the opposite of clean.
# FAILS. Deliberately no general "poison shims": a failing shim is still FOUND
# by `command -v`, which reports the tool as present, the opposite of clean.
# macOS has one observation-only exception: install_name_tool gets a logging
# sentinel because the installer must shadow Apple's dialog-producing shim and
# never uses this command to decide whether a dependency is installed.
# trace Leave the toolchain working behind wrappers that log the call then exec
# the real binary, answering whether the installer ever REACHES for a
# compiler/git without changing behaviour.
Expand All @@ -23,6 +26,9 @@
# source ./clean-machine.env
set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALL_NAME_TOOL_HELPER="$SCRIPT_DIR/clean-machine-install-name-tool.sh"

MODE="${1:-}"
REMOVE=0
[ "${2:-}" = "--remove" ] && REMOVE=1
Expand All @@ -45,7 +51,11 @@ printf '#!/usr/bin/env bash\n# Undo clean-machine-env.sh --remove. Safe to run t
chmod +x "$RESTORE"

# The toolchain we care about: a consumer install must need none of it.
TOOLS="xcode-select xcrun clang clang++ cc c++ gcc g++ git cmake make brew ninja cargo rustc"
# cctools binaries are included because their /usr/bin shims can trigger the same
# developer-tools dialog. uv's exact optional install_name_tool self-ID patch is
# observed separately and narrowly allow-listed by clean-machine-assert.sh.
TOOLS="xcode-select xcrun clang clang++ cc c++ gcc g++ git cmake make brew ninja cargo rustc
install_name_tool lipo otool objdump vtool strip nm"

note() { echo "[clean-machine] $*"; }

Expand Down Expand Up @@ -85,6 +95,14 @@ scrub_path() {
# ── mask ──────────────────────────────────────────────────────────────────────
if [ "$MODE" = "mask" ]; then
NEWPATH="$(scrub_path)"
if [ "$OS" = "Darwin" ]; then
# Do not execute /usr/bin/install_name_tool as a self-test on a CLT-free Mac: that
# is the GUI prompt this lane exists to prevent. This sentinel is ahead of /usr/bin,
# logs argv with explicit argc and hex-encoded argument boundaries, and fails without
# touching a dylib. install.sh's still-more-local uv guard must win over it.
bash "$INSTALL_NAME_TOOL_HELPER" write sentinel "$BIN/install_name_tool"
NEWPATH="$BIN:$NEWPATH"
fi
{
echo "export PATH='$NEWPATH'"
# UNSET, not a fake path: `xcode-select -p` honours DEVELOPER_DIR and prints it
Expand All @@ -94,6 +112,8 @@ if [ "$MODE" = "mask" ]; then
echo "unset SDKROOT CC CXX CFLAGS CXXFLAGS LDFLAGS CMAKE_GENERATOR CMAKE_PREFIX_PATH || true"
echo "export HOMEBREW_NO_AUTO_UPDATE=1"
echo "export UNSLOTH_CLEAN_MACHINE=1"

echo "export UNSLOTH_TOOL_TRACE='$TRACE'"
} >> "$ENV_FILE"

if [ "$REMOVE" = "1" ] && [ "$OS" = "Darwin" ]; then
Expand Down Expand Up @@ -210,12 +230,17 @@ if [ "$MODE" = "trace" ]; then
real="$(command -v "$tool" 2>/dev/null || true)"
[ -n "$real" ] || continue
# Logs then execs the REAL binary, so behaviour is unchanged and the trace answers
# "did the installer reach for this?" honestly.
cat > "$BIN/$tool" <<WRAP
# "did the installer reach for this?" honestly. install_name_tool needs preserved
# argument boundaries via hex so the assertion can require exact -id PATH PATH argv.
if [ "$tool" = "install_name_tool" ]; then
bash "$INSTALL_NAME_TOOL_HELPER" write passthrough "$BIN/$tool" "$real"
else
cat > "$BIN/$tool" <<WRAP
#!/bin/sh
printf '%s\t%s\n' "$tool" "\$*" >> "$TRACE"
exec "$real" "\$@"
WRAP
fi
chmod +x "$BIN/$tool"
done
{
Expand Down
94 changes: 94 additions & 0 deletions .github/scripts/clean-machine-install-name-tool.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
# Shared install_name_tool trace wrapper and CLT-absent sentinel contract.
set -euo pipefail

usage() {
echo "usage: $0 write {sentinel|passthrough} TARGET [REAL_TOOL]" >&2
echo " $0 verify-sentinel TRACE MARKER" >&2

echo " $0 decode TRACE_ARG" >&2
exit 2
}


decode_trace_arg() {
encoded=$1
case "$encoded" in h*) hex=${encoded#h} ;; *) return 1 ;; esac
case "$hex" in *[!0123456789abcdef]*) return 1 ;; esac
[ $(( ${#hex} % 2 )) -eq 0 ] || return 1
decoded=""
while [ -n "$hex" ]; do
rest=${hex#??}
pair=${hex%"$rest"}
hex=$rest
printf -v byte '%b' "\\x$pair"
decoded+=$byte
done
printf '%s' "$decoded"
}

case "${1:-}" in
write)
kind="${2:-}"
target="${3:-}"
[ -n "$target" ] || usage
case "$kind" in sentinel|passthrough) ;; *) usage ;; esac

cat > "$target" <<'WRAPPER'
#!/bin/sh
: "${UNSLOTH_TOOL_TRACE:?UNSLOTH_TOOL_TRACE is required}"
encode_trace_arg() {
printf '%s' "$1" | od -An -v -tx1 | tr -d ' \n'
}
printf 'install_name_tool\t%s' "$#" >> "$UNSLOTH_TOOL_TRACE"
for arg in "$@"; do printf '\th%s' "$(encode_trace_arg "$arg")" >> "$UNSLOTH_TOOL_TRACE"; done
printf '\n' >> "$UNSLOTH_TOOL_TRACE"
WRAPPER
if [ "$kind" = "sentinel" ]; then
printf '%s\n' 'exit 97' >> "$target"
else
real_tool="${4:-}"
[ -n "$real_tool" ] || usage
case "$real_tool" in *'"'*|*$'\n'*) echo "unsupported tool path: $real_tool" >&2; exit 2 ;; esac
printf 'exec "%s" "$@"\n' "$real_tool" >> "$target"
fi
chmod +x "$target"
;;

decode)
[ "$#" -eq 2 ] || usage
decode_trace_arg "$2"
;;


verify-sentinel)
trace="${2:-}"
marker="${3:-}"
[ -n "$trace" ] && [ -n "$marker" ] || usage
[ -n "${UNSLOTH_TOOL_TRACE:-}" ] && [ "$UNSLOTH_TOOL_TRACE" = "$trace" ] || {
echo "::error::install_name_tool sentinel trace environment is not active" >&2
exit 1
}

set +e
install_name_tool "--${marker}-sentinel-self-test" >/dev/null 2>&1
sentinel_rc=$?
set -e
[ "$sentinel_rc" -eq 97 ] || {
echo "::error::install_name_tool sentinel returned $sentinel_rc, expected 97" >&2
exit 1
}
marker_hex=$(printf '%s' "--${marker}-sentinel-self-test" | od -An -v -tx1 | tr -d ' \n')
expected=$(printf 'install_name_tool\t1\th%s' "$marker_hex")
grep -Fqx "$expected" "$trace" || {
echo "::error::install_name_tool sentinel did not preserve/record its self-test argv" >&2
cat "$trace" >&2 || true
exit 1
}
: > "$trace"
;;

*) usage ;;
esac
Loading
Loading