Files
memind/scripts/lib/bundle-mach-dylibs.sh
T
john 78e5a6c026
Memind CI / Test, build, and release guards (pull_request) Failing after 18s
feat(ops): add imgproxy native runtime tooling and record 103 deployment
Bundle vendored imgproxy with Mach-O dylibs for 103 launchd, fix install to exec the binary directly with launchctl enable, and document the live native release in the runtime topology.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 05:38:14 +08:00

224 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Recursively copy non-system dylibs next to a Mach-O binary and rewrite load paths.
set -euo pipefail
find_homebrew_lib() {
local base="$1"
find "${HOMEBREW_PREFIX:-/opt/homebrew}/Cellar" "${HOMEBREW_PREFIX:-/opt/homebrew}/opt" \
-name "${base}" -path '*/lib/*' 2>/dev/null | head -1
}
resolve_dependency_path() {
local owner="$1"
local dep="$2"
local reference="${3:-${owner}}"
if [[ "${dep}" != @* ]]; then
[[ -f "${dep}" ]] || return 1
printf '%s\n' "${dep}"
return 0
fi
local owner_dir reference_dir
owner_dir="$(cd "$(dirname "${owner}")" && pwd)"
reference_dir="$(cd "$(dirname "${reference}")" && pwd)"
case "${dep}" in
@loader_path/*)
local candidate="${owner_dir}/${dep#@loader_path/}"
[[ -f "${candidate}" ]] || return 1
printf '%s\n' "${candidate}"
;;
@executable_path/*)
local rel="${dep#@executable_path/}"
local candidate="${owner_dir}/${rel}"
if [[ ! -f "${candidate}" ]]; then
candidate="${reference_dir}/${rel}"
fi
if [[ ! -f "${candidate}" ]]; then
candidate="$(find_homebrew_lib "$(basename "${rel}")")"
fi
[[ -f "${candidate}" ]] || return 1
printf '%s\n' "${candidate}"
;;
@rpath/*)
local rel="${dep#@rpath/}"
local rpath candidate
while IFS= read -r rpath; do
case "${rpath}" in
@loader_path/*) candidate="${owner_dir}/${rpath#@loader_path/}/${rel}" ;;
@executable_path/*) candidate="${owner_dir}/${rpath#@executable_path/}/${rel}" ;;
*) candidate="${rpath}/${rel}" ;;
esac
if [[ -f "${candidate}" ]]; then
printf '%s\n' "${candidate}"
return 0
fi
done < <(
otool -l "${owner}" | awk '
/cmd LC_RPATH/ { want=1; next }
want && /path / {
sub(/^.*path /, "", $0)
sub(/ \(offset.*$/, "", $0)
print
want=0
}
'
)
candidate="${owner_dir}/${rel}"
[[ -f "${candidate}" ]] || return 1
printf '%s\n' "${candidate}"
;;
*)
return 1
;;
esac
}
list_load_dylibs() {
local target="$1"
otool -l "${target}" | awk '
/cmd LC_LOAD_DYLIB/ { want=1; next }
want && /name / {
sub(/^.*name /, "", $0)
sub(/ \(offset.*$/, "", $0)
print
want=0
}
'
}
bundle_mach_dylibs() {
local binary="${1:?binary path required}"
local lib_dir="${2:?lib directory required}"
local reference_binary="${3:-${binary}}"
[[ -f "${binary}" ]] || {
echo "bundle_mach_dylibs: missing binary: ${binary}" >&2
return 1
}
mkdir -p "${lib_dir}"
local -a queue=("${binary}")
local seen_list=" "
local -a sources=()
is_system_lib() {
case "$1" in
/usr/lib/*|/System/*|/Library/*) return 0 ;;
esac
return 1
}
already_seen() {
[[ "${seen_list}" == *" $1 "* ]]
}
remember() {
seen_list+=" $1 "
}
while ((${#queue[@]} > 0)); do
local target="${queue[0]}"
queue=("${queue[@]:1}")
[[ -f "${target}" ]] || continue
local dep resolved
while IFS= read -r dep; do
[[ -n "${dep}" ]] || continue
is_system_lib "${dep}" && continue
already_seen "${dep}" && continue
remember "${dep}"
resolved="$(resolve_dependency_path "${target}" "${dep}" "${reference_binary}")" || {
echo "bundle_mach_dylibs: missing dependency ${dep} for ${target}" >&2
return 1
}
already_seen "${resolved}" || remember "${resolved}"
if [[ "${resolved}" != "${binary}" ]]; then
sources+=("${resolved}")
queue+=("${resolved}")
fi
done < <(list_load_dylibs "${target}")
done
local src base
for src in "${sources[@]}"; do
base="$(basename "${src}")"
cp -f "${src}" "${lib_dir}/${base}"
chmod 755 "${lib_dir}/${base}"
done
rewrite_dep() {
local target="$1"
local dep="$2"
local new_path="$3"
install_name_tool -change "${dep}" "${new_path}" "${target}" 2>/dev/null || true
}
rewrite_targets() {
local target="$1"
local prefix="$2"
local dep base
while IFS= read -r dep; do
[[ -n "${dep}" ]] || continue
is_system_lib "${dep}" && continue
[[ "${dep}" == @* ]] && continue
base="$(basename "${dep}")"
[[ -f "${lib_dir}/${base}" ]] || continue
rewrite_dep "${target}" "${dep}" "${prefix}${base}"
done < <(list_load_dylibs "${target}")
}
rewrite_rpath_targets() {
local target="$1"
local prefix="$2"
local dep base
while IFS= read -r dep; do
[[ -n "${dep}" ]] || continue
[[ "${dep}" == @rpath/* ]] || continue
base="$(basename "${dep}")"
[[ -f "${lib_dir}/${base}" ]] || continue
rewrite_dep "${target}" "${dep}" "${prefix}${base}"
done < <(list_load_dylibs "${target}")
}
local changed=1
while ((changed)); do
changed=0
rewrite_targets "${binary}" "@executable_path/../lib/"
rewrite_rpath_targets "${binary}" "@executable_path/../lib/"
for staged in "${lib_dir}"/*.dylib; do
[[ -f "${staged}" ]] || continue
before="$(list_load_dylibs "${staged}" | { grep -E 'homebrew|^@rpath/' || true; } | wc -l | tr -d ' ')"
rewrite_targets "${staged}" "@loader_path/"
rewrite_rpath_targets "${staged}" "@loader_path/"
after="$(list_load_dylibs "${staged}" | { grep -E 'homebrew|^@rpath/' || true; } | wc -l | tr -d ' ')"
[[ "${before}" != "${after}" ]] && changed=1
done
done
local remaining
remaining="$(list_load_dylibs "${binary}" | grep homebrew || true)"
if [[ -n "${remaining}" ]]; then
echo "bundle_mach_dylibs: unresolved homebrew dependencies remain:" >&2
printf '%s\n' "${remaining}" >&2
return 1
fi
install_name_tool -add_rpath "@executable_path/../lib" "${binary}" 2>/dev/null || true
local staged
for staged in "${lib_dir}"/*.dylib; do
[[ -f "${staged}" ]] || continue
codesign --force --sign - "${staged}" >/dev/null
done
codesign --force --sign - "${binary}" >/dev/null
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
bundle_mach_dylibs "$@"
fi