Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
109 changes: 106 additions & 3 deletions public/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -138,22 +138,125 @@ cleanup_openclaw_bin_conflict() {
return 1
}

npm_log_indicates_missing_build_tools() {
local log="$1"
if [[ -z "$log" || ! -f "$log" ]]; then
return 1
fi

grep -Eiq "(not found: make|make: command not found|cmake: command not found|CMAKE_MAKE_PROGRAM is not set|Could not find CMAKE|gyp ERR! find Python|no developer tools were found|is not able to compile a simple test program|Failed to build llama\\.cpp|It seems that \"make\" is not installed in your system|It seems that the used \"cmake\" doesn't work properly)" "$log"
}

install_build_tools_linux() {
require_sudo

if command -v apt-get &> /dev/null; then
maybe_sudo apt-get update -y
maybe_sudo apt-get install -y build-essential python3 make g++ cmake
return 0
fi

if command -v dnf &> /dev/null; then
maybe_sudo dnf install -y gcc gcc-c++ make cmake python3
return 0
fi

if command -v yum &> /dev/null; then
maybe_sudo yum install -y gcc gcc-c++ make cmake python3
return 0
fi

if command -v apk &> /dev/null; then
maybe_sudo apk add --no-cache build-base python3 cmake
return 0
fi

echo -e "${WARN}→${NC} Could not detect package manager for auto-installing build tools."
return 1
}

install_build_tools_macos() {
local ok=true

if ! xcode-select -p >/dev/null 2>&1; then
echo -e "${WARN}→${NC} Installing Xcode Command Line Tools..."
xcode-select --install >/dev/null 2>&1 || true
if ! xcode-select -p >/dev/null 2>&1; then
echo -e "${WARN}→${NC} Xcode Command Line Tools are not ready yet."
echo -e "${INFO}i${NC} Complete the installer dialog, then re-run this installer."
ok=false
fi
fi

if ! command -v cmake >/dev/null 2>&1; then
if command -v brew >/dev/null 2>&1; then
echo -e "${WARN}→${NC} Installing cmake..."
brew install cmake
else
echo -e "${WARN}→${NC} Homebrew not available; cannot auto-install cmake."
ok=false
fi
fi

if ! command -v make >/dev/null 2>&1; then
echo -e "${WARN}→${NC} make is still unavailable."
ok=false
fi
if ! command -v cmake >/dev/null 2>&1; then
echo -e "${WARN}→${NC} cmake is still unavailable."
ok=false
fi

[[ "$ok" == "true" ]]
}

auto_install_build_tools_for_npm_failure() {
local log="$1"
if ! npm_log_indicates_missing_build_tools "$log"; then
return 1
fi

echo -e "${WARN}→${NC} Detected missing native build tools; attempting automatic setup..."
if [[ "$OS" == "linux" ]]; then
install_build_tools_linux || return 1
elif [[ "$OS" == "macos" ]]; then
install_build_tools_macos || return 1
else
return 1
fi
echo -e "${SUCCESS}✓${NC} Build tools setup complete"
return 0
}

run_npm_global_install_once() {
local spec="$1"
local log="$2"

SHARP_IGNORE_GLOBAL_LIBVIPS="$SHARP_IGNORE_GLOBAL_LIBVIPS" npm --loglevel "$NPM_LOGLEVEL" ${NPM_SILENT_FLAG:+$NPM_SILENT_FLAG} --no-fund --no-audit install -g "$spec" 2>&1 | tee "$log"
}

