370 lines
11 KiB
Bash
Executable File
370 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Unified background agent script for YAZE (macOS launchd, Linux systemd)
|
|
# Subcommands:
|
|
# install [--build-type X] [--use-inotify] [--ubuntu-bootstrap] [--enable-linger]
|
|
# uninstall
|
|
# run-once # one-shot build & test
|
|
# watch # linux: inotify-based watch loop
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
|
|
BUILD_DIR_DEFAULT="${PROJECT_ROOT}/build"
|
|
BUILD_TYPE_DEFAULT="Debug"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $0 <subcommand> [options]
|
|
|
|
Subcommands:
|
|
install Install background agent
|
|
--build-type X CMake build type (default: ${BUILD_TYPE_DEFAULT})
|
|
--use-inotify Linux: use long-lived inotify watcher
|
|
--ubuntu-bootstrap Install Ubuntu deps via apt (sudo)
|
|
--enable-linger Enable user lingering (Linux)
|
|
|
|
uninstall Remove installed background agent
|
|
|
|
run-once One-shot build and test
|
|
env: BUILD_DIR, BUILD_TYPE
|
|
|
|
watch Linux: inotify-based watch loop
|
|
env: BUILD_DIR, BUILD_TYPE, INTERVAL_SECONDS (fallback)
|
|
|
|
Environment:
|
|
BUILD_DIR Build directory (default: ${BUILD_DIR_DEFAULT})
|
|
BUILD_TYPE CMake build type (default: ${BUILD_TYPE_DEFAULT})
|
|
EOF
|
|
}
|
|
|
|
build_and_test() {
|
|
local build_dir="${BUILD_DIR:-${BUILD_DIR_DEFAULT}}"
|
|
local build_type="${BUILD_TYPE:-${BUILD_TYPE_DEFAULT}}"
|
|
local log_file="${LOG_FILE:-"${build_dir}/yaze_agent.log"}"
|
|
|
|
mkdir -p "${build_dir}"
|
|
{
|
|
echo "==== $(date '+%Y-%m-%d %H:%M:%S') | yaze agent run (type=${build_type}) ===="
|
|
echo "Project: ${PROJECT_ROOT}"
|
|
|
|
cmake -S "${PROJECT_ROOT}" -B "${build_dir}" -DCMAKE_BUILD_TYPE="${build_type}"
|
|
cmake --build "${build_dir}" --config "${build_type}" -j
|
|
|
|
if [[ -x "${build_dir}/bin/yaze_test" ]]; then
|
|
"${build_dir}/bin/yaze_test"
|
|
else
|
|
if command -v ctest >/dev/null 2>&1; then
|
|
ctest --test-dir "${build_dir}" --output-on-failure
|
|
else
|
|
echo "ctest not found and test binary missing. Skipping tests." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
} >>"${log_file}" 2>&1
|
|
}
|
|
|
|
sub_run_once() {
|
|
build_and_test
|
|
}
|
|
|
|
sub_watch() {
|
|
local build_dir="${BUILD_DIR:-${BUILD_DIR_DEFAULT}}"
|
|
local build_type="${BUILD_TYPE:-${BUILD_TYPE_DEFAULT}}"
|
|
local interval="${INTERVAL_SECONDS:-5}"
|
|
mkdir -p "${build_dir}"
|
|
if command -v inotifywait >/dev/null 2>&1; then
|
|
echo "[agent] using inotifywait watcher"
|
|
while true; do
|
|
BUILD_DIR="${build_dir}" BUILD_TYPE="${build_type}" build_and_test || true
|
|
inotifywait -r -e modify,create,delete,move \
|
|
"${PROJECT_ROOT}/src" \
|
|
"${PROJECT_ROOT}/test" \
|
|
"${PROJECT_ROOT}/CMakeLists.txt" \
|
|
"${PROJECT_ROOT}/src/CMakeLists.txt" >/dev/null 2>&1 || true
|
|
done
|
|
else
|
|
echo "[agent] inotifywait not found; running periodic loop (${interval}s)"
|
|
while true; do
|
|
BUILD_DIR="${build_dir}" BUILD_TYPE="${build_type}" build_and_test || true
|
|
sleep "${interval}"
|
|
done
|
|
fi
|
|
}
|
|
|
|
ensure_exec() {
|
|
if [[ ! -x "$1" ]]; then
|
|
chmod +x "$1"
|
|
fi
|
|
}
|
|
|
|
ubuntu_bootstrap() {
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
sudo apt-get update
|
|
sudo apt-get install -y inotify-tools build-essential cmake ninja-build pkg-config \
|
|
libsdl2-dev libpng-dev libglew-dev libwavpack-dev libabsl-dev \
|
|
libboost-all-dev libboost-python-dev python3-dev libpython3-dev
|
|
else
|
|
echo "apt-get not found; skipping Ubuntu bootstrap" >&2
|
|
fi
|
|
}
|
|
|
|
enable_linger_linux() {
|
|
if command -v loginctl >/dev/null 2>&1; then
|
|
if sudo -n true 2>/dev/null; then
|
|
sudo loginctl enable-linger "$USER" || true
|
|
else
|
|
echo "Note: enabling linger may require sudo: sudo loginctl enable-linger $USER" >&2
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Wrapper to run systemctl --user with a session bus in headless shells
|
|
systemctl_user() {
|
|
# Only apply on Linux
|
|
if [[ "$(uname -s)" != "Linux" ]]; then
|
|
systemctl "$@"
|
|
return
|
|
fi
|
|
local uid
|
|
uid="$(id -u)"
|
|
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/${uid}}"
|
|
export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=${XDG_RUNTIME_DIR}/bus}"
|
|
if [[ ! -S "${XDG_RUNTIME_DIR}/bus" ]]; then
|
|
echo "[agent] Warning: user bus not found at ${XDG_RUNTIME_DIR}/bus. If this fails, run: sudo loginctl enable-linger $USER" >&2
|
|
fi
|
|
systemctl --user "$@"
|
|
}
|
|
|
|
is_systemd_available() {
|
|
# True if systemd is PID 1 and systemctl exists and a user bus likely available
|
|
if [[ ! -d /run/systemd/system ]]; then
|
|
return 1
|
|
fi
|
|
if ! command -v systemctl >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
start_userland_agent() {
|
|
local build_type="${1}"
|
|
local build_dir="${2}"
|
|
local agent_home="$HOME/.local/share/yaze-agent"
|
|
mkdir -p "${agent_home}"
|
|
local log_file="${agent_home}/agent.log"
|
|
nohup env BUILD_TYPE="${build_type}" BUILD_DIR="${build_dir}" "${SCRIPT_DIR}/agent.sh" watch >>"${log_file}" 2>&1 & echo $! > "${agent_home}/agent.pid"
|
|
echo "Userland agent started (PID $(cat "${agent_home}/agent.pid")) - logs: ${log_file}"
|
|
}
|
|
|
|
stop_userland_agent() {
|
|
local agent_home="$HOME/.local/share/yaze-agent"
|
|
local pid_file="${agent_home}/agent.pid"
|
|
if [[ -f "${pid_file}" ]]; then
|
|
local pid
|
|
pid="$(cat "${pid_file}")"
|
|
if kill -0 "${pid}" >/dev/null 2>&1; then
|
|
kill "${pid}" || true
|
|
fi
|
|
rm -f "${pid_file}"
|
|
echo "Stopped userland agent"
|
|
fi
|
|
}
|
|
|
|
install_macos() {
|
|
local build_type="${1}"
|
|
local build_dir="${2}"
|
|
local label="com.yaze.watchtest"
|
|
local plist_path="$HOME/Library/LaunchAgents/${label}.plist"
|
|
|
|
mkdir -p "${build_dir}"
|
|
|
|
cat >"$plist_path" <<PLIST
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>Label</key>
|
|
<string>${label}</string>
|
|
<key>RunAtLoad</key>
|
|
<true/>
|
|
<key>ProgramArguments</key>
|
|
<array>
|
|
<string>/bin/zsh</string>
|
|
<string>-lc</string>
|
|
<string>"${SCRIPT_DIR}/agent.sh" run-once</string>
|
|
</array>
|
|
<key>WorkingDirectory</key>
|
|
<string>${PROJECT_ROOT}</string>
|
|
<key>WatchPaths</key>
|
|
<array>
|
|
<string>${PROJECT_ROOT}/src</string>
|
|
<string>${PROJECT_ROOT}/test</string>
|
|
<string>${PROJECT_ROOT}/CMakeLists.txt</string>
|
|
<string>${PROJECT_ROOT}/src/CMakeLists.txt</string>
|
|
</array>
|
|
<key>StandardOutPath</key>
|
|
<string>${build_dir}/yaze_agent.out.log</string>
|
|
<key>StandardErrorPath</key>
|
|
<string>${build_dir}/yaze_agent.err.log</string>
|
|
<key>EnvironmentVariables</key>
|
|
<dict>
|
|
<key>BUILD_TYPE</key><string>${build_type}</string>
|
|
<key>BUILD_DIR</key><string>${build_dir}</string>
|
|
</dict>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
|
|
launchctl unload -w "$plist_path" 2>/dev/null || true
|
|
launchctl load -w "$plist_path"
|
|
echo "LaunchAgent installed and loaded: ${plist_path}"
|
|
}
|
|
|
|
install_linux() {
|
|
local build_type="${1}"
|
|
local build_dir="${2}"
|
|
local use_inotify="${3}"
|
|
local systemd_dir="$HOME/.config/systemd/user"
|
|
local service_name="yaze-watchtest.service"
|
|
local path_name="yaze-watchtest.path"
|
|
|
|
mkdir -p "${systemd_dir}" "${build_dir}"
|
|
|
|
if ! is_systemd_available; then
|
|
echo "[agent] systemd not available; installing userland background agent"
|
|
start_userland_agent "${build_type}" "${build_dir}"
|
|
return
|
|
fi
|
|
|
|
if [[ "${use_inotify}" == "1" ]]; then
|
|
cat >"${systemd_dir}/yaze-watchtest-inotify.service" <<UNIT
|
|
[Unit]
|
|
Description=Yaze inotify watcher build-and-test
|
|
|
|
[Service]
|
|
Type=simple
|
|
Environment=BUILD_TYPE=${build_type}
|
|
Environment=BUILD_DIR=${build_dir}
|
|
WorkingDirectory=${PROJECT_ROOT}
|
|
ExecStart=/bin/bash -lc '"${SCRIPT_DIR}/agent.sh" watch'
|
|
Restart=always
|
|
RestartSec=2
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
UNIT
|
|
|
|
systemctl_user daemon-reload
|
|
systemctl_user enable --now yaze-watchtest-inotify.service
|
|
echo "systemd user service enabled: yaze-watchtest-inotify.service"
|
|
return
|
|
fi
|
|
|
|
cat >"${systemd_dir}/${service_name}" <<UNIT
|
|
[Unit]
|
|
Description=Yaze build-and-test (one-shot)
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
Environment=BUILD_TYPE=${build_type}
|
|
Environment=BUILD_DIR=${build_dir}
|
|
WorkingDirectory=${PROJECT_ROOT}
|
|
ExecStart=/bin/bash -lc '"${SCRIPT_DIR}/agent.sh" run-once'
|
|
UNIT
|
|
|
|
cat >"${systemd_dir}/${path_name}" <<UNIT
|
|
[Unit]
|
|
Description=Watch Yaze src/test for changes
|
|
|
|
[Path]
|
|
PathModified=${PROJECT_ROOT}/src
|
|
PathModified=${PROJECT_ROOT}/test
|
|
PathModified=${PROJECT_ROOT}/CMakeLists.txt
|
|
PathModified=${PROJECT_ROOT}/src/CMakeLists.txt
|
|
Unit=${service_name}
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
UNIT
|
|
|
|
systemctl_user daemon-reload
|
|
systemctl_user enable --now "$path_name"
|
|
echo "systemd user path unit enabled: ${path_name}"
|
|
systemctl_user start "$service_name" || true
|
|
}
|
|
|
|
sub_install() {
|
|
local build_type="${BUILD_TYPE:-${BUILD_TYPE_DEFAULT}}"
|
|
local build_dir="${BUILD_DIR:-${BUILD_DIR_DEFAULT}}"
|
|
local use_inotify=0
|
|
local do_bootstrap=0
|
|
local do_linger=0
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--build-type) build_type="$2"; shift 2 ;;
|
|
--use-inotify) use_inotify=1; shift ;;
|
|
--ubuntu-bootstrap) do_bootstrap=1; shift ;;
|
|
--enable-linger) do_linger=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unknown option for install: $1" >&2; usage; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
case "$(uname -s)" in
|
|
Darwin)
|
|
install_macos "${build_type}" "${build_dir}" ;;
|
|
Linux)
|
|
if (( do_bootstrap == 1 )); then ubuntu_bootstrap; fi
|
|
if (( do_linger == 1 )); then enable_linger_linux; fi
|
|
install_linux "${build_type}" "${build_dir}" "${use_inotify}" ;;
|
|
*)
|
|
echo "Unsupported platform" >&2; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
sub_uninstall() {
|
|
case "$(uname -s)" in
|
|
Darwin)
|
|
local label="com.yaze.watchtest"
|
|
local plist_path="$HOME/Library/LaunchAgents/${label}.plist"
|
|
launchctl unload -w "$plist_path" 2>/dev/null || true
|
|
rm -f "$plist_path"
|
|
echo "Removed LaunchAgent ${label}"
|
|
;;
|
|
Linux)
|
|
local systemd_dir="$HOME/.config/systemd/user"
|
|
if is_systemd_available; then
|
|
systemctl_user stop yaze-watchtest.path 2>/dev/null || true
|
|
systemctl_user disable yaze-watchtest.path 2>/dev/null || true
|
|
systemctl_user stop yaze-watchtest.service 2>/dev/null || true
|
|
systemctl_user stop yaze-watchtest-inotify.service 2>/dev/null || true
|
|
systemctl_user disable yaze-watchtest-inotify.service 2>/dev/null || true
|
|
rm -f "${systemd_dir}/yaze-watchtest.service" "${systemd_dir}/yaze-watchtest.path" "${systemd_dir}/yaze-watchtest-inotify.service"
|
|
systemctl_user daemon-reload || true
|
|
echo "Removed systemd user units"
|
|
fi
|
|
stop_userland_agent
|
|
;;
|
|
*) echo "Unsupported platform" >&2; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
main() {
|
|
ensure_exec "$0"
|
|
local subcmd="${1:-}"; shift || true
|
|
case "${subcmd}" in
|
|
install) sub_install "$@" ;;
|
|
uninstall) sub_uninstall ;;
|
|
run-once) sub_run_once ;;
|
|
watch) sub_watch ;;
|
|
-h|--help|help|"") usage ;;
|
|
*) echo "Unknown subcommand: ${subcmd}" >&2; usage; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|
|
|
|
|