Files
.config/fish/environments/templates/cpp/bin/bw

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