install_openclaw_npm() {
local spec="$1"
local log
log="$(mktempfile)"
if ! SHARP_IGNORE_GLOBAL_LIBVIPS="$SHARP_IGNORE_GLOBAL_LIBVIPS" npm --loglevel "$NPM_LOGLEVEL" ${NPM_SILENT_FLAG:+$NPM_SILENT_FLAG} --no-fund --no-audit install -g "$spec" 2>&1 | tee "$log"; then
if ! run_npm_global_install_once "$spec" "$log"; then
if auto_install_build_tools_for_npm_failure "$log"; then
echo -e "${WARN}→${NC} Retrying npm install after build tools setup..."
if run_npm_global_install_once "$spec" "$log"; then
return 0
fi
fi
if grep -q "ENOTEMPTY: directory not empty, rename .*openclaw" "$log"; then
echo -e "${WARN}→${NC} npm left a stale openclaw directory; cleaning and retrying..."
cleanup_npm_openclaw_paths
SHARP_IGNORE_GLOBAL_LIBVIPS="$SHARP_IGNORE_GLOBAL_LIBVIPS" npm --loglevel "$NPM_LOGLEVEL" ${NPM_SILENT_FLAG:+$NPM_SILENT_FLAG} --no-fund --no-audit install -g "$spec"
run_npm_global_install_once "$spec" "$log"
return $?
fi
if grep -q "EEXIST" "$log"; then
local conflict=""
conflict="$(extract_openclaw_conflict_path "$log" || true)"
if [[ -n "$conflict" ]] && cleanup_openclaw_bin_conflict "$conflict"; then
SHARP_IGNORE_GLOBAL_LIBVIPS="$SHARP_IGNORE_GLOBAL_LIBVIPS" npm --loglevel "$NPM_LOGLEVEL" ${NPM_SILENT_FLAG:+$NPM_SILENT_FLAG} --no-fund --no-audit install -g "$spec"
run_npm_global_install_once "$spec" "$log"
return $?
fi
echo -e "${ERROR}npm failed because an openclaw binary already exists.${NC}"
Expand Down
93 changes: 76 additions & 17 deletions scripts/test-install-sh-unit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ source "${ROOT_DIR}/public/install.sh"
echo "==> case: direct PATH"
(
bin="${TMP_DIR}/case-path/bin"
make_exe "${bin}/clawdbot" 'echo "ok" >/dev/null'
make_exe "${bin}/openclaw" 'echo "ok" >/dev/null'
export PATH="${bin}:/usr/bin:/bin"

got="$(resolve_clawdbot_bin)"
assert_eq "$got" "${bin}/clawdbot" "resolve_clawdbot_bin (direct PATH)"
got="$(resolve_openclaw_bin)"
assert_eq "$got" "${bin}/openclaw" "resolve_openclaw_bin (direct PATH)"
)

echo "==> case: npm prefix -g"
Expand All @@ -61,12 +61,12 @@ echo "==> case: npm prefix -g"
tool_bin="${root}/tool-bin"

make_exe "${tool_bin}/npm" "if [[ \"\$1\" == \"prefix\" && \"\$2\" == \"-g\" ]]; then echo \"${prefix}\"; exit 0; fi; exit 1"
make_exe "${prefix}/bin/clawdbot" 'echo "ok" >/dev/null'
make_exe "${prefix}/bin/openclaw" 'echo "ok" >/dev/null'

export PATH="${tool_bin}:/usr/bin:/bin"

got="$(resolve_clawdbot_bin)"
assert_eq "$got" "${prefix}/bin/clawdbot" "resolve_clawdbot_bin (npm prefix -g)"
got="$(resolve_openclaw_bin)"
assert_eq "$got" "${prefix}/bin/openclaw" "resolve_openclaw_bin (npm prefix -g)"
)

echo "==> case: npm prefix -g fallback"
Expand All @@ -76,12 +76,12 @@ echo "==> case: npm prefix -g fallback"
tool_bin="${root}/tool-bin"

make_exe "${tool_bin}/npm" "if [[ \"\$1\" == \"bin\" && \"\$2\" == \"-g\" ]]; then exit 1; fi; if [[ \"\$1\" == \"prefix\" && \"\$2\" == \"-g\" ]]; then echo \"${prefix}\"; exit 0; fi; exit 1"
make_exe "${prefix}/bin/clawdbot" 'echo "ok" >/dev/null'
make_exe "${prefix}/bin/openclaw" 'echo "ok" >/dev/null'

export PATH="${tool_bin}:/usr/bin:/bin"

