StarPilot
This commit is contained in:
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Collects high-signal diagnostics for UI freeze incidents on-device.
|
||||
# Usage:
|
||||
# ./scripts/debug_ui_freeze.sh
|
||||
# Optional env:
|
||||
# OUT_DIR=/data/media/0/realdata/ui_freeze_debug
|
||||
|
||||
OUT_DIR="${OUT_DIR:-/data/media/0/realdata/ui_freeze_debug}"
|
||||
TS="$(date +%Y%m%d_%H%M%S)"
|
||||
OUT_FILE="${OUT_DIR}/ui_freeze_${TS}.log"
|
||||
|
||||
mkdir -p "${OUT_DIR}"
|
||||
|
||||
{
|
||||
echo "=== UI Freeze Debug ==="
|
||||
echo "timestamp: $(date -Ins)"
|
||||
echo "hostname: $(hostname)"
|
||||
echo "kernel: $(uname -a)"
|
||||
echo
|
||||
|
||||
echo "=== Process Snapshot ==="
|
||||
ps -eo pid,ppid,stat,ni,etime,comm,args | grep -E "(manager.py|selfdrive.ui.ui|/selfdrive/ui/ui|weston|loggerd|encoderd|modeld|dmonitoringmodeld)" || true
|
||||
echo
|
||||
|
||||
echo "=== Manager State (tmux service output) ==="
|
||||
if command -v tmux >/dev/null 2>&1; then
|
||||
tmux capture-pane -pt comma:0 -S -220 2>/dev/null || true
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "=== UI Watchdog Files (/dev/shm/wd_*) ==="
|
||||
ls -l /dev/shm/wd_* 2>/dev/null || true
|
||||
echo
|
||||
|
||||
echo "=== UI PID + Watchdog Timestamp Delta ==="
|
||||
for ui_pid in $(pgrep -f "selfdrive.ui.ui|/selfdrive/ui/ui" 2>/dev/null || true); do
|
||||
echo "ui_pid=${ui_pid}"
|
||||
wd_file="/dev/shm/wd_${ui_pid}"
|
||||
if [[ -f "${wd_file}" ]]; then
|
||||
now_ns="$(python3 - <<'PY'
|
||||
import time
|
||||
print(int(time.monotonic_ns()))
|
||||
PY
|
||||
)"
|
||||
last_ns="$(python3 - "${wd_file}" <<'PY'
|
||||
import struct, sys
|
||||
with open(sys.argv[1], "rb") as f:
|
||||
print(struct.unpack("Q", f.read())[0])
|
||||
PY
|
||||
)"
|
||||
python3 - "${now_ns}" "${last_ns}" <<'PY'
|
||||
import sys
|
||||
now_ns = int(sys.argv[1]); last_ns = int(sys.argv[2])
|
||||
print(f"watchdog_age_s={(now_ns-last_ns)/1e9:.3f}")
|
||||
PY
|
||||
else
|
||||
echo "watchdog_file_missing=${wd_file}"
|
||||
fi
|
||||
echo
|
||||
done
|
||||
|
||||
echo "=== /data/log (recent ui/watchdog/weston/gpu errors) ==="
|
||||
grep -RinE "Watchdog timeout for ui|Process ui crashed|ui.*exit|weston|Wayland|EGL|gbm|drm|GPU|segfault|SIGSEGV|killed process|OOM|fatal" /data/log/swaglog* 2>/dev/null | tail -n 400 || true
|
||||
echo
|
||||
|
||||
echo "=== dmesg (recent high-signal lines) ==="
|
||||
dmesg 2>/dev/null | grep -Ei "drm|gpu|msm|kgsl|weston|wayland|segfault|oom|killed process|hang" | tail -n 300 || true
|
||||
echo
|
||||
|
||||
echo "=== systemd journal (weston / ui if available) ==="
|
||||
journalctl -n 300 --no-pager 2>/dev/null | grep -Ei "weston|wayland|ui|kgsl|drm|gpu|segfault|oom" || true
|
||||
echo
|
||||
|
||||
echo "=== open files for UI process(es) ==="
|
||||
for ui_pid in $(pgrep -f "selfdrive.ui.ui|/selfdrive/ui/ui" 2>/dev/null || true); do
|
||||
echo "--- lsof for pid ${ui_pid} ---"
|
||||
lsof -p "${ui_pid}" 2>/dev/null | tail -n 200 || true
|
||||
done
|
||||
echo
|
||||
|
||||
echo "=== End ==="
|
||||
} > "${OUT_FILE}" 2>&1
|
||||
|
||||
echo "Wrote: ${OUT_FILE}"
|
||||
Executable
+624
@@ -0,0 +1,624 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
# Make Docker Desktop binaries discoverable (docker + credential helpers) even
|
||||
# when the caller shell PATH is minimal.
|
||||
if [[ -d /Applications/Docker.app/Contents/Resources/bin ]]; then
|
||||
export PATH="/Applications/Docker.app/Contents/Resources/bin:${PATH}"
|
||||
fi
|
||||
|
||||
IMAGE_NAME="${COMMA_BUILD_IMAGE:-starpilot-larch64-builder:latest}"
|
||||
SYSROOT_DIR_DEFAULT="${ROOT_DIR}/.comma_sysroot"
|
||||
SYSROOT_DIR="${COMMA_SYSROOT_DIR:-${SYSROOT_DIR_DEFAULT}}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
scripts/laptop_device_build.sh doctor
|
||||
scripts/laptop_device_build.sh setup [device-host] [device-user] [ssh-port]
|
||||
scripts/laptop_device_build.sh setup-sysroot <device-host> [device-user] [ssh-port]
|
||||
scripts/laptop_device_build.sh setup-sysroot-agnos [manifest-path]
|
||||
scripts/laptop_device_build.sh build-image
|
||||
scripts/laptop_device_build.sh build [jobs] [scons args...]
|
||||
scripts/laptop_device_build.sh scons [scons args...]
|
||||
scripts/laptop_device_build.sh manager [jobs] [--no-build] [-- manager args...]
|
||||
scripts/laptop_device_build.sh shell
|
||||
|
||||
Modes:
|
||||
doctor Check host/container prerequisites for laptop-side device builds.
|
||||
setup One-time setup for laptop builds (venv + image + sysroot).
|
||||
setup-sysroot Sync required runtime/linker libs from a comma device over SSH.
|
||||
setup-sysroot-agnos Download AGNOS system image and extract sysroot locally.
|
||||
build-image Build the Linux/aarch64 container image used for device-target builds.
|
||||
build Run larch64 scons build in container and set prebuilt flag.
|
||||
scons Run raw scons in the larch64 container (no prebuilt touch).
|
||||
manager Run system/manager/manager.py in the larch64 container.
|
||||
shell Open an interactive shell in the build container with all mounts.
|
||||
|
||||
Environment overrides:
|
||||
COMMA_BUILD_IMAGE Container image tag (default: starpilot-larch64-builder:latest)
|
||||
COMMA_SYSROOT_DIR Sysroot location (default: ./.comma_sysroot)
|
||||
COMMA_AUTOSTART_DOCKER Auto-launch Docker Desktop on macOS when needed (default: 1)
|
||||
EOF
|
||||
}
|
||||
|
||||
err() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || err "Missing required command: $1"
|
||||
}
|
||||
|
||||
detect_engine() {
|
||||
local docker_cmd=""
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker_cmd="$(command -v docker)"
|
||||
elif [[ -x /Applications/Docker.app/Contents/Resources/bin/docker ]]; then
|
||||
docker_cmd="/Applications/Docker.app/Contents/Resources/bin/docker"
|
||||
fi
|
||||
|
||||
if [[ -n "${docker_cmd}" ]]; then
|
||||
# Needed for docker credential helpers (e.g. docker-credential-desktop).
|
||||
export PATH="$(dirname "${docker_cmd}"):${PATH}"
|
||||
if ! "${docker_cmd}" info >/dev/null 2>&1; then
|
||||
if [[ "$(uname -s)" == "Darwin" ]] && [[ "${COMMA_AUTOSTART_DOCKER:-1}" =~ ^(1|true|yes|on)$ ]]; then
|
||||
if [[ -d /Applications/Docker.app ]] && command -v open >/dev/null 2>&1; then
|
||||
echo "Docker daemon is not running. Launching Docker Desktop..." >&2
|
||||
open -g -a Docker >/dev/null 2>&1 || open -a Docker >/dev/null 2>&1 || true
|
||||
for ((i=0; i<300; i++)); do
|
||||
if "${docker_cmd}" info >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
fi
|
||||
if ! "${docker_cmd}" info >/dev/null 2>&1; then
|
||||
err "Docker CLI found, but Docker daemon is not running. Start Docker Desktop (open -a Docker) and retry."
|
||||
fi
|
||||
fi
|
||||
echo "${docker_cmd}"
|
||||
return
|
||||
fi
|
||||
if command -v podman >/dev/null 2>&1; then
|
||||
local podman_cmd
|
||||
podman_cmd="$(command -v podman)"
|
||||
if ! "${podman_cmd}" info >/dev/null 2>&1; then
|
||||
err "Podman CLI found, but Podman service is not running."
|
||||
fi
|
||||
echo "${podman_cmd}"
|
||||
return
|
||||
fi
|
||||
err "No container runtime found (install docker or podman)."
|
||||
}
|
||||
|
||||
ensure_image_exists() {
|
||||
local engine="$1"
|
||||
if ! "${engine}" image inspect "${IMAGE_NAME}" >/dev/null 2>&1; then
|
||||
echo "Container image ${IMAGE_NAME} not found. Building it now..."
|
||||
"${engine}" build --pull --platform linux/arm64 -f tools/laptop_device_build/Dockerfile -t "${IMAGE_NAME}" .
|
||||
fi
|
||||
assert_image_arch "${engine}"
|
||||
}
|
||||
|
||||
assert_image_arch() {
|
||||
local engine="$1"
|
||||
local arch
|
||||
arch="$("${engine}" image inspect "${IMAGE_NAME}" --format '{{.Architecture}}' 2>/dev/null || true)"
|
||||
if [[ "${arch}" != "arm64" ]]; then
|
||||
err "Container image ${IMAGE_NAME} is '${arch:-unknown}', expected 'arm64'. Rebuild with: ${engine} build --pull --platform linux/arm64 -f tools/laptop_device_build/Dockerfile -t ${IMAGE_NAME} ."
|
||||
fi
|
||||
}
|
||||
|
||||
assert_runtime_machine() {
|
||||
local engine="$1"
|
||||
local machine
|
||||
machine="$("${engine}" run --rm --platform linux/arm64 "${IMAGE_NAME}" bash -lc "readelf -h \$(command -v python3) | awk -F: '/Machine/ {gsub(/^ +/, \"\", \$2); print \$2}'" 2>/dev/null || true)"
|
||||
if [[ "${machine}" != "AArch64" ]]; then
|
||||
err "Container Python runtime is '${machine:-unknown}', expected 'AArch64'. This image can still build device artifacts, but cannot run manager with larch64 Python extensions. Rebuild from an arm64-capable base image before using 'manager' mode."
|
||||
fi
|
||||
}
|
||||
|
||||
default_jobs() {
|
||||
if command -v nproc >/dev/null 2>&1; then
|
||||
nproc
|
||||
elif command -v sysctl >/dev/null 2>&1; then
|
||||
sysctl -n hw.ncpu
|
||||
else
|
||||
echo 8
|
||||
fi
|
||||
}
|
||||
|
||||
build_container_image() {
|
||||
local engine
|
||||
engine="$(detect_engine)"
|
||||
"${engine}" build --pull --platform linux/arm64 \
|
||||
-f tools/laptop_device_build/Dockerfile \
|
||||
-t "${IMAGE_NAME}" .
|
||||
assert_image_arch "${engine}"
|
||||
}
|
||||
|
||||
sanitize_sysroot_headers() {
|
||||
# Device /usr/local/include can include older capnp/kj headers that conflict
|
||||
# with generated cereal headers in this tree. Keep ffmpeg headers, drop capnp.
|
||||
local removed=0
|
||||
local path=""
|
||||
for path in \
|
||||
"${SYSROOT_DIR}/usr/local/include/capnp" \
|
||||
"${SYSROOT_DIR}/usr/local/include/kj"; do
|
||||
if [[ -e "${path}" ]]; then
|
||||
rm -rf "${path}"
|
||||
removed=1
|
||||
fi
|
||||
done
|
||||
if [[ "${removed}" -eq 1 ]]; then
|
||||
echo "Sanitized sysroot headers: removed capnp/kj from usr/local/include"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_sysroot_layout() {
|
||||
[[ -d "${SYSROOT_DIR}/usr/local/lib" ]] || err "Missing ${SYSROOT_DIR}/usr/local/lib. Run setup-sysroot first."
|
||||
[[ -d "${SYSROOT_DIR}/usr/local/include" ]] || err "Missing ${SYSROOT_DIR}/usr/local/include. Run setup-sysroot first."
|
||||
[[ -d "${SYSROOT_DIR}/lib/aarch64-linux-gnu" ]] || err "Missing ${SYSROOT_DIR}/lib/aarch64-linux-gnu. Run setup-sysroot first."
|
||||
[[ -d "${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu" ]] || err "Missing ${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu. Run setup-sysroot first."
|
||||
[[ -d "${SYSROOT_DIR}/usr/include" ]] || err "Missing ${SYSROOT_DIR}/usr/include. Run setup-sysroot first."
|
||||
[[ -d "${SYSROOT_DIR}/system/vendor/lib64" ]] || err "Missing ${SYSROOT_DIR}/system/vendor/lib64. Run setup-sysroot first."
|
||||
sanitize_sysroot_headers
|
||||
repair_sysroot_linker
|
||||
}
|
||||
|
||||
is_aarch64_elf() {
|
||||
local path="${1:-}"
|
||||
[[ -f "${path}" ]] || return 1
|
||||
local desc
|
||||
desc="$(file -b "${path}" 2>/dev/null || true)"
|
||||
[[ "${desc}" == *"ELF 64-bit LSB"* && "${desc}" == *"ARM aarch64"* ]]
|
||||
}
|
||||
|
||||
repair_sysroot_linker() {
|
||||
local lib_ld="${SYSROOT_DIR}/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1"
|
||||
local usr_ld="${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1"
|
||||
|
||||
# Some sysroot extraction paths can create this as an empty directory.
|
||||
if [[ -d "${lib_ld}" ]]; then
|
||||
rm -rf "${lib_ld}"
|
||||
fi
|
||||
|
||||
# Ensure lib-path loader exists (symlink to usr/lib loader is fine).
|
||||
if [[ ! -e "${lib_ld}" && -f "${usr_ld}" ]]; then
|
||||
mkdir -p "$(dirname "${lib_ld}")"
|
||||
ln -s "../../usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1" "${lib_ld}"
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_ldso_source() {
|
||||
local lib_ld="${SYSROOT_DIR}/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1"
|
||||
local usr_ld="${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1"
|
||||
|
||||
repair_sysroot_linker
|
||||
|
||||
if is_aarch64_elf "${lib_ld}"; then
|
||||
echo "${lib_ld}"
|
||||
return 0
|
||||
fi
|
||||
if is_aarch64_elf "${usr_ld}"; then
|
||||
echo "${usr_ld}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
err "Missing valid aarch64 dynamic loader in sysroot. Expected one of: ${lib_ld} or ${usr_ld}. Re-run setup-sysroot."
|
||||
}
|
||||
|
||||
scrub_mixed_arch_artifacts() {
|
||||
# Desktop launchers can leave macOS objects with the same paths as larch64 outputs.
|
||||
# Remove known cross-target extension intermediates so device builds always relink.
|
||||
rm -f \
|
||||
"${ROOT_DIR}/cereal/messaging/"*.o \
|
||||
"${ROOT_DIR}/cereal/messaging/bridge" \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/"*.os \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/"*.o \
|
||||
"${ROOT_DIR}/msgq_repo/libmsgq.a" \
|
||||
"${ROOT_DIR}/msgq_repo/libvisionipc.a" \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/ipc_pyx.so" \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/visionipc/"*.os \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/visionipc/"*.o \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/visionipc/visionipc_pyx.so" \
|
||||
"${ROOT_DIR}/msgq/ipc_pyx.so" \
|
||||
"${ROOT_DIR}/msgq/visionipc/visionipc_pyx.so" \
|
||||
"${ROOT_DIR}/common/params_pyx.o" \
|
||||
"${ROOT_DIR}/common/params_pyx.so" \
|
||||
"${ROOT_DIR}/common/transformations/transformations.o" \
|
||||
"${ROOT_DIR}/common/transformations/transformations.so" \
|
||||
"${ROOT_DIR}/selfdrive/modeld/models/commonmodel_pyx.o" \
|
||||
"${ROOT_DIR}/selfdrive/modeld/models/commonmodel_pyx.so" \
|
||||
"${ROOT_DIR}/frogpilot/tinygrad_modeld/models/commonmodel_pyx.o" \
|
||||
"${ROOT_DIR}/frogpilot/tinygrad_modeld/models/commonmodel_pyx.so"
|
||||
}
|
||||
|
||||
sync_sysroot_from_device() {
|
||||
local host="${1:-}"
|
||||
local user="${2:-comma}"
|
||||
local port="${3:-22}"
|
||||
[[ -n "${host}" ]] || err "setup-sysroot requires <device-host>."
|
||||
|
||||
require_cmd rsync
|
||||
mkdir -p "${SYSROOT_DIR}"
|
||||
local ssh_cmd="ssh -p ${port} -o StrictHostKeyChecking=accept-new"
|
||||
|
||||
rsync -a --delete -e "${ssh_cmd}" "${user}@${host}:/usr/local/lib/" "${SYSROOT_DIR}/usr/local/lib/"
|
||||
rsync -a --delete -e "${ssh_cmd}" "${user}@${host}:/usr/local/include/" "${SYSROOT_DIR}/usr/local/include/"
|
||||
rsync -a --delete -e "${ssh_cmd}" "${user}@${host}:/lib/aarch64-linux-gnu/" "${SYSROOT_DIR}/lib/aarch64-linux-gnu/"
|
||||
rsync -a --delete -e "${ssh_cmd}" "${user}@${host}:/usr/lib/aarch64-linux-gnu/" "${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu/"
|
||||
rsync -a --delete -e "${ssh_cmd}" "${user}@${host}:/usr/include/" "${SYSROOT_DIR}/usr/include/"
|
||||
|
||||
local vendor_src=""
|
||||
for candidate in /system/vendor/lib64 /vendor/lib64; do
|
||||
if ssh -p "${port}" -o StrictHostKeyChecking=accept-new "${user}@${host}" "test -d '${candidate}'"; then
|
||||
vendor_src="${candidate}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "${vendor_src}" ]]; then
|
||||
rsync -a --delete -e "${ssh_cmd}" "${user}@${host}:${vendor_src}/" "${SYSROOT_DIR}/system/vendor/lib64/"
|
||||
else
|
||||
local vendor_dst="${SYSROOT_DIR}/system/vendor/lib64"
|
||||
echo "Warning: no vendor lib64 dir found on device; falling back to usr/lib/aarch64-linux-gnu"
|
||||
if [[ -L "${vendor_dst}" || -f "${vendor_dst}" ]]; then
|
||||
rm -f "${vendor_dst}"
|
||||
elif [[ -d "${vendor_dst}" ]]; then
|
||||
rm -rf "${vendor_dst}"
|
||||
fi
|
||||
mkdir -p "${SYSROOT_DIR}/system/vendor"
|
||||
ln -s ../../usr/lib/aarch64-linux-gnu "${vendor_dst}"
|
||||
fi
|
||||
|
||||
sanitize_sysroot_headers
|
||||
|
||||
echo "Sysroot synced to: ${SYSROOT_DIR}"
|
||||
}
|
||||
|
||||
setup_sysroot_from_agnos() {
|
||||
local manifest="${1:-system/hardware/tici/agnos.json}"
|
||||
local engine
|
||||
engine="$(detect_engine)"
|
||||
ensure_image_exists "${engine}"
|
||||
mkdir -p "${SYSROOT_DIR}" "${ROOT_DIR}/.cache/agnos"
|
||||
|
||||
"${engine}" run --rm --platform linux/arm64 \
|
||||
-v "${ROOT_DIR}:/work" \
|
||||
-v "${SYSROOT_DIR}:/opt/tici-sysroot" \
|
||||
-v "${ROOT_DIR}/.cache:/work/.cache" \
|
||||
-w /work \
|
||||
"${IMAGE_NAME}" \
|
||||
python3 tools/laptop_device_build/extract_sysroot_from_agnos.py \
|
||||
--manifest "${manifest}" \
|
||||
--output-dir /opt/tici-sysroot \
|
||||
--cache-dir /work/.cache/agnos
|
||||
}
|
||||
|
||||
run_larch64_scons() {
|
||||
local jobs="$1"
|
||||
shift || true
|
||||
local engine
|
||||
local started_at
|
||||
engine="$(detect_engine)"
|
||||
ensure_image_exists "${engine}"
|
||||
ensure_sysroot_layout
|
||||
scrub_mixed_arch_artifacts
|
||||
started_at="$(date +%s)"
|
||||
|
||||
echo "==> Starting larch64 scons build"
|
||||
echo " jobs: ${jobs}"
|
||||
echo " note: warp artifact precompile can take several minutes on first run"
|
||||
|
||||
mkdir -p "${ROOT_DIR}/.cache/scons"
|
||||
|
||||
local extra_args=""
|
||||
local jobs_prefix="-j${jobs}"
|
||||
if [[ "$#" -gt 0 ]]; then
|
||||
for arg in "$@"; do
|
||||
case "${arg}" in
|
||||
-j|--jobs|-j*|--jobs=*)
|
||||
jobs_prefix=""
|
||||
;;
|
||||
esac
|
||||
extra_args+=" $(printf '%q' "${arg}")"
|
||||
done
|
||||
fi
|
||||
|
||||
local cmd
|
||||
cmd="$(cat <<EOF
|
||||
set -euo pipefail
|
||||
export UV_CACHE_DIR=/tmp/uv-cache
|
||||
export SCONS_CACHE=/work/.cache/scons
|
||||
export SP_FORCE_TICI=1
|
||||
export SP_FORCE_ARCH=larch64
|
||||
export SP_TICI_SYSROOT=/opt/tici-sysroot
|
||||
export SP_BUILD_WARP_ARTIFACTS="\${SP_BUILD_WARP_ARTIFACTS:-0}"
|
||||
export SP_SKIP_DM_TINYGRAD_PKL="\${SP_SKIP_DM_TINYGRAD_PKL:-1}"
|
||||
export LD_LIBRARY_PATH=/usr/local/lib:/usr/lib/aarch64-linux-gnu:/lib/aarch64-linux-gnu:/opt/tici-sysroot/usr/local/lib:/opt/tici-sysroot/usr/lib/aarch64-linux-gnu:/opt/tici-sysroot/lib/aarch64-linux-gnu:/system/vendor/lib64:\${LD_LIBRARY_PATH:-}
|
||||
export TMPDIR=/tmp
|
||||
mkdir -p "\${UV_CACHE_DIR}"
|
||||
UV_PROJECT_ENVIRONMENT=/work/.venv-linux-arm64 uv sync --frozen --all-extras
|
||||
source /work/.venv-linux-arm64/bin/activate
|
||||
CACHE_FLAG="--cache-disable"
|
||||
if [[ "\${SP_ENABLE_SCONS_CACHE:-0}" =~ ^(1|true|yes|on)$ ]]; then
|
||||
CACHE_FLAG=""
|
||||
fi
|
||||
scons ${jobs_prefix} \${CACHE_FLAG}${extra_args}
|
||||
EOF
|
||||
)"
|
||||
|
||||
"${engine}" run --rm --platform linux/arm64 \
|
||||
-v "${ROOT_DIR}:/work" \
|
||||
-v "${SYSROOT_DIR}:/opt/tici-sysroot:ro" \
|
||||
-v "${SYSROOT_DIR}/system/vendor/lib64:/system/vendor/lib64:ro" \
|
||||
-v "${ROOT_DIR}/.cache:/work/.cache" \
|
||||
-v "${ROOT_DIR}/.cache/scons:/data/scons_cache" \
|
||||
-w /work \
|
||||
"${IMAGE_NAME}" bash -lc "${cmd}"
|
||||
|
||||
echo "==> larch64 scons completed in $(( $(date +%s) - started_at ))s"
|
||||
}
|
||||
|
||||
setup_host_venv() {
|
||||
if [[ ! -f "${ROOT_DIR}/.venv/bin/activate" ]]; then
|
||||
if [[ -x "${ROOT_DIR}/tools/install_python_dependencies.sh" ]]; then
|
||||
(cd "${ROOT_DIR}" && tools/install_python_dependencies.sh)
|
||||
else
|
||||
err "Missing host .venv and installer script."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
setup_all() {
|
||||
local host="${1:-}"
|
||||
local user="${2:-comma}"
|
||||
local port="${3:-22}"
|
||||
|
||||
setup_host_venv
|
||||
build_container_image
|
||||
|
||||
if [[ -n "${host}" ]]; then
|
||||
sync_sysroot_from_device "${host}" "${user}" "${port}"
|
||||
else
|
||||
setup_sysroot_from_agnos "system/hardware/tici/agnos.json"
|
||||
fi
|
||||
}
|
||||
|
||||
run_larch64_build() {
|
||||
local jobs="${1:-$(default_jobs)}"
|
||||
shift || true
|
||||
echo "==> Build pass 1/2: full project build"
|
||||
run_larch64_scons "${jobs}" "$@"
|
||||
# Ensure prebuilt runtime compatibility probes can import these modules.
|
||||
# scrub_mixed_arch_artifacts clears them at the start of each run, so
|
||||
# targeted builds must always regenerate this core set.
|
||||
# Do NOT regenerate dmonitoring_model_tinygrad.pkl on laptop builds:
|
||||
# it is backend-captured and should come from device/QCOM-compatible artifacts.
|
||||
echo "==> Build pass 2/2: required runtime artifacts"
|
||||
run_larch64_scons "${jobs}" \
|
||||
selfdrive/modeld/models/dmonitoring_model_metadata.pkl \
|
||||
selfdrive/modeld/models/driving_vision_metadata.pkl \
|
||||
selfdrive/modeld/models/driving_policy_metadata.pkl \
|
||||
selfdrive/modeld/models/driving_vision_tinygrad.pkl \
|
||||
selfdrive/modeld/models/driving_policy_tinygrad.pkl \
|
||||
rednose/helpers/ekf_sym_pyx.so \
|
||||
common/params_pyx.so \
|
||||
common/transformations/transformations.so \
|
||||
selfdrive/pandad/pandad_api_impl.so \
|
||||
selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so \
|
||||
selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so \
|
||||
selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so \
|
||||
selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so \
|
||||
selfdrive/modeld/models/commonmodel_pyx.so \
|
||||
cereal/messaging/bridge \
|
||||
msgq_repo/msgq/ipc_pyx.so \
|
||||
msgq_repo/msgq/visionipc/visionipc_pyx.so
|
||||
touch "${ROOT_DIR}/prebuilt"
|
||||
}
|
||||
|
||||
manager_artifacts_ready() {
|
||||
[[ -f "${ROOT_DIR}/msgq/ipc_pyx.so" ]] &&
|
||||
[[ -f "${ROOT_DIR}/msgq/visionipc/visionipc_pyx.so" ]] &&
|
||||
[[ -f "${ROOT_DIR}/common/params_pyx.so" ]] &&
|
||||
[[ -f "${ROOT_DIR}/selfdrive/pandad/pandad_api_impl.so" ]] &&
|
||||
[[ -f "${ROOT_DIR}/selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" ]] &&
|
||||
[[ -f "${ROOT_DIR}/selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so" ]] &&
|
||||
[[ -f "${ROOT_DIR}/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" ]] &&
|
||||
[[ -f "${ROOT_DIR}/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so" ]] &&
|
||||
[[ -f "${ROOT_DIR}/selfdrive/ui/ui" ]]
|
||||
}
|
||||
|
||||
run_manager() {
|
||||
local jobs="$(default_jobs)"
|
||||
local auto_build=1
|
||||
local manager_args=()
|
||||
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "${1}" in
|
||||
--no-build)
|
||||
auto_build=0
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift || true
|
||||
manager_args=("$@")
|
||||
break
|
||||
;;
|
||||
*)
|
||||
manager_args+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "${auto_build}" -eq 1 ]] && ! manager_artifacts_ready; then
|
||||
echo "Missing manager runtime artifacts. Running device-target build first..."
|
||||
run_larch64_build "${jobs}"
|
||||
fi
|
||||
|
||||
local engine
|
||||
engine="$(detect_engine)"
|
||||
ensure_image_exists "${engine}"
|
||||
assert_runtime_machine "${engine}"
|
||||
ensure_sysroot_layout
|
||||
mkdir -p "${ROOT_DIR}/.cache/scons"
|
||||
|
||||
local manager_args_q=""
|
||||
if [[ "${#manager_args[@]}" -gt 0 ]]; then
|
||||
for arg in "${manager_args[@]}"; do
|
||||
manager_args_q+=" $(printf '%q' "${arg}")"
|
||||
done
|
||||
fi
|
||||
|
||||
local cmd
|
||||
cmd="$(cat <<EOF
|
||||
set -euo pipefail
|
||||
export UV_CACHE_DIR=/tmp/uv-cache
|
||||
export SP_FORCE_TICI=1
|
||||
export SP_FORCE_ARCH=larch64
|
||||
export SP_TICI_SYSROOT=/opt/tici-sysroot
|
||||
export SP_BUILD_WARP_ARTIFACTS="\${SP_BUILD_WARP_ARTIFACTS:-0}"
|
||||
export LD_LIBRARY_PATH=/usr/local/lib:/usr/lib/aarch64-linux-gnu:/lib/aarch64-linux-gnu:/opt/tici-sysroot/usr/local/lib:/opt/tici-sysroot/usr/lib/aarch64-linux-gnu:/opt/tici-sysroot/lib/aarch64-linux-gnu:/system/vendor/lib64:\${LD_LIBRARY_PATH:-}
|
||||
export TMPDIR=/tmp
|
||||
mkdir -p "\${UV_CACHE_DIR}"
|
||||
UV_PROJECT_ENVIRONMENT=/work/.venv-linux-arm64 uv sync --frozen --all-extras
|
||||
source /work/.venv-linux-arm64/bin/activate
|
||||
cd /work
|
||||
python3 system/manager/manager.py${manager_args_q}
|
||||
EOF
|
||||
)"
|
||||
|
||||
"${engine}" run --rm --platform linux/arm64 \
|
||||
-v "${ROOT_DIR}:/work" \
|
||||
-v "${SYSROOT_DIR}:/opt/tici-sysroot:ro" \
|
||||
-v "${SYSROOT_DIR}/system/vendor/lib64:/system/vendor/lib64:ro" \
|
||||
-v "${ROOT_DIR}/.cache:/work/.cache" \
|
||||
-v "${ROOT_DIR}/.cache/scons:/data/scons_cache" \
|
||||
-w /work \
|
||||
"${IMAGE_NAME}" bash -lc "${cmd}"
|
||||
}
|
||||
|
||||
run_shell() {
|
||||
local engine
|
||||
engine="$(detect_engine)"
|
||||
ensure_image_exists "${engine}"
|
||||
ensure_sysroot_layout
|
||||
"${engine}" run --rm -it --platform linux/arm64 \
|
||||
-v "${ROOT_DIR}:/work" \
|
||||
-v "${SYSROOT_DIR}:/opt/tici-sysroot:ro" \
|
||||
-v "${SYSROOT_DIR}/system/vendor/lib64:/system/vendor/lib64:ro" \
|
||||
-v "${ROOT_DIR}/.cache:/work/.cache" \
|
||||
-v "${ROOT_DIR}/.cache/scons:/data/scons_cache" \
|
||||
-w /work \
|
||||
"${IMAGE_NAME}" bash
|
||||
}
|
||||
|
||||
doctor() {
|
||||
local failed=0
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
echo "OK: docker found"
|
||||
elif command -v podman >/dev/null 2>&1; then
|
||||
echo "OK: podman found"
|
||||
else
|
||||
echo "FAIL: install docker or podman"
|
||||
failed=1
|
||||
fi
|
||||
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
echo "OK: rsync found"
|
||||
else
|
||||
echo "WARN: rsync missing (needed only for setup-sysroot from a physical device)"
|
||||
fi
|
||||
|
||||
if [[ -d "${SYSROOT_DIR}" ]]; then
|
||||
if [[ -d "${SYSROOT_DIR}/usr/local/lib" && -d "${SYSROOT_DIR}/usr/local/include" && -d "${SYSROOT_DIR}/lib/aarch64-linux-gnu" && -d "${SYSROOT_DIR}/usr/lib/aarch64-linux-gnu" && -d "${SYSROOT_DIR}/usr/include" && -d "${SYSROOT_DIR}/system/vendor/lib64" ]]; then
|
||||
echo "OK: sysroot present at ${SYSROOT_DIR}"
|
||||
else
|
||||
echo "WARN: sysroot dir exists but is incomplete (${SYSROOT_DIR})"
|
||||
failed=1
|
||||
fi
|
||||
else
|
||||
echo "WARN: sysroot missing (${SYSROOT_DIR})"
|
||||
failed=1
|
||||
fi
|
||||
|
||||
if [[ -f "${ROOT_DIR}/.venv/bin/activate" ]]; then
|
||||
echo "OK: host .venv present"
|
||||
else
|
||||
echo "WARN: host .venv missing (run scripts/laptop_device_build.sh setup)"
|
||||
failed=1
|
||||
fi
|
||||
|
||||
if [[ "${failed}" -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local mode="${1:-}"
|
||||
case "${mode}" in
|
||||
doctor)
|
||||
doctor
|
||||
;;
|
||||
setup)
|
||||
shift || true
|
||||
setup_all "${1:-}" "${2:-comma}" "${3:-22}"
|
||||
;;
|
||||
setup-sysroot)
|
||||
shift
|
||||
sync_sysroot_from_device "${1:-}" "${2:-comma}" "${3:-22}"
|
||||
;;
|
||||
setup-sysroot-agnos)
|
||||
shift || true
|
||||
setup_sysroot_from_agnos "${1:-system/hardware/tici/agnos.json}"
|
||||
;;
|
||||
build-image)
|
||||
build_container_image
|
||||
;;
|
||||
build)
|
||||
shift || true
|
||||
local jobs_arg="${1:-$(default_jobs)}"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
shift || true
|
||||
else
|
||||
jobs_arg="$(default_jobs)"
|
||||
fi
|
||||
run_larch64_build "${jobs_arg}" "$@"
|
||||
;;
|
||||
scons)
|
||||
shift || true
|
||||
run_larch64_scons "$(default_jobs)" "$@"
|
||||
;;
|
||||
manager)
|
||||
shift || true
|
||||
run_manager "$@"
|
||||
;;
|
||||
shell)
|
||||
run_shell
|
||||
;;
|
||||
""|-h|--help|help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+302
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
if [[ ! -f .venv/bin/activate ]]; then
|
||||
echo "Missing .venv. Run tools/install_python_dependencies.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
default_jobs() {
|
||||
if command -v nproc >/dev/null 2>&1; then
|
||||
nproc
|
||||
elif command -v sysctl >/dev/null 2>&1; then
|
||||
sysctl -n hw.ncpu
|
||||
else
|
||||
echo 8
|
||||
fi
|
||||
}
|
||||
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
if [[ -d /opt/homebrew/bin ]]; then
|
||||
export PATH="/opt/homebrew/bin:${PATH}"
|
||||
fi
|
||||
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
# Ensure desktop host extension builds stay on Apple toolchain.
|
||||
export CC="/usr/bin/clang"
|
||||
export CXX="/usr/bin/clang++"
|
||||
fi
|
||||
unset CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH CPPFLAGS CFLAGS CXXFLAGS LDFLAGS
|
||||
|
||||
PY_BIN="${ROOT_DIR}/.venv/bin/python3"
|
||||
if [[ ! -x "${PY_BIN}" ]]; then
|
||||
echo "Missing ${PY_BIN}. Run tools/install_python_dependencies.sh first."
|
||||
exit 1
|
||||
fi
|
||||
export PATH="${ROOT_DIR}/.venv/bin:${PATH}"
|
||||
|
||||
export PYTHONPATH="${ROOT_DIR}:${ROOT_DIR}/frogpilot/third_party"
|
||||
for d in "${ROOT_DIR}"/*_repo; do [[ -d "$d" ]] && export PYTHONPATH="${PYTHONPATH}:$d"; done
|
||||
[[ -d "${ROOT_DIR}/third_party/acados" ]] && export PYTHONPATH="${PYTHONPATH}:${ROOT_DIR}/third_party/acados"
|
||||
export BIG=0
|
||||
export NOBOARD=1
|
||||
export SIMULATION=1
|
||||
export SKIP_FW_QUERY=1
|
||||
export USE_WEBCAM=1
|
||||
|
||||
backup_dir="$(mktemp -d /tmp/frogpilot_c4_ui_backup.XXXXXX)"
|
||||
backup_manifest="${backup_dir}/.artifact_manifest"
|
||||
PRE_TRACKED_DIRTY="$(mktemp /tmp/frogpilot_c4_pretracked.XXXXXX)"
|
||||
POST_TRACKED_DIRTY="$(mktemp /tmp/frogpilot_c4_posttracked.XXXXXX)"
|
||||
FAKE_WIFI_PID=""
|
||||
|
||||
runtime_artifacts=(
|
||||
"common/params_pyx.so"
|
||||
"msgq/ipc_pyx.so"
|
||||
"msgq/visionipc/visionipc_pyx.so"
|
||||
"common/transformations/transformations.so"
|
||||
)
|
||||
|
||||
collect_tracked_dirty() {
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
if ! git -C "${ROOT_DIR}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
git -C "${ROOT_DIR}" status --porcelain --untracked-files=no | sed -E 's/^.. //'
|
||||
}
|
||||
|
||||
restore_runtime_artifacts() {
|
||||
if [[ -d "${backup_dir}" ]]; then
|
||||
if [[ -f "${backup_manifest}" ]]; then
|
||||
while IFS= read -r rel; do
|
||||
[[ -z "${rel}" ]] && continue
|
||||
if [[ -f "${backup_dir}/${rel}" ]]; then
|
||||
cp -f "${backup_dir}/${rel}" "${ROOT_DIR}/${rel}"
|
||||
else
|
||||
rm -f "${ROOT_DIR}/${rel}"
|
||||
fi
|
||||
done < "${backup_manifest}"
|
||||
fi
|
||||
rm -rf "${backup_dir}"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_new_tracked_changes() {
|
||||
if ! collect_tracked_dirty > "${POST_TRACKED_DIRTY}"; then
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r path; do
|
||||
[[ -z "${path}" ]] && continue
|
||||
if ! grep -Fxq "${path}" "${PRE_TRACKED_DIRTY}"; then
|
||||
git -C "${ROOT_DIR}" checkout -- "${path}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
done < "${POST_TRACKED_DIRTY}"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${FAKE_WIFI_PID}" ]]; then
|
||||
kill "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
|
||||
wait "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
restore_runtime_artifacts
|
||||
restore_new_tracked_changes
|
||||
rm -f "${PRE_TRACKED_DIRTY}" "${POST_TRACKED_DIRTY}"
|
||||
}
|
||||
|
||||
collect_tracked_dirty > "${PRE_TRACKED_DIRTY}" || true
|
||||
trap cleanup EXIT
|
||||
|
||||
backup_if_elf() {
|
||||
local rel="$1"
|
||||
local src="${ROOT_DIR}/${rel}"
|
||||
echo "${rel}" >> "${backup_manifest}"
|
||||
if [[ -f "${src}" ]]; then
|
||||
mkdir -p "${backup_dir}/$(dirname "${rel}")"
|
||||
cp -f "${src}" "${backup_dir}/${rel}"
|
||||
fi
|
||||
}
|
||||
|
||||
for rel in "${runtime_artifacts[@]}"; do
|
||||
backup_if_elf "${rel}"
|
||||
done
|
||||
|
||||
archive_is_aarch64_elf() {
|
||||
local path="$1"
|
||||
[[ -f "${path}" ]] || return 1
|
||||
if command -v readelf >/dev/null 2>&1; then
|
||||
readelf -h "${path}" 2>/dev/null | grep -iq "aarch64"
|
||||
else
|
||||
objdump -a "${path}" 2>/dev/null | head -n 12 | grep -iqE "elf64-littleaarch64|aarch64"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_if_elf() {
|
||||
local rel="$1"
|
||||
local path="${ROOT_DIR}/${rel}"
|
||||
if [[ -f "${path}" ]] && file "${path}" | grep -q "ELF"; then
|
||||
rm -f "${path}"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_common_host_artifacts() {
|
||||
if archive_is_aarch64_elf "${ROOT_DIR}/common/libcommon.a"; then
|
||||
rm -f \
|
||||
"${ROOT_DIR}/common/libcommon.a" \
|
||||
"${ROOT_DIR}/common/"*.o \
|
||||
"${ROOT_DIR}/common/params_pyx.o" \
|
||||
"${ROOT_DIR}/common/transformations/transformations.o"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_msgq_host_artifacts() {
|
||||
# Clear mixed-arch Python extension objects from both msgq trees. These
|
||||
# commonly conflict after switching between ./build (larch64) and ./c4 (macOS).
|
||||
remove_if_elf "msgq/ipc_pyx.o"
|
||||
remove_if_elf "msgq/visionipc/visionipc_pyx.o"
|
||||
remove_if_elf "msgq_repo/msgq/ipc_pyx.o"
|
||||
remove_if_elf "msgq_repo/msgq/visionipc/visionipc_pyx.o"
|
||||
|
||||
if archive_is_aarch64_elf "${ROOT_DIR}/msgq_repo/libmsgq.a" || archive_is_aarch64_elf "${ROOT_DIR}/msgq_repo/libvisionipc.a"; then
|
||||
rm -f \
|
||||
"${ROOT_DIR}/msgq_repo/libmsgq.a" \
|
||||
"${ROOT_DIR}/msgq_repo/libvisionipc.a" \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/"*.os \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/visionipc/"*.os \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/ipc_pyx.o" \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/visionipc/visionipc_pyx.o"
|
||||
fi
|
||||
}
|
||||
|
||||
python_ui_runtime_ok() {
|
||||
"${PY_BIN}" - <<'PY'
|
||||
import pyray # noqa: F401
|
||||
import openpilot.common.params_pyx # noqa: F401
|
||||
import openpilot.common.transformations.transformations # noqa: F401
|
||||
import msgq.ipc_pyx # noqa: F401
|
||||
import msgq.visionipc.visionipc_pyx # noqa: F401
|
||||
PY
|
||||
}
|
||||
|
||||
sync_deps() {
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
UV_PROJECT_ENVIRONMENT=.venv uv sync --frozen --all-extras
|
||||
else
|
||||
echo "Missing uv. Install dependencies with tools/install_python_dependencies.sh."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sync_raylib() {
|
||||
if "${PY_BIN}" - <<'PY' >/dev/null 2>&1
|
||||
import pyray # noqa: F401
|
||||
PY
|
||||
then
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
UV_PROJECT_ENVIRONMENT=.venv uv pip install "raylib<5.5.0.3"
|
||||
else
|
||||
echo "Missing uv. Install dependencies with tools/install_python_dependencies.sh."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_scons() {
|
||||
local jobs_arg="$1"
|
||||
shift || true
|
||||
local scons_bin="${ROOT_DIR}/.venv/bin/scons"
|
||||
if [[ -x "${scons_bin}" ]]; then
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${scons_bin}" -j"${jobs_arg}" "$@"
|
||||
elif "${PY_BIN}" -m SCons --version >/dev/null 2>&1; then
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${PY_BIN}" -m SCons -j"${jobs_arg}" "$@"
|
||||
else
|
||||
echo "SCons not found in .venv after sync."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
kill_stale_c4_ui() {
|
||||
pkill -f "selfdrive/ui/ui.py" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
start_fake_wifi() {
|
||||
if [[ ! "${SP_C4_FAKE_WIFI:-1}" =~ ^(1|true|yes|on)$ ]]; then
|
||||
export SP_ALLOW_DESKTOP_FAKE_WIFI=0
|
||||
return
|
||||
fi
|
||||
|
||||
export SP_ALLOW_DESKTOP_FAKE_WIFI=1
|
||||
"${PY_BIN}" selfdrive/debug/fake_wifi.py --network wifi --strength great --interval 0.2 &
|
||||
FAKE_WIFI_PID=$!
|
||||
}
|
||||
|
||||
if ! python_ui_runtime_ok >/dev/null 2>&1; then
|
||||
echo "Preparing macOS Python UI runtime extensions..."
|
||||
sync_deps
|
||||
sync_raylib
|
||||
prepare_common_host_artifacts
|
||||
prepare_msgq_host_artifacts
|
||||
remove_if_elf "common/params_pyx.so"
|
||||
remove_if_elf "common/transformations/transformations.so"
|
||||
remove_if_elf "msgq/ipc_pyx.so"
|
||||
remove_if_elf "msgq/visionipc/visionipc_pyx.so"
|
||||
|
||||
run_scons "${jobs}" common/params_pyx.so common/transformations/transformations.so
|
||||
(
|
||||
cd "${ROOT_DIR}/msgq_repo"
|
||||
local_scons_bin="${ROOT_DIR}/.venv/bin/scons"
|
||||
if [[ -x "${local_scons_bin}" ]]; then
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${local_scons_bin}" -j"${jobs}" msgq/ipc_pyx.so msgq/visionipc/visionipc_pyx.so
|
||||
elif "${PY_BIN}" -m SCons --version >/dev/null 2>&1; then
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${PY_BIN}" -m SCons -j"${jobs}" msgq/ipc_pyx.so msgq/visionipc/visionipc_pyx.so
|
||||
else
|
||||
echo "SCons not found in .venv after sync."
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
fi
|
||||
|
||||
if ! python_ui_runtime_ok >/dev/null 2>&1; then
|
||||
"${PY_BIN}" - <<'PY'
|
||||
import site
|
||||
import sys
|
||||
print("python:", sys.executable)
|
||||
print("version:", sys.version)
|
||||
print("site-packages:", site.getsitepackages())
|
||||
PY
|
||||
echo "Unable to load Python UI runtime extensions after rebuild."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${SP_C4_COMPILE_ONLY:-0}" == "1" ]]; then
|
||||
echo "C4 runtime artifacts prepared."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
"${PY_BIN}" - <<'PY'
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.version import terms_version, training_version
|
||||
|
||||
params = Params()
|
||||
params.put("HasAcceptedTerms", terms_version)
|
||||
params.put("CompletedTrainingVersion", training_version)
|
||||
params.put_bool("OpenpilotEnabledToggle", True)
|
||||
params.put_bool("IsDriverViewEnabled", False)
|
||||
PY
|
||||
|
||||
kill_stale_c4_ui
|
||||
start_fake_wifi
|
||||
"${PY_BIN}" selfdrive/ui/ui.py "$@"
|
||||
Executable
+231
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
if [[ ! -f .venv/bin/activate ]]; then
|
||||
echo "Missing .venv. Run tools/install_python_dependencies.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
default_jobs() {
|
||||
if command -v nproc >/dev/null 2>&1; then
|
||||
nproc
|
||||
elif command -v sysctl >/dev/null 2>&1; then
|
||||
sysctl -n hw.ncpu
|
||||
else
|
||||
echo 8
|
||||
fi
|
||||
}
|
||||
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
OS_EXT="linux"
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
OS_EXT="macos"
|
||||
fi
|
||||
HOST_UI="${ROOT_DIR}/selfdrive/ui/ui.${OS_EXT}"
|
||||
PRE_TRACKED_DIRTY="$(mktemp /tmp/frogpilot_c3_pretracked.XXXXXX)"
|
||||
POST_TRACKED_DIRTY="$(mktemp /tmp/frogpilot_c3_posttracked.XXXXXX)"
|
||||
BACKUP_DIR="$(mktemp -d /tmp/frogpilot_c3_backup.XXXXXX)"
|
||||
BACKUP_MANIFEST="${BACKUP_DIR}/.artifact_manifest"
|
||||
FAKE_WIFI_PID=""
|
||||
|
||||
runtime_artifacts=(
|
||||
"selfdrive/ui/ui"
|
||||
"common/libcommon.a"
|
||||
"common/transformations/libtransformations.a"
|
||||
"cereal/libsocketmaster.a"
|
||||
"msgq_repo/libmsgq.a"
|
||||
"msgq_repo/libvisionipc.a"
|
||||
"common/params_pyx.so"
|
||||
"common/transformations/transformations.so"
|
||||
"msgq_repo/msgq/ipc_pyx.so"
|
||||
"msgq_repo/msgq/visionipc/visionipc_pyx.so"
|
||||
)
|
||||
|
||||
collect_tracked_dirty() {
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
if ! git -C "${ROOT_DIR}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
git -C "${ROOT_DIR}" status --porcelain --untracked-files=no | sed -E 's/^.. //'
|
||||
}
|
||||
|
||||
backup_artifact() {
|
||||
local rel="$1"
|
||||
local src="${ROOT_DIR}/${rel}"
|
||||
echo "${rel}" >> "${BACKUP_MANIFEST}"
|
||||
if [[ -f "${src}" ]]; then
|
||||
mkdir -p "${BACKUP_DIR}/$(dirname "${rel}")"
|
||||
cp -f "${src}" "${BACKUP_DIR}/${rel}"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_runtime_artifacts() {
|
||||
if [[ ! -d "${BACKUP_DIR}" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -f "${BACKUP_MANIFEST}" ]]; then
|
||||
while IFS= read -r rel; do
|
||||
[[ -z "${rel}" ]] && continue
|
||||
if [[ -f "${BACKUP_DIR}/${rel}" ]]; then
|
||||
cp -f "${BACKUP_DIR}/${rel}" "${ROOT_DIR}/${rel}"
|
||||
else
|
||||
rm -f "${ROOT_DIR}/${rel}"
|
||||
fi
|
||||
done < "${BACKUP_MANIFEST}"
|
||||
fi
|
||||
|
||||
rm -rf "${BACKUP_DIR}"
|
||||
}
|
||||
|
||||
restore_new_tracked_changes() {
|
||||
if ! collect_tracked_dirty > "${POST_TRACKED_DIRTY}"; then
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r path; do
|
||||
[[ -z "${path}" ]] && continue
|
||||
if ! grep -Fxq "${path}" "${PRE_TRACKED_DIRTY}"; then
|
||||
git -C "${ROOT_DIR}" checkout -- "${path}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
done < "${POST_TRACKED_DIRTY}"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
restore_runtime_artifacts
|
||||
restore_new_tracked_changes
|
||||
stop_fake_wifi
|
||||
rm -f "${PRE_TRACKED_DIRTY}" "${POST_TRACKED_DIRTY}"
|
||||
}
|
||||
|
||||
start_fake_wifi() {
|
||||
if [[ ! "${SP_C3_FAKE_WIFI:-1}" =~ ^(1|true|yes|on)$ ]]; then
|
||||
export SP_ALLOW_DESKTOP_FAKE_WIFI=0
|
||||
return
|
||||
fi
|
||||
|
||||
export SP_ALLOW_DESKTOP_FAKE_WIFI=1
|
||||
"${ROOT_DIR}/.venv/bin/python3" selfdrive/debug/fake_wifi.py --network wifi --strength great --interval 0.2 &
|
||||
FAKE_WIFI_PID=$!
|
||||
}
|
||||
|
||||
stop_fake_wifi() {
|
||||
if [[ -n "${FAKE_WIFI_PID}" ]]; then
|
||||
kill "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
|
||||
FAKE_WIFI_PID=""
|
||||
fi
|
||||
}
|
||||
|
||||
archive_is_aarch64_elf() {
|
||||
local path="$1"
|
||||
[[ -f "${path}" ]] || return 1
|
||||
if command -v readelf >/dev/null 2>&1; then
|
||||
readelf -h "${path}" 2>/dev/null | grep -iq "aarch64"
|
||||
else
|
||||
objdump -a "${path}" 2>/dev/null | head -n 12 | grep -iqE "elf64-littleaarch64|aarch64"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_if_elf() {
|
||||
local rel="$1"
|
||||
local path="${ROOT_DIR}/${rel}"
|
||||
if [[ -f "${path}" ]] && file "${path}" | grep -q "ELF"; then
|
||||
rm -f "${path}"
|
||||
fi
|
||||
}
|
||||
|
||||
purge_objects() {
|
||||
local root="$1"
|
||||
if [[ -d "${root}" ]]; then
|
||||
find "${root}" -type f \( -name '*.o' -o -name '*.os' \) -delete
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_host_artifacts() {
|
||||
if archive_is_aarch64_elf "${ROOT_DIR}/common/libcommon.a"; then
|
||||
rm -f "${ROOT_DIR}/common/libcommon.a" "${ROOT_DIR}/common/params_pyx.o"
|
||||
purge_objects "${ROOT_DIR}/common"
|
||||
fi
|
||||
|
||||
if archive_is_aarch64_elf "${ROOT_DIR}/cereal/libsocketmaster.a"; then
|
||||
rm -f "${ROOT_DIR}/cereal/libsocketmaster.a"
|
||||
purge_objects "${ROOT_DIR}/cereal/messaging"
|
||||
fi
|
||||
|
||||
if archive_is_aarch64_elf "${ROOT_DIR}/common/transformations/libtransformations.a"; then
|
||||
rm -f "${ROOT_DIR}/common/transformations/libtransformations.a" \
|
||||
"${ROOT_DIR}/common/transformations/transformations.o"
|
||||
purge_objects "${ROOT_DIR}/common/transformations"
|
||||
fi
|
||||
|
||||
if archive_is_aarch64_elf "${ROOT_DIR}/msgq_repo/libmsgq.a" || archive_is_aarch64_elf "${ROOT_DIR}/msgq_repo/libvisionipc.a"; then
|
||||
rm -f "${ROOT_DIR}/msgq_repo/libmsgq.a" "${ROOT_DIR}/msgq_repo/libvisionipc.a"
|
||||
purge_objects "${ROOT_DIR}/msgq_repo/msgq"
|
||||
purge_objects "${ROOT_DIR}/msgq_repo/msgq/visionipc"
|
||||
fi
|
||||
|
||||
remove_if_elf "common/params_pyx.so"
|
||||
remove_if_elf "common/transformations/transformations.so"
|
||||
remove_if_elf "msgq_repo/msgq/ipc_pyx.so"
|
||||
remove_if_elf "msgq_repo/msgq/visionipc/visionipc_pyx.so"
|
||||
|
||||
purge_objects "${ROOT_DIR}/selfdrive/ui"
|
||||
purge_objects "${ROOT_DIR}/frogpilot/ui"
|
||||
rm -f "${ROOT_DIR}/selfdrive/ui/ui"
|
||||
}
|
||||
|
||||
collect_tracked_dirty > "${PRE_TRACKED_DIRTY}" || true
|
||||
trap cleanup EXIT
|
||||
|
||||
for rel in "${runtime_artifacts[@]}"; do
|
||||
backup_artifact "${rel}"
|
||||
done
|
||||
|
||||
source .venv/bin/activate
|
||||
if [[ -d /opt/homebrew/bin ]]; then
|
||||
export PATH="/opt/homebrew/bin:${PATH}"
|
||||
fi
|
||||
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
# Keep desktop host builds on Apple toolchain even if shell exports Homebrew llvm.
|
||||
export CC="/usr/bin/clang"
|
||||
export CXX="/usr/bin/clang++"
|
||||
fi
|
||||
unset CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH CPPFLAGS CFLAGS CXXFLAGS LDFLAGS
|
||||
|
||||
prepare_host_artifacts
|
||||
|
||||
export PYTHONPATH="${ROOT_DIR}:${ROOT_DIR}/frogpilot/third_party"
|
||||
for d in "${ROOT_DIR}"/*_repo; do [[ -d "$d" ]] && export PYTHONPATH="${PYTHONPATH}:$d"; done
|
||||
[[ -d "${ROOT_DIR}/third_party/acados" ]] && export PYTHONPATH="${PYTHONPATH}:${ROOT_DIR}/third_party/acados"
|
||||
|
||||
export SP_DISABLE_AUTO_DEVICE_SCONS=1
|
||||
scons -j"${jobs}" selfdrive/ui/ui
|
||||
|
||||
cp -f "${ROOT_DIR}/selfdrive/ui/ui" "${HOST_UI}"
|
||||
cleanup
|
||||
trap - EXIT
|
||||
|
||||
if [[ "${SP_C3_COMPILE_ONLY:-0}" == "1" ]]; then
|
||||
echo "C3 UI binary prepared."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
start_fake_wifi
|
||||
trap stop_fake_wifi EXIT
|
||||
"${HOST_UI}" "$@"
|
||||
rc=$?
|
||||
stop_fake_wifi
|
||||
trap - EXIT
|
||||
exit "${rc}"
|
||||
Executable
+302
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
if [[ ! -f .venv/bin/activate ]]; then
|
||||
echo "Missing .venv. Run tools/install_python_dependencies.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
default_jobs() {
|
||||
if command -v nproc >/dev/null 2>&1; then
|
||||
nproc
|
||||
elif command -v sysctl >/dev/null 2>&1; then
|
||||
sysctl -n hw.ncpu
|
||||
else
|
||||
echo 8
|
||||
fi
|
||||
}
|
||||
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
if [[ -d /opt/homebrew/bin ]]; then
|
||||
export PATH="/opt/homebrew/bin:${PATH}"
|
||||
fi
|
||||
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
# Ensure desktop host extension builds stay on Apple toolchain.
|
||||
export CC="/usr/bin/clang"
|
||||
export CXX="/usr/bin/clang++"
|
||||
fi
|
||||
unset CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH CPPFLAGS CFLAGS CXXFLAGS LDFLAGS
|
||||
|
||||
PY_BIN="${ROOT_DIR}/.venv/bin/python3"
|
||||
if [[ ! -x "${PY_BIN}" ]]; then
|
||||
echo "Missing ${PY_BIN}. Run tools/install_python_dependencies.sh first."
|
||||
exit 1
|
||||
fi
|
||||
export PATH="${ROOT_DIR}/.venv/bin:${PATH}"
|
||||
|
||||
export PYTHONPATH="${ROOT_DIR}:${ROOT_DIR}/frogpilot/third_party"
|
||||
for d in "${ROOT_DIR}"/*_repo; do [[ -d "$d" ]] && export PYTHONPATH="${PYTHONPATH}:$d"; done
|
||||
[[ -d "${ROOT_DIR}/third_party/acados" ]] && export PYTHONPATH="${PYTHONPATH}:${ROOT_DIR}/third_party/acados"
|
||||
export BIG=1
|
||||
export NOBOARD=1
|
||||
export SIMULATION=1
|
||||
export SKIP_FW_QUERY=1
|
||||
export USE_WEBCAM=1
|
||||
|
||||
backup_dir="$(mktemp -d /tmp/frogpilot_raybig_ui_backup.XXXXXX)"
|
||||
backup_manifest="${backup_dir}/.artifact_manifest"
|
||||
PRE_TRACKED_DIRTY="$(mktemp /tmp/frogpilot_raybig_pretracked.XXXXXX)"
|
||||
POST_TRACKED_DIRTY="$(mktemp /tmp/frogpilot_raybig_posttracked.XXXXXX)"
|
||||
FAKE_WIFI_PID=""
|
||||
|
||||
runtime_artifacts=(
|
||||
"common/params_pyx.so"
|
||||
"msgq/ipc_pyx.so"
|
||||
"msgq/visionipc/visionipc_pyx.so"
|
||||
"common/transformations/transformations.so"
|
||||
)
|
||||
|
||||
collect_tracked_dirty() {
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
if ! git -C "${ROOT_DIR}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
git -C "${ROOT_DIR}" status --porcelain --untracked-files=no | sed -E 's/^.. //'
|
||||
}
|
||||
|
||||
restore_runtime_artifacts() {
|
||||
if [[ -d "${backup_dir}" ]]; then
|
||||
if [[ -f "${backup_manifest}" ]]; then
|
||||
while IFS= read -r rel; do
|
||||
[[ -z "${rel}" ]] && continue
|
||||
if [[ -f "${backup_dir}/${rel}" ]]; then
|
||||
cp -f "${backup_dir}/${rel}" "${ROOT_DIR}/${rel}"
|
||||
else
|
||||
rm -f "${ROOT_DIR}/${rel}"
|
||||
fi
|
||||
done < "${backup_manifest}"
|
||||
fi
|
||||
rm -rf "${backup_dir}"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_new_tracked_changes() {
|
||||
if ! collect_tracked_dirty > "${POST_TRACKED_DIRTY}"; then
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r path; do
|
||||
[[ -z "${path}" ]] && continue
|
||||
if ! grep -Fxq "${path}" "${PRE_TRACKED_DIRTY}"; then
|
||||
git -C "${ROOT_DIR}" checkout -- "${path}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
done < "${POST_TRACKED_DIRTY}"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${FAKE_WIFI_PID}" ]]; then
|
||||
kill "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
|
||||
wait "${FAKE_WIFI_PID}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
restore_runtime_artifacts
|
||||
restore_new_tracked_changes
|
||||
rm -f "${PRE_TRACKED_DIRTY}" "${POST_TRACKED_DIRTY}"
|
||||
}
|
||||
|
||||
collect_tracked_dirty > "${PRE_TRACKED_DIRTY}" || true
|
||||
trap cleanup EXIT
|
||||
|
||||
backup_if_elf() {
|
||||
local rel="$1"
|
||||
local src="${ROOT_DIR}/${rel}"
|
||||
echo "${rel}" >> "${backup_manifest}"
|
||||
if [[ -f "${src}" ]]; then
|
||||
mkdir -p "${backup_dir}/$(dirname "${rel}")"
|
||||
cp -f "${src}" "${backup_dir}/${rel}"
|
||||
fi
|
||||
}
|
||||
|
||||
for rel in "${runtime_artifacts[@]}"; do
|
||||
backup_if_elf "${rel}"
|
||||
done
|
||||
|
||||
archive_is_aarch64_elf() {
|
||||
local path="$1"
|
||||
[[ -f "${path}" ]] || return 1
|
||||
if command -v readelf >/dev/null 2>&1; then
|
||||
readelf -h "${path}" 2>/dev/null | grep -iq "aarch64"
|
||||
else
|
||||
objdump -a "${path}" 2>/dev/null | head -n 12 | grep -iqE "elf64-littleaarch64|aarch64"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_if_elf() {
|
||||
local rel="$1"
|
||||
local path="${ROOT_DIR}/${rel}"
|
||||
if [[ -f "${path}" ]] && file "${path}" | grep -q "ELF"; then
|
||||
rm -f "${path}"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_common_host_artifacts() {
|
||||
if archive_is_aarch64_elf "${ROOT_DIR}/common/libcommon.a"; then
|
||||
rm -f \
|
||||
"${ROOT_DIR}/common/libcommon.a" \
|
||||
"${ROOT_DIR}/common/"*.o \
|
||||
"${ROOT_DIR}/common/params_pyx.o" \
|
||||
"${ROOT_DIR}/common/transformations/transformations.o"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_msgq_host_artifacts() {
|
||||
# Clear mixed-arch Python extension objects from both msgq trees. These
|
||||
# commonly conflict after switching between ./build (larch64) and ./raybig (macOS).
|
||||
remove_if_elf "msgq/ipc_pyx.o"
|
||||
remove_if_elf "msgq/visionipc/visionipc_pyx.o"
|
||||
remove_if_elf "msgq_repo/msgq/ipc_pyx.o"
|
||||
remove_if_elf "msgq_repo/msgq/visionipc/visionipc_pyx.o"
|
||||
|
||||
if archive_is_aarch64_elf "${ROOT_DIR}/msgq_repo/libmsgq.a" || archive_is_aarch64_elf "${ROOT_DIR}/msgq_repo/libvisionipc.a"; then
|
||||
rm -f \
|
||||
"${ROOT_DIR}/msgq_repo/libmsgq.a" \
|
||||
"${ROOT_DIR}/msgq_repo/libvisionipc.a" \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/"*.os \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/visionipc/"*.os \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/ipc_pyx.o" \
|
||||
"${ROOT_DIR}/msgq_repo/msgq/visionipc/visionipc_pyx.o"
|
||||
fi
|
||||
}
|
||||
|
||||
python_ui_runtime_ok() {
|
||||
"${PY_BIN}" - <<'PY'
|
||||
import pyray # noqa: F401
|
||||
import openpilot.common.params_pyx # noqa: F401
|
||||
import openpilot.common.transformations.transformations # noqa: F401
|
||||
import msgq.ipc_pyx # noqa: F401
|
||||
import msgq.visionipc.visionipc_pyx # noqa: F401
|
||||
PY
|
||||
}
|
||||
|
||||
sync_deps() {
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
UV_PROJECT_ENVIRONMENT=.venv uv sync --frozen --all-extras
|
||||
else
|
||||
echo "Missing uv. Install dependencies with tools/install_python_dependencies.sh."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sync_raylib() {
|
||||
if "${PY_BIN}" - <<'PY' >/dev/null 2>&1
|
||||
import pyray # noqa: F401
|
||||
PY
|
||||
then
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
UV_PROJECT_ENVIRONMENT=.venv uv pip install "raylib<5.5.0.3"
|
||||
else
|
||||
echo "Missing uv. Install dependencies with tools/install_python_dependencies.sh."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_scons() {
|
||||
local jobs_arg="$1"
|
||||
shift || true
|
||||
local scons_bin="${ROOT_DIR}/.venv/bin/scons"
|
||||
if [[ -x "${scons_bin}" ]]; then
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${scons_bin}" -j"${jobs_arg}" "$@"
|
||||
elif "${PY_BIN}" -m SCons --version >/dev/null 2>&1; then
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${PY_BIN}" -m SCons -j"${jobs_arg}" "$@"
|
||||
else
|
||||
echo "SCons not found in .venv after sync."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
kill_stale_raybig_ui() {
|
||||
pkill -f "selfdrive/ui/ui.py" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
start_fake_wifi() {
|
||||
if [[ ! "${SP_RAYBIG_FAKE_WIFI:-1}" =~ ^(1|true|yes|on)$ ]]; then
|
||||
export SP_ALLOW_DESKTOP_FAKE_WIFI=0
|
||||
return
|
||||
fi
|
||||
|
||||
export SP_ALLOW_DESKTOP_FAKE_WIFI=1
|
||||
"${PY_BIN}" selfdrive/debug/fake_wifi.py --network wifi --strength great --interval 0.2 &
|
||||
FAKE_WIFI_PID=$!
|
||||
}
|
||||
|
||||
if ! python_ui_runtime_ok >/dev/null 2>&1; then
|
||||
echo "Preparing macOS Python UI runtime extensions..."
|
||||
sync_deps
|
||||
sync_raylib
|
||||
prepare_common_host_artifacts
|
||||
prepare_msgq_host_artifacts
|
||||
remove_if_elf "common/params_pyx.so"
|
||||
remove_if_elf "common/transformations/transformations.so"
|
||||
remove_if_elf "msgq/ipc_pyx.so"
|
||||
remove_if_elf "msgq/visionipc/visionipc_pyx.so"
|
||||
|
||||
run_scons "${jobs}" common/params_pyx.so common/transformations/transformations.so
|
||||
(
|
||||
cd "${ROOT_DIR}/msgq_repo"
|
||||
local_scons_bin="${ROOT_DIR}/.venv/bin/scons"
|
||||
if [[ -x "${local_scons_bin}" ]]; then
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${local_scons_bin}" -j"${jobs}" msgq/ipc_pyx.so msgq/visionipc/visionipc_pyx.so
|
||||
elif "${PY_BIN}" -m SCons --version >/dev/null 2>&1; then
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${PY_BIN}" -m SCons -j"${jobs}" msgq/ipc_pyx.so msgq/visionipc/visionipc_pyx.so
|
||||
else
|
||||
echo "SCons not found in .venv after sync."
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
fi
|
||||
|
||||
if ! python_ui_runtime_ok >/dev/null 2>&1; then
|
||||
"${PY_BIN}" - <<'PY'
|
||||
import site
|
||||
import sys
|
||||
print("python:", sys.executable)
|
||||
print("version:", sys.version)
|
||||
print("site-packages:", site.getsitepackages())
|
||||
PY
|
||||
echo "Unable to load Python UI runtime extensions after rebuild."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${SP_RAYBIG_COMPILE_ONLY:-0}" == "1" ]]; then
|
||||
echo "Raybig runtime artifacts prepared."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
"${PY_BIN}" - <<'PY'
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.version import terms_version, training_version
|
||||
|
||||
params = Params()
|
||||
params.put("HasAcceptedTerms", terms_version)
|
||||
params.put("CompletedTrainingVersion", training_version)
|
||||
params.put_bool("OpenpilotEnabledToggle", True)
|
||||
params.put_bool("IsDriverViewEnabled", False)
|
||||
PY
|
||||
|
||||
kill_stale_raybig_ui
|
||||
start_fake_wifi
|
||||
"${PY_BIN}" selfdrive/ui/ui.py "$@"
|
||||
Executable
+187
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
scripts/starpilot_build_flow.sh verify
|
||||
scripts/starpilot_build_flow.sh mac [jobs]
|
||||
scripts/starpilot_build_flow.sh device [jobs]
|
||||
scripts/starpilot_build_flow.sh laptop-setup [device-host]
|
||||
scripts/starpilot_build_flow.sh laptop-device [jobs]
|
||||
|
||||
Modes:
|
||||
verify Check critical StarPilot parity build guards in-tree.
|
||||
mac Run macOS developer validation build (Qt UI + Python compile checks).
|
||||
device Run full on-device build (comma hardware only) and set prebuilt flag.
|
||||
laptop-setup Prepare laptop device-build environment (venv + image + sysroot).
|
||||
laptop-device Run full device-target build in Linux/aarch64 container on laptop.
|
||||
|
||||
Notes:
|
||||
- mac mode does NOT produce device binaries.
|
||||
- device mode must be run on comma hardware (/TICI present).
|
||||
- laptop-device mode requires docker/podman and a prepared sysroot.
|
||||
- prepare sysroot via scripts/laptop_device_build.sh setup-sysroot-agnos
|
||||
or scripts/laptop_device_build.sh setup-sysroot <device-ip>.
|
||||
EOF
|
||||
}
|
||||
|
||||
require_venv() {
|
||||
if [[ ! -f "${ROOT_DIR}/.venv/bin/activate" ]]; then
|
||||
echo "Missing .venv. Initialize it first."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_tree() {
|
||||
local failed=0
|
||||
|
||||
if ! rg -q "libs = \\['m', 'pthread'" system/camerad/SConscript; then
|
||||
echo "FAIL: system/camerad/SConscript is missing pthread link guard."
|
||||
failed=1
|
||||
else
|
||||
echo "OK: camerad link libs include pthread."
|
||||
fi
|
||||
|
||||
if ! rg -q "TimezoneFinder = None" system/timed.py; then
|
||||
echo "FAIL: system/timed.py missing TimezoneFinder fallback."
|
||||
failed=1
|
||||
else
|
||||
echo "OK: timed.py has TimezoneFinder fallback."
|
||||
fi
|
||||
|
||||
if ! rg -q "if tf is not None:" system/timed.py; then
|
||||
echo "FAIL: system/timed.py missing guarded timezone usage."
|
||||
failed=1
|
||||
else
|
||||
echo "OK: timed.py guards timezonefinder usage."
|
||||
fi
|
||||
|
||||
if ! rg -q "if os.path.isfile\\(\"/TICI\"\\)" common/spinner.py; then
|
||||
echo "FAIL: common/spinner.py missing device-only legacy spinner path."
|
||||
failed=1
|
||||
else
|
||||
echo "OK: spinner uses device-only legacy fallback."
|
||||
fi
|
||||
|
||||
if ! rg -q "if os.path.isfile\\(\"/TICI\"\\)" common/text_window.py; then
|
||||
echo "FAIL: common/text_window.py missing device-only legacy text path."
|
||||
failed=1
|
||||
else
|
||||
echo "OK: text window uses device-only legacy fallback."
|
||||
fi
|
||||
|
||||
if ! source .venv/bin/activate >/dev/null 2>&1; then
|
||||
echo "FAIL: .venv is required for loggerd import checks."
|
||||
failed=1
|
||||
else
|
||||
if ! python - <<'PY'
|
||||
import builtins
|
||||
import importlib
|
||||
|
||||
real_import = builtins.__import__
|
||||
def fake_import(name, *args, **kwargs):
|
||||
if name == "xattr":
|
||||
raise ModuleNotFoundError("simulated missing xattr")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
builtins.__import__ = fake_import
|
||||
importlib.import_module("openpilot.system.loggerd.xattr_cache")
|
||||
print("xattr_cache import fallback OK")
|
||||
PY
|
||||
then
|
||||
echo "FAIL: loggerd xattr fallback check failed."
|
||||
failed=1
|
||||
else
|
||||
echo "OK: loggerd xattr fallback works when xattr module is missing."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${failed}" -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
build_mac() {
|
||||
local jobs="${1:-8}"
|
||||
require_venv
|
||||
source .venv/bin/activate
|
||||
|
||||
verify_tree
|
||||
|
||||
# Homebrew tools are needed for SConstruct on macOS.
|
||||
export PATH="/opt/homebrew/bin:${PATH}"
|
||||
export TMPDIR="${TMPDIR:-/tmp}"
|
||||
|
||||
python -m py_compile \
|
||||
system/timed.py \
|
||||
common/spinner.py \
|
||||
common/text_window.py \
|
||||
frogpilot/system/frogpilot_stats.py
|
||||
|
||||
python -m compileall -q \
|
||||
selfdrive/ui \
|
||||
system/ui \
|
||||
frogpilot/system/the_pond \
|
||||
frogpilot/system/galaxy
|
||||
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 scons -j"${jobs}" selfdrive/ui/ui
|
||||
|
||||
echo "mac build validation complete."
|
||||
}
|
||||
|
||||
build_device() {
|
||||
local jobs="${1:-$(nproc)}"
|
||||
|
||||
if [[ ! -f /TICI ]]; then
|
||||
echo "device mode must run on comma hardware (/TICI not found)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_venv
|
||||
source .venv/bin/activate
|
||||
|
||||
verify_tree
|
||||
scons -j"${jobs}"
|
||||
touch prebuilt
|
||||
|
||||
echo "device build complete and prebuilt flag set."
|
||||
}
|
||||
|
||||
main() {
|
||||
if [[ $# -lt 1 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local mode="$1"
|
||||
shift || true
|
||||
|
||||
case "${mode}" in
|
||||
verify)
|
||||
verify_tree
|
||||
;;
|
||||
mac)
|
||||
build_mac "${1:-8}"
|
||||
;;
|
||||
device)
|
||||
build_device "${1:-$(nproc)}"
|
||||
;;
|
||||
laptop-setup)
|
||||
scripts/laptop_device_build.sh setup "${1:-}" "${2:-comma}" "${3:-22}"
|
||||
;;
|
||||
laptop-device)
|
||||
scripts/laptop_device_build.sh build "${1:-8}"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user