Files
LEDMatrix/scripts/diagnose_dependencies.sh
T
16fbb7ebeb fix(install): survive the rgbmatrix build on low-memory Pis (#430)
* fix(install): survive the rgbmatrix build on low-memory Pis

The one-shot installer failed at Step 6 on a 1GB Pi with "Failed building
wheel for rgbmatrix", and told the user to install build tools they already
had. The real cause was the kernel OOM killer.

Upstream's pyproject.toml declares no [tool.scikit-build] options, so
scikit-build-core drives Ninja at its default of nproc+2 jobs -- six
concurrent compiles on a 4-core Pi. CMakeLists.txt compiles the same 14
sources three times (~45 translation units), two of them Cython-generated
C++ where a single cc1plus peaks near 800MB. That does not fit in 512MB-1GB
of RAM.

Add scripts/install/lib_lowmem.sh and wire it into the installer:

- Cap build parallelism at max(1, min(cores, RAM/768)) via
  CMAKE_BUILD_PARALLEL_LEVEL, which is what cmake --build actually reads.
  MAKEFLAGS is ignored by Ninja and is set only as a Makefile-generator
  fallback. A 4GB Pi 4 still gets 4 jobs; 512MB and 1GB boards get 1.
- Add a temporary swapfile sized to bring RAM+swap to 3GB (capped at 2GB),
  removed once the build finishes. An EXIT trap is the backstop for the
  error path. Nothing is written to /etc/fstab or /etc/dphys-swapfile.
  Existing swap is measured excluding zram, which is compressed RAM and so
  does not help a build OOM.
- Keep pip's build tree off tmpfs. Debian 13 mounts /tmp as tmpfs, so the
  default held the whole C++ build tree in RAM alongside the compiler.
- Diagnose OOM failures from the build log and the kernel ring buffer,
  instead of always blaming missing build tools. The OOM killer writes
  nothing to pip's output, which is why this was misreported.
- Report RAM and the chosen job count in the Step 1 preflight, and emit a
  heartbeat during the compile so a deliberately serial 15-25 minute build
  does not look like a hang.

New flags --skip-swap and --build-jobs N, with LEDMATRIX_SKIP_SWAP and
LEDMATRIX_BUILD_JOBS equivalents.

Also skip the duplicate apt-get update that the one-shot installer and
first_time_install.sh each ran a minute apart, and complete the
dphys-swapfile advice in diagnose_dependencies.sh with the CONF_MAXSWAP
line, without which raising CONF_SWAPSIZE above 2048 is silently clamped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdsJs65WnUo8BtKHMAGA1q

* fix(install): address review findings on the low-memory build path

Three fixes from PR review:

- Validate --build-jobs / LEDMATRIX_BUILD_JOBS before check_memory's
  fallback return. When lib_lowmem.sh is absent that return also honoured
  the override, so a non-numeric value skipped validation and instead blew
  up later in an arithmetic test in Step 6 with a generic error.
- Fall back to the default TMPDIR when the disk-backed build directory
  cannot be created, rather than pointing the build at a path that does
  not exist. A nearly-full disk is the likely cause on exactly the devices
  this targets.
- Pass LEDMATRIX_APT_UPDATED explicitly to the sudo child instead of
  relying on -E, which a sudoers env_reset/env_keep policy can strip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdsJs65WnUo8BtKHMAGA1q

* fix(install): don't add 30s to every rgbmatrix build

The build progress heartbeat slept for the full 30s report interval
before re-checking whether the compile had finished, so every build paid
up to 30 seconds of dead wall time -- including fast ones on a Pi 4/5 and
every --force-rebuild run.

Poll every 2s and report every 30s instead. Measured: 30s of overhead on
an instant build drops to 2s, with heartbeats still emitted on the same
schedule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdsJs65WnUo8BtKHMAGA1q

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-03 15:21:18 -04:00

202 lines
7.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Diagnostic script for Python dependency installation issues
# Run this if pip gets stuck on "Preparing metadata (pyproject.toml)"
set -e
echo "=========================================="
echo "LEDMatrix Dependency Diagnostic Tool"
echo "=========================================="
echo ""
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Get project root (parent of scripts/ directory)
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
echo "Project directory: $PROJECT_ROOT_DIR"
echo ""
# Check system resources
echo "=== System Resources ==="
echo "Disk space:"
df -h / | tail -1
echo ""
echo "Memory:"
free -h
echo ""
echo "CPU info:"
grep -E "^model name|^Hardware|^Revision" /proc/cpuinfo | head -3 || echo "CPU info not available"
echo ""
# Check Python and pip versions
echo "=== Python Environment ==="
echo "Python version:"
python3 --version
echo ""
echo "Pip version:"
python3 -m pip --version || echo "pip not available"
echo ""
# Check if timeout command is available
echo "=== Available Tools ==="
if command -v timeout >/dev/null 2>&1; then
echo -e "${GREEN}${NC} timeout command available"
else
echo -e "${YELLOW}${NC} timeout command not available (install with: sudo apt install coreutils)"
fi
if command -v apt >/dev/null 2>&1; then
echo -e "${GREEN}${NC} apt available"
else
echo -e "${RED}${NC} apt not available"
fi
echo ""
# Check installed build tools
echo "=== Build Tools ==="
BUILD_TOOLS=("gcc" "g++" "make" "python3-dev" "build-essential" "cython3")
for tool in "${BUILD_TOOLS[@]}"; do
if dpkg -l | grep -q "^ii.*$tool"; then
echo -e "${GREEN}${NC} $tool installed"
else
echo -e "${RED}${NC} $tool not installed"
fi
done
echo ""
# Check pip cache
echo "=== Pip Cache ==="
PIP_CACHE_DIR=$(python3 -m pip cache dir 2>/dev/null || echo "unknown")
echo "Pip cache directory: $PIP_CACHE_DIR"
if [ -d "$PIP_CACHE_DIR" ]; then
CACHE_SIZE=$(du -sh "$PIP_CACHE_DIR" 2>/dev/null | cut -f1 || echo "unknown")
echo "Cache size: $CACHE_SIZE"
echo "You can clear the cache with: python3 -m pip cache purge"
fi
echo ""
# Check requirements.txt
echo "=== Requirements File ==="
if [ -f "$PROJECT_ROOT_DIR/requirements.txt" ]; then
echo -e "${GREEN}${NC} requirements.txt found"
TOTAL_PACKAGES=$(grep -v '^#' "$PROJECT_ROOT_DIR/requirements.txt" | grep -v '^$' | wc -l)
echo "Total packages: $TOTAL_PACKAGES"
echo ""
echo "Packages that may need building from source:"
grep -v '^#' "$PROJECT_ROOT_DIR/requirements.txt" | grep -v '^$' | grep -E "(numpy|freetype|cython|scipy|pandas)" || echo " (none detected)"
else
echo -e "${RED}${NC} requirements.txt not found at $PROJECT_ROOT_DIR/requirements.txt"
fi
echo ""
# Test installing a simple package
echo "=== Test Installation ==="
echo "Testing pip with a simple package (setuptools)..."
if python3 -m pip install --break-system-packages --upgrade --quiet setuptools >/dev/null 2>&1; then
echo -e "${GREEN}${NC} Pip is working correctly"
else
echo -e "${RED}${NC} Pip installation test failed"
echo "Try: python3 -m pip install --break-system-packages --upgrade pip setuptools wheel"
fi
echo ""
# Check for common issues
echo "=== Common Issues Check ==="
# Check if running as root
if [ "$EUID" -eq 0 ]; then
echo -e "${YELLON}${NC} Running as root - ensure --break-system-packages flag is used"
else
echo -e "${GREEN}${NC} Not running as root (good for user installs)"
fi
# Check network connectivity
if ping -c 1 -W 3 pypi.org >/dev/null 2>&1; then
echo -e "${GREEN}${NC} Network connectivity to PyPI OK"
else
echo -e "${RED}${NC} Cannot reach pypi.org - check network connection"
fi
# Check for proxy issues
if [ -n "${HTTP_PROXY:-}" ] || [ -n "${HTTPS_PROXY:-}" ]; then
echo -e "${BLUE}${NC} Proxy configured: HTTP_PROXY=${HTTP_PROXY:-none}, HTTPS_PROXY=${HTTPS_PROXY:-none}"
else
echo -e "${GREEN}${NC} No proxy configured"
fi
echo ""
# Recommendations
echo "=== Recommendations ==="
echo ""
echo "If pip gets stuck on 'Preparing metadata (pyproject.toml)':"
echo ""
echo "1. Install/upgrade build tools:"
echo " sudo apt update && sudo apt install -y build-essential python3-dev python3-pip python3-setuptools python3-wheel cython3"
echo ""
echo "2. Upgrade pip and build tools:"
echo " python3 -m pip install --break-system-packages --upgrade pip setuptools wheel"
echo ""
echo "3. Try installing packages one at a time with verbose output:"
echo " python3 -m pip install --break-system-packages --no-cache-dir --verbose <package-name>"
echo ""
echo "4. For packages that build from source (like numpy), try:"
echo " - Install pre-built wheels: python3 -m pip install --break-system-packages --only-binary :all: <package>"
echo " - Or install via apt if available: sudo apt install python3-<package>"
echo ""
echo "5. Clear pip cache if corrupted:"
echo " python3 -m pip cache purge"
echo ""
echo "6. Check disk space - building packages requires temporary space"
echo " df -h"
echo ""
echo "7. For slow builds or out-of-memory kills, increase swap space."
echo " first_time_install.sh already adds temporary swap on low-memory devices;"
echo " this makes it permanent. Set CONF_MAXSWAP too - it defaults to 2048 and"
echo " silently clamps CONF_SWAPSIZE, so raising CONF_SWAPSIZE alone does nothing."
echo " sudo dphys-swapfile swapoff"
echo " sudo sed -i 's/^#\\?CONF_SWAPSIZE=.*/CONF_SWAPSIZE=2048/' /etc/dphys-swapfile"
echo " sudo sed -i 's/^#\\?CONF_MAXSWAP=.*/CONF_MAXSWAP=2048/' /etc/dphys-swapfile"
echo " sudo dphys-swapfile setup"
echo " sudo dphys-swapfile swapon"
echo ""
echo "8. Install packages with timeout to identify problematic ones:"
echo " timeout 600 python3 -m pip install --break-system-packages --no-cache-dir --verbose <package>"
echo ""
# Check which packages are already installed
echo "=== Currently Installed Packages ==="
echo "Checking which requirements are already satisfied..."
if [ -f "$PROJECT_ROOT_DIR/requirements.txt" ]; then
while IFS= read -r line || [ -n "$line" ]; do
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [[ "$line" =~ ^#.*$ ]] || [[ -z "$line" ]]; then
continue
fi
PACKAGE_NAME=$(echo "$line" | sed -E 's/[<>=!].*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr '[:upper:]' '[:lower:]')
# Try importing the package (basic check)
if python3 -c "import $PACKAGE_NAME" >/dev/null 2>&1; then
INSTALLED_VERSION=$(python3 -c "import $PACKAGE_NAME; print(getattr($PACKAGE_NAME, '__version__', 'unknown'))" 2>/dev/null || echo "unknown")
echo -e "${GREEN}${NC} $PACKAGE_NAME ($INSTALLED_VERSION)"
else
echo -e "${RED}${NC} $PACKAGE_NAME (not installed or import failed)"
fi
done < "$PROJECT_ROOT_DIR/requirements.txt" | head -20
echo ""
echo "(Showing first 20 packages - run full check with: python3 -m pip check)"
fi
echo ""
echo "=========================================="
echo "Diagnostic complete!"
echo "=========================================="