got="$(resolve_clawdbot_bin)"
assert_eq "$got" "${prefix}/bin/clawdbot" "resolve_clawdbot_bin (npm prefix -g)"
got="$(resolve_openclaw_bin)"
assert_eq "$got" "${prefix}/bin/openclaw" "resolve_openclaw_bin (npm prefix -g)"
)

echo "==> case: nodenv rehash shim creation"
Expand All @@ -97,34 +97,93 @@ echo "==> case: nodenv rehash shim creation"
#!/usr/bin/env bash
set -euo pipefail
if [[ "\${1:-}" == "rehash" ]]; then
cat >"${shim}/clawdbot" <<'SHIM'
cat >"${shim}/openclaw" <<'SHIM'
#!/usr/bin/env bash
set -euo pipefail
echo ok >/dev/null
SHIM
chmod +x "${shim}/clawdbot"
chmod +x "${shim}/openclaw"
exit 0
fi
exit 0
EOF
chmod +x "${tool_bin}/nodenv"

export PATH="${shim}:${tool_bin}:/usr/bin:/bin"
command -v clawdbot >/dev/null 2>&1 && fail "precondition: clawdbot unexpectedly present"
command -v openclaw >/dev/null 2>&1 && fail "precondition: openclaw unexpectedly present"

got="$(resolve_clawdbot_bin)"
assert_eq "$got" "${shim}/clawdbot" "resolve_clawdbot_bin (nodenv rehash)"
got="$(resolve_openclaw_bin)"
assert_eq "$got" "${shim}/openclaw" "resolve_openclaw_bin (nodenv rehash)"
)

echo "==> case: warn_clawdbot_not_found (smoke)"
echo "==> case: warn_openclaw_not_found (smoke)"
(
root="${TMP_DIR}/case-warn"
tool_bin="${root}/tool-bin"
make_exe "${tool_bin}/npm" 'if [[ "$1" == "prefix" && "$2" == "-g" ]]; then echo "/tmp/prefix"; exit 0; fi; if [[ "$1" == "bin" && "$2" == "-g" ]]; then echo "/tmp/prefix/bin"; exit 0; fi; exit 1'
export PATH="${tool_bin}:/usr/bin:/bin"

out="$(warn_clawdbot_not_found 2>&1 || true)"
assert_nonempty "$out" "warn_clawdbot_not_found output"
out="$(warn_openclaw_not_found 2>&1 || true)"
assert_nonempty "$out" "warn_openclaw_not_found output"
)

echo "==> case: npm_log_indicates_missing_build_tools"
(
root="${TMP_DIR}/case-build-tools-signature"
mkdir -p "${root}"

positive_log="${root}/positive.log"
negative_log="${root}/negative.log"

cat >"${positive_log}" <<'EOF'
gyp ERR! stack Error: not found: make
EOF
cat >"${negative_log}" <<'EOF'
npm ERR! code EEXIST
EOF

if ! npm_log_indicates_missing_build_tools "${positive_log}"; then
fail "npm_log_indicates_missing_build_tools should detect missing build tools"
fi
if npm_log_indicates_missing_build_tools "${negative_log}"; then
fail "npm_log_indicates_missing_build_tools false positive"
fi
)

echo "==> case: install_openclaw_npm (auto-install build tools + retry)"
(
root="${TMP_DIR}/case-install-openclaw-auto-build-tools"
mkdir -p "${root}"

export OS="linux"
install_attempts=0
auto_install_called=0

run_npm_global_install_once() {
local _spec="$1"
local log="$2"
install_attempts=$((install_attempts + 1))
if [[ "$install_attempts" -eq 1 ]]; then
cat >"${log}" <<'EOF'
gyp ERR! stack Error: not found: make
EOF
return 1
fi
cat >"${log}" <<'EOF'
ok
EOF
return 0
}

auto_install_build_tools_for_npm_failure() {
local _log="$1"
auto_install_called=1
return 0
}

install_openclaw_npm "openclaw@latest"
assert_eq "$install_attempts" "2" "install_openclaw_npm retry count"
assert_eq "$auto_install_called" "1" "install_openclaw_npm auto-install hook"
)

echo "OK"