TOOLS
This commit is contained in:
Executable
+531
@@ -0,0 +1,531 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
PLATFORM_KEY="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
HOST_PLATFORM_ROOT="${ROOT_DIR}/.host_runtime/${PLATFORM_KEY}"
|
||||
HOST_ROOT=""
|
||||
WORK_DIR=""
|
||||
HOST_VENV=""
|
||||
HOST_LOCK_DIR=""
|
||||
HOST_LOCK_PID_FILE=""
|
||||
HOST_LOCK_CMD_FILE=""
|
||||
HOST_LOCK_HELD=0
|
||||
HOST_BUCKETS=(shared cabana plotjuggler)
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./dev <command> [args...]
|
||||
./tool <command> [args...]
|
||||
./tools/host <command> [args...]
|
||||
|
||||
Commands:
|
||||
c3 Launch the desktop Qt UI from the isolated host cache.
|
||||
c4 Launch the small raylib UI from the isolated host cache.
|
||||
raybig Launch the large raylib UI from the isolated host cache.
|
||||
replay Build and run replay from the isolated host cache.
|
||||
cabana Build and run cabana from the isolated host cache.
|
||||
plotjuggler Run PlotJuggler helper from the isolated host cache.
|
||||
juggle Alias for plotjuggler.
|
||||
sync Refresh the isolated host cache only.
|
||||
shell Open a shell inside the isolated host cache.
|
||||
help Show this help text.
|
||||
|
||||
Notes:
|
||||
- Host-tool builds happen under ./.host_runtime/ and do not touch the main tree.
|
||||
- `cabana` and `plotjuggler` use their own host-runtime buckets and can run together.
|
||||
- Other commands that share a bucket still wait on that bucket's lock.
|
||||
- `./build` remains the device-target flow.
|
||||
- For c3/c4/raybig, pass the jobs count first to preserve existing shorthand:
|
||||
./dev c3 8
|
||||
./dev raybig 12
|
||||
- `./dev sync` refreshes all host buckets. Use `./dev sync cabana` to sync one.
|
||||
EOF
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
resolve_host_bucket() {
|
||||
local name="${1:-shared}"
|
||||
|
||||
case "${name}" in
|
||||
shared|default|ui|c3|c4|raybig|replay|shell)
|
||||
echo "shared"
|
||||
;;
|
||||
cabana)
|
||||
echo "cabana"
|
||||
;;
|
||||
plotjuggler|juggle)
|
||||
echo "plotjuggler"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
set_host_bucket() {
|
||||
local bucket="$1"
|
||||
|
||||
if [[ "${bucket}" == "shared" ]]; then
|
||||
HOST_ROOT="${HOST_PLATFORM_ROOT}"
|
||||
else
|
||||
HOST_ROOT="${HOST_PLATFORM_ROOT}/${bucket}"
|
||||
fi
|
||||
WORK_DIR="${HOST_ROOT}/worktree"
|
||||
HOST_VENV="${HOST_ROOT}/venv"
|
||||
HOST_LOCK_DIR="${HOST_ROOT}/lock"
|
||||
HOST_LOCK_PID_FILE="${HOST_LOCK_DIR}/pid"
|
||||
HOST_LOCK_CMD_FILE="${HOST_LOCK_DIR}/command"
|
||||
}
|
||||
|
||||
lock_owner_summary() {
|
||||
local lock_pid=""
|
||||
local lock_cmd="unknown"
|
||||
|
||||
if [[ -f "${HOST_LOCK_PID_FILE}" ]]; then
|
||||
lock_pid="$(<"${HOST_LOCK_PID_FILE}")"
|
||||
fi
|
||||
if [[ -f "${HOST_LOCK_CMD_FILE}" ]]; then
|
||||
lock_cmd="$(<"${HOST_LOCK_CMD_FILE}")"
|
||||
fi
|
||||
|
||||
if [[ -n "${lock_pid}" ]]; then
|
||||
echo "pid ${lock_pid} (${lock_cmd})"
|
||||
else
|
||||
echo "${lock_cmd}"
|
||||
fi
|
||||
}
|
||||
|
||||
lock_is_stale() {
|
||||
local lock_pid=""
|
||||
|
||||
if [[ ! -f "${HOST_LOCK_PID_FILE}" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
lock_pid="$(<"${HOST_LOCK_PID_FILE}")"
|
||||
if [[ ! "${lock_pid}" =~ ^[0-9]+$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if kill -0 "${lock_pid}" 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
release_host_lock() {
|
||||
if [[ "${HOST_LOCK_HELD}" != "1" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
rm -f "${HOST_LOCK_PID_FILE}" "${HOST_LOCK_CMD_FILE}"
|
||||
rmdir "${HOST_LOCK_DIR}" 2>/dev/null || rm -rf "${HOST_LOCK_DIR}"
|
||||
HOST_LOCK_HELD=0
|
||||
}
|
||||
|
||||
acquire_host_lock() {
|
||||
local lock_label="$1"
|
||||
local notified=0
|
||||
|
||||
mkdir -p "${HOST_ROOT}"
|
||||
|
||||
while true; do
|
||||
if mkdir "${HOST_LOCK_DIR}" 2>/dev/null; then
|
||||
printf '%s\n' "$$" > "${HOST_LOCK_PID_FILE}"
|
||||
printf '%s\n' "${lock_label}" > "${HOST_LOCK_CMD_FILE}"
|
||||
HOST_LOCK_HELD=1
|
||||
trap release_host_lock EXIT
|
||||
return
|
||||
fi
|
||||
|
||||
if lock_is_stale; then
|
||||
echo "Removing stale host runtime lock: $(lock_owner_summary)" >&2
|
||||
rm -rf "${HOST_LOCK_DIR}"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "${notified}" == "0" ]]; then
|
||||
echo "Waiting for host runtime lock in ${HOST_ROOT}. Another shorthand command is using it: $(lock_owner_summary)." >&2
|
||||
notified=1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
ensure_venv() {
|
||||
if [[ ! -x "${ROOT_DIR}/.venv/bin/python3" ]]; then
|
||||
echo "Missing .venv. Run tools/install_python_dependencies.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
echo "Missing uv. Run tools/install_python_dependencies.sh first."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sync_selected_bucket() {
|
||||
local bucket="$1"
|
||||
|
||||
set_host_bucket "${bucket}"
|
||||
acquire_host_lock "sync ${bucket}"
|
||||
sync_worktree
|
||||
echo "Host tool cache synced (${bucket}): ${WORK_DIR}"
|
||||
release_host_lock
|
||||
}
|
||||
|
||||
sync_all_buckets() {
|
||||
local bucket=""
|
||||
for bucket in "${HOST_BUCKETS[@]}"; do
|
||||
sync_selected_bucket "${bucket}"
|
||||
done
|
||||
}
|
||||
|
||||
purge_host_python_artifacts() {
|
||||
rm -f \
|
||||
"${WORK_DIR}/common/params_pyx.so" \
|
||||
"${WORK_DIR}/common/params_pyx.o" \
|
||||
"${WORK_DIR}/common/params_pyx.cpp" \
|
||||
"${WORK_DIR}/common/transformations/transformations.so" \
|
||||
"${WORK_DIR}/common/transformations/transformations.o" \
|
||||
"${WORK_DIR}/common/transformations/transformations.cpp" \
|
||||
"${WORK_DIR}/msgq_repo/msgq/ipc_pyx.so" \
|
||||
"${WORK_DIR}/msgq_repo/msgq/ipc_pyx.o" \
|
||||
"${WORK_DIR}/msgq_repo/msgq/ipc_pyx.cpp" \
|
||||
"${WORK_DIR}/msgq_repo/msgq/visionipc/visionipc_pyx.so" \
|
||||
"${WORK_DIR}/msgq_repo/msgq/visionipc/visionipc_pyx.o" \
|
||||
"${WORK_DIR}/msgq_repo/msgq/visionipc/visionipc_pyx.cpp"
|
||||
}
|
||||
|
||||
ensure_host_python_tools() {
|
||||
ensure_venv
|
||||
|
||||
local root_py_minor
|
||||
root_py_minor="$("${ROOT_DIR}/.venv/bin/python3" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
||||
|
||||
if [[ -x "${HOST_VENV}/bin/python3" ]]; then
|
||||
local host_py_minor
|
||||
host_py_minor="$("${HOST_VENV}/bin/python3" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
||||
if [[ "${host_py_minor}" != "${root_py_minor}" ]]; then
|
||||
rm -rf "${HOST_VENV}"
|
||||
purge_host_python_artifacts
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -x "${HOST_VENV}/bin/scons" && -x "${HOST_VENV}/bin/cythonize" && -x "${HOST_VENV}/bin/python3" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
(
|
||||
cd "${WORK_DIR}"
|
||||
UV_PROJECT_ENVIRONMENT="${HOST_VENV}" UV_PYTHON="${ROOT_DIR}/.venv/bin/python3" uv sync --frozen --all-extras
|
||||
)
|
||||
}
|
||||
|
||||
sync_worktree() {
|
||||
ensure_venv
|
||||
|
||||
mkdir -p "${HOST_ROOT}"
|
||||
|
||||
local excludes=(
|
||||
".git/"
|
||||
".venv/"
|
||||
".venv-linux-arm64/"
|
||||
".cache/"
|
||||
".comma_sysroot/"
|
||||
".host_runtime/"
|
||||
"__pycache__/"
|
||||
"*.pyc"
|
||||
"*.pyo"
|
||||
"*.o"
|
||||
"*.os"
|
||||
".sconsign.dblite"
|
||||
"compile_commands.json"
|
||||
"tools/plotjuggler/bin/"
|
||||
"tools/replay/replay"
|
||||
"tools/replay/tests/test_replay"
|
||||
"tools/cabana/cabana"
|
||||
"tools/cabana/tests/test_cabana"
|
||||
"selfdrive/ui/ui"
|
||||
"selfdrive/ui/ui.macos"
|
||||
"selfdrive/ui/ui.larch64"
|
||||
"cereal/libcereal.a"
|
||||
"cereal/libsocketmaster.a"
|
||||
"cereal/messaging/bridge"
|
||||
"common/libcommon.a"
|
||||
"common/params_pyx.so"
|
||||
"common/params_pyx.cpp"
|
||||
"common/transformations/libtransformations.a"
|
||||
"common/transformations/transformations.so"
|
||||
"msgq_repo/libmsgq.a"
|
||||
"msgq_repo/libvisionipc.a"
|
||||
"msgq_repo/msgq/ipc_pyx.so"
|
||||
"msgq_repo/msgq/visionipc/visionipc_pyx.so"
|
||||
"rednose_repo/rednose/helpers/ekf_sym_pyx.so"
|
||||
"selfdrive/modeld/models/commonmodel_pyx.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"
|
||||
"third_party/libjson11.a"
|
||||
"third_party/libkaitai.a"
|
||||
)
|
||||
|
||||
local rsync_args=(-a --delete)
|
||||
local pattern=""
|
||||
for pattern in "${excludes[@]}"; do
|
||||
rsync_args+=(--exclude "${pattern}")
|
||||
done
|
||||
|
||||
rsync "${rsync_args[@]}" "${ROOT_DIR}/" "${WORK_DIR}/"
|
||||
rm -f "${WORK_DIR}/third_party/libjson11.a" "${WORK_DIR}/third_party/libkaitai.a"
|
||||
ensure_host_python_tools
|
||||
rm -rf "${WORK_DIR}/.venv"
|
||||
ln -s "${HOST_VENV}" "${WORK_DIR}/.venv"
|
||||
}
|
||||
|
||||
setup_build_env() {
|
||||
if [[ -d /opt/homebrew/bin ]]; then
|
||||
export PATH="/opt/homebrew/bin:${PATH}"
|
||||
fi
|
||||
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
export CC="/usr/bin/clang"
|
||||
export CXX="/usr/bin/clang++"
|
||||
fi
|
||||
|
||||
unset CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH CPPFLAGS CFLAGS CXXFLAGS LDFLAGS
|
||||
}
|
||||
|
||||
export_workdir_pythonpath() {
|
||||
export PYTHONPATH="${WORK_DIR}:${WORK_DIR}/frogpilot/third_party"
|
||||
local repo_dir=""
|
||||
for repo_dir in "${WORK_DIR}"/*_repo; do
|
||||
[[ -d "${repo_dir}" ]] && export PYTHONPATH="${PYTHONPATH}:${repo_dir}"
|
||||
done
|
||||
[[ -d "${WORK_DIR}/third_party/acados" ]] && export PYTHONPATH="${PYTHONPATH}:${WORK_DIR}/third_party/acados"
|
||||
}
|
||||
|
||||
run_host_scons() {
|
||||
local jobs="$1"
|
||||
shift || true
|
||||
|
||||
(
|
||||
cd "${WORK_DIR}"
|
||||
setup_build_env
|
||||
source .venv/bin/activate
|
||||
SP_DISABLE_AUTO_DEVICE_SCONS=1 "${WORK_DIR}/.venv/bin/scons" --extras -j"${jobs}" "$@"
|
||||
)
|
||||
}
|
||||
|
||||
run_in_worktree() {
|
||||
(
|
||||
cd "${WORK_DIR}"
|
||||
setup_build_env
|
||||
"$@"
|
||||
)
|
||||
}
|
||||
|
||||
run_python_tool() {
|
||||
local script_path="$1"
|
||||
shift || true
|
||||
|
||||
(
|
||||
cd "${WORK_DIR}"
|
||||
setup_build_env
|
||||
export_workdir_pythonpath
|
||||
source .venv/bin/activate
|
||||
exec "${WORK_DIR}/.venv/bin/python3" "${WORK_DIR}/${script_path}" "$@"
|
||||
)
|
||||
}
|
||||
|
||||
launch_c3() {
|
||||
local jobs
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
sync_worktree
|
||||
run_in_worktree "${WORK_DIR}/scripts/launch_ui_desktop.sh" "${jobs}" "$@"
|
||||
}
|
||||
|
||||
launch_c4() {
|
||||
local jobs
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
sync_worktree
|
||||
run_in_worktree "${WORK_DIR}/scripts/launch_ui_c4_desktop.sh" "${jobs}" "$@"
|
||||
}
|
||||
|
||||
launch_raybig() {
|
||||
local jobs
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
sync_worktree
|
||||
run_in_worktree "${WORK_DIR}/scripts/launch_ui_raybig_desktop.sh" "${jobs}" "$@"
|
||||
}
|
||||
|
||||
launch_replay() {
|
||||
local jobs
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
sync_worktree
|
||||
run_host_scons "${jobs}" tools/replay/replay
|
||||
|
||||
(
|
||||
cd "${WORK_DIR}"
|
||||
setup_build_env
|
||||
export BASEDIR="${WORK_DIR}"
|
||||
exec "${WORK_DIR}/tools/replay/replay" "$@"
|
||||
)
|
||||
}
|
||||
|
||||
launch_cabana() {
|
||||
local jobs
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
sync_worktree
|
||||
run_host_scons "${jobs}" tools/cabana/cabana
|
||||
|
||||
(
|
||||
cd "${WORK_DIR}"
|
||||
setup_build_env
|
||||
export BASEDIR="${WORK_DIR}"
|
||||
exec "${WORK_DIR}/tools/cabana/cabana" "$@"
|
||||
)
|
||||
}
|
||||
|
||||
launch_plotjuggler() {
|
||||
local jobs
|
||||
jobs="$(default_jobs)"
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
jobs="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
sync_worktree
|
||||
run_host_scons "${jobs}" \
|
||||
common/params_pyx.so \
|
||||
common/transformations/transformations.so \
|
||||
msgq_repo/msgq/ipc_pyx.so \
|
||||
msgq_repo/msgq/visionipc/visionipc_pyx.so
|
||||
run_python_tool "tools/plotjuggler/juggle.py" "$@"
|
||||
}
|
||||
|
||||
open_shell() {
|
||||
sync_worktree
|
||||
(
|
||||
cd "${WORK_DIR}"
|
||||
setup_build_env
|
||||
export_workdir_pythonpath
|
||||
source .venv/bin/activate
|
||||
exec "${SHELL:-/bin/bash}"
|
||||
)
|
||||
}
|
||||
|
||||
main() {
|
||||
local command="${1:-help}"
|
||||
local bucket=""
|
||||
if [[ $# -gt 0 ]]; then
|
||||
shift || true
|
||||
fi
|
||||
|
||||
case "${command}" in
|
||||
help|-h|--help)
|
||||
usage
|
||||
;;
|
||||
c3|c4|raybig|replay|shell)
|
||||
set_host_bucket "shared"
|
||||
acquire_host_lock "${command} $*"
|
||||
;;
|
||||
cabana)
|
||||
set_host_bucket "cabana"
|
||||
acquire_host_lock "${command} $*"
|
||||
;;
|
||||
plotjuggler|juggle)
|
||||
set_host_bucket "plotjuggler"
|
||||
acquire_host_lock "${command} $*"
|
||||
;;
|
||||
sync)
|
||||
if [[ $# -gt 0 ]]; then
|
||||
bucket="$(resolve_host_bucket "${1}")" || {
|
||||
echo "Unknown host bucket for sync: ${1}" >&2
|
||||
echo "Valid sync buckets: shared, cabana, plotjuggler" >&2
|
||||
exit 1
|
||||
}
|
||||
shift || true
|
||||
sync_selected_bucket "${bucket}"
|
||||
else
|
||||
sync_all_buckets
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: ${command}" >&2
|
||||
echo >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${command}" in
|
||||
c3)
|
||||
launch_c3 "$@"
|
||||
;;
|
||||
c4)
|
||||
launch_c4 "$@"
|
||||
;;
|
||||
raybig)
|
||||
launch_raybig "$@"
|
||||
;;
|
||||
replay)
|
||||
launch_replay "$@"
|
||||
;;
|
||||
cabana)
|
||||
launch_cabana "$@"
|
||||
;;
|
||||
plotjuggler|juggle)
|
||||
launch_plotjuggler "$@"
|
||||
;;
|
||||
shell)
|
||||
open_shell
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
set_host_bucket "shared"
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user