227 lines
6.2 KiB
Bash
Executable File
227 lines
6.2 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Watch-build script for automatic rebuilding and restarting
|
|
# Monitors source files and runs 'make prod' when changes
|
|
# are detected
|
|
|
|
set -e
|
|
|
|
usage() {
|
|
echo "Usage: $(basename "$0") [MAKE_COMMAND...]"
|
|
echo "If MAKE_COMMAND is omitted, the default is: 'make clean && make prod'"
|
|
echo "Example:"
|
|
echo " $(basename "$0") \"make debug\""
|
|
exit 0
|
|
}
|
|
|
|
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
|
usage
|
|
fi
|
|
|
|
if [[ $# -gt 0 ]]; then
|
|
MAKE_CMD="$*"
|
|
else
|
|
MAKE_CMD="make clean && make prod"
|
|
fi
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Process ID of the running program
|
|
DEV_PID=""
|
|
|
|
cleanup() {
|
|
echo -e "\n${YELLOW}Cleaning up...${NC}"
|
|
|
|
if [[ -n "$DEV_PID" ]]; then
|
|
echo -e "${YELLOW}Stopping running program (PID: $DEV_PID)${NC}"
|
|
kill $DEV_PID 2>/dev/null || true
|
|
wait $DEV_PID 2>/dev/null || true
|
|
fi
|
|
|
|
exit 0
|
|
}
|
|
|
|
# Trap cleanup on script exit
|
|
trap cleanup SIGINT SIGTERM EXIT
|
|
|
|
build_and_run() {
|
|
echo -e "${BLUE}Building project...${NC}"
|
|
echo -e "${BLUE}Running build command: ${YELLOW}$MAKE_CMD${NC}"
|
|
|
|
# Stop the currently running program if it exists
|
|
if [[ -n "$DEV_PID" ]]; then
|
|
echo -e "${YELLOW}Stopping running program (PID: $DEV_PID)${NC}"
|
|
kill $DEV_PID 2>/dev/null || true
|
|
wait $DEV_PID 2>/dev/null || true
|
|
DEV_PID=""
|
|
fi
|
|
|
|
# Build the project using the provided command and capture output
|
|
TMPOUT=$(mktemp)
|
|
# Record build start time (epoch seconds) to filter processes started during the build
|
|
BUILD_START_TS=$(date +%s)
|
|
if bash -c "$MAKE_CMD" 2>&1 | tee "$TMPOUT"; then
|
|
echo -e "${GREEN}Build successful! Determining output binary...${NC}"
|
|
|
|
# Try to find the output file from the last '-o <file>' in build output
|
|
# Use portable awk parsing to avoid non-GNU grep issues on some systems
|
|
OUT_FILE=$(awk '{
|
|
for(i=1;i<=NF;i++){
|
|
if($i=="-o"){ if(i+1<=NF) print $(i+1) }
|
|
else if($i ~ /^-o/){ print substr($i,3) }
|
|
}
|
|
}' "$TMPOUT" | tail -n1)
|
|
|
|
# If not found, fall back to newest file in out/
|
|
if [[ -z "$OUT_FILE" ]]; then
|
|
OUT_FILE=$(ls -t out/* 2>/dev/null | head -n1 || true)
|
|
fi
|
|
|
|
# Final fallback to previous default
|
|
if [[ -z "$OUT_FILE" ]]; then
|
|
OUT_FILE="./out/prod"
|
|
fi
|
|
|
|
# Normalize path (if relative and doesn't start with ./ or /, prefix ./)
|
|
if [[ "$OUT_FILE" != /* && "$OUT_FILE" != ./* ]]; then
|
|
OUT_FILE="./$OUT_FILE"
|
|
fi
|
|
|
|
echo -e "${BLUE}Resolved output binary: ${YELLOW}$OUT_FILE${NC}"
|
|
|
|
# If the build itself already started the program, detect an existing PID
|
|
EXISTING_PID=""
|
|
|
|
# Gather candidate PIDs by matching the basename
|
|
if command -v pgrep &> /dev/null; then
|
|
CANDIDS=$(pgrep -f "$(basename "$OUT_FILE")" || true)
|
|
else
|
|
CANDIDS=$(ps aux | grep "$(basename "$OUT_FILE")" | grep -v grep | awk '{print $2}' || true)
|
|
fi
|
|
|
|
if [[ -n "$CANDIDS" ]]; then
|
|
# Resolve the canonical path of the output binary if possible
|
|
if command -v realpath &> /dev/null; then
|
|
REAL_OUT=$(realpath "$OUT_FILE" 2>/dev/null || true)
|
|
else
|
|
REAL_OUT=$(readlink -f "$OUT_FILE" 2>/dev/null || true)
|
|
fi
|
|
|
|
for pid in $CANDIDS; do
|
|
# Skip non-numeric entries
|
|
if ! [[ "$pid" =~ ^[0-9]+$ ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Check process start time and ignore ones that started before the build
|
|
# Use ps lstart to get human-readable start time and convert to epoch
|
|
PID_LSTART=$(ps -o lstart= -p "$pid" 2>/dev/null || true)
|
|
if [[ -n "$PID_LSTART" ]]; then
|
|
PID_EPOCH=$(date -d "$PID_LSTART" +%s 2>/dev/null || true)
|
|
if [[ -n "$PID_EPOCH" && "$PID_EPOCH" -lt "$BUILD_START_TS" ]]; then
|
|
# process started before build, skip
|
|
continue
|
|
fi
|
|
fi
|
|
# Prefer checking /proc/<pid>/exe for an exact match (Linux)
|
|
if [[ -r "/proc/$pid/exe" ]]; then
|
|
PID_EXE=$(readlink -f "/proc/$pid/exe" 2>/dev/null || true)
|
|
if [[ -n "$PID_EXE" && -n "$REAL_OUT" && "$PID_EXE" = "$REAL_OUT" ]]; then
|
|
EXISTING_PID=$pid
|
|
break
|
|
fi
|
|
fi
|
|
|
|
# Fallback: check the process cmdline contains the output path or basename
|
|
if [[ -r "/proc/$pid/cmdline" ]]; then
|
|
CMDLINE=$(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null || true)
|
|
if [[ -n "$CMDLINE" && ( "$CMDLINE" = *"$OUT_FILE"* || "$CMDLINE" = *"$(basename "$OUT_FILE")"* ) ]]; then
|
|
EXISTING_PID=$pid
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# if [[ -n "$EXISTING_PID" ]]; then
|
|
# DEV_PID=$EXISTING_PID
|
|
# echo -e "${YELLOW}Program already running (PID: $DEV_PID). Reusing it.${NC}"
|
|
# else
|
|
if [[ -x "$OUT_FILE" || -f "$OUT_FILE" ]]; then
|
|
# Start the program in background
|
|
"$OUT_FILE" &
|
|
DEV_PID=$!
|
|
echo -e "${GREEN}Program started with PID: $DEV_PID${NC}"
|
|
else
|
|
echo -e "${RED}Resolved output '$OUT_FILE' does not exist or is not executable.${NC}"
|
|
DEV_PID=""
|
|
fi
|
|
# fi
|
|
|
|
# clean up temp
|
|
rm -f "$TMPOUT" || true
|
|
else
|
|
echo -e "${RED}build failed${NC}"
|
|
DEV_PID=""
|
|
rm -f "$TMPOUT" || true
|
|
fi
|
|
}
|
|
|
|
clear_console() {
|
|
if command -v tput &> /dev/null; then
|
|
tput reset
|
|
elif command -v clear &> /dev/null; then
|
|
clear
|
|
else
|
|
printf '\033c'
|
|
fi
|
|
}
|
|
|
|
check_dependencies() {
|
|
if ! command -v inotifywait &> /dev/null; then
|
|
echo -e "${RED}error:${NC} inotifywait is not installed."
|
|
echo -e "Please install ${YELLOW}inotify-tools${NC}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
clear_console
|
|
|
|
check_dependencies
|
|
|
|
echo -e "${GREEN}watching on changes...${NC}"
|
|
echo -e "monitoring: ${BLUE}src/, lib/, makefile${NC}"
|
|
echo ""
|
|
|
|
build_and_run
|
|
|
|
# Watch for changes
|
|
while true; do
|
|
inotifywait -e modify,create,delete,move \
|
|
--recursive \
|
|
--quiet \
|
|
src/ lib/ makefile 2>/dev/null \
|
|
|| {
|
|
echo -e "${RED}error:${NC} Failed to watch directories"
|
|
exit 1
|
|
}
|
|
|
|
echo -e "\n${BLUE}file change detected, rebuilding${NC}"
|
|
|
|
# Small delay to handle rapid successive changes
|
|
sleep 0.5
|
|
|
|
clear_console
|
|
build_and_run
|
|
echo -e "${BLUE}watching for changes${NC}"
|
|
done
|
|
}
|
|
|
|
main
|