#!/usr/bin/env bash set -euo pipefail UNAME_OUT="$(uname -s 2>/dev/null || echo unknown)" RUNNING_IN_MINGW="false" case "$UNAME_OUT" in CYGWIN*|MSYS*|MINGW*) RUNNING_IN_MINGW="true" ;; esac to_host_path() { local posix_path="$1" if [[ "$RUNNING_IN_MINGW" == "true" ]]; then if command -v cygpath >/dev/null 2>&1; then cygpath -w "$posix_path" return fi if [[ "$posix_path" =~ ^/([a-zA-Z])/(.*)$ ]]; then local drive="${BASH_REMATCH[1]}" local remainder="${BASH_REMATCH[2]}" drive="$(printf '%s' "$drive" | tr '[:lower:]' '[:upper:]')" if [[ -n "$remainder" ]]; then printf '%s:/%s' "$drive" "$remainder" else printf '%s:/' "$drive" fi return fi fi printf '%s' "$posix_path" } TMP_FILES=() build_runner_pid="" heartbeat_pid="" cleanup() { if [[ -n "${build_runner_pid:-}" ]] && kill -0 "$build_runner_pid" >/dev/null 2>&1; then kill "$build_runner_pid" >/dev/null 2>&1 || true fi if [[ -n "${heartbeat_pid:-}" ]] && kill -0 "$heartbeat_pid" >/dev/null 2>&1; then kill "$heartbeat_pid" >/dev/null 2>&1 || true fi if [[ -n "${build_runner_pid:-}" ]]; then wait "$build_runner_pid" >/dev/null 2>&1 || true fi if [[ -n "${heartbeat_pid:-}" ]]; then wait "$heartbeat_pid" >/dev/null 2>&1 || true fi local file for file in "${TMP_FILES[@]:-}"; do if [[ -n "${file:-}" && -e "$file" ]]; then rm -f "$file" fi done } trap cleanup SIGINT SIGTERM EXIT SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$REPO_ROOT" LOG_DIR="$REPO_ROOT/.agents/logs" mkdir -p "$LOG_DIR" # Clean up old full build logs, keeping only the last 10 cleanup_old_logs() { local log_dir="$1" local keep_count=10 if [[ ! -d "$log_dir" ]]; then return 0 fi # Find all full-build-*.log files and sort by filename (which contains timestamp) # Filenames are in format: full-build-YYYYMMDD-HHMMSS.log # Sorting alphabetically gives chronological order (newest last) local files files=$(find "$log_dir" -maxdepth 1 -type f -name "full-build-*.log" 2>/dev/null | sort || true) if [[ -z "$files" ]]; then return 0 fi # Delete everything except the newest keep_count files; use POSIX-safe tail/awk local file_count file_count=$(printf '%s\n' "$files" | grep -c . || true) local delete_count=$((file_count - keep_count)) if ((delete_count > 0)); then local files_to_delete files_to_delete=$(printf '%s\n' "$files" | head -n "$delete_count") while IFS= read -r file; do if [[ -n "$file" && -f "$file" ]]; then rm -f "$file" fi done <<< "$files_to_delete" fi } cleanup_old_logs "$LOG_DIR" TIMESTAMP="$(date +%Y%m%d-%H%M%S)" LOG_FILE="$LOG_DIR/full-build-${TIMESTAMP}.log" LOG_FILE_FOR_DISPLAY="$LOG_FILE" if [[ "$RUNNING_IN_MINGW" == "true" ]]; then LOG_FILE_FOR_DISPLAY="$(to_host_path "$LOG_FILE")" fi BUILD_TIMEOUT_SECONDS="${QUIET_BUILD_TIMEOUT_SECONDS:-180}" if ! [[ "$BUILD_TIMEOUT_SECONDS" =~ ^[0-9]+$ ]]; then BUILD_TIMEOUT_SECONDS=180 fi HEARTBEAT_INTERVAL_SECONDS="${QUIET_BUILD_HEARTBEAT_SECONDS:-60}" if ! [[ "$HEARTBEAT_INTERVAL_SECONDS" =~ ^[0-9]+$ ]] || ((HEARTBEAT_INTERVAL_SECONDS < 1)); then HEARTBEAT_INTERVAL_SECONDS=60 fi MAVEN_FLAGS=( -B -ntp -Dstyle.color=never -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN ) EXTRA_MAVEN_ARGS=() if (($# > 0)); then EXTRA_MAVEN_ARGS=("$@") fi if ((${#EXTRA_MAVEN_ARGS[@]} > 0)); then printf 'Forwarding extra Maven args:' for arg in "${EXTRA_MAVEN_ARGS[@]}"; do printf ' %q' "$arg" done printf '\n' fi GOALS=(clean license:format formatter:format test install) MAVEN_CMD_PREFIX=(mvn) if [[ -x "./mvnw" ]]; then MAVEN_CMD_PREFIX=("./mvnw") echo "Using Maven Wrapper: ./mvnw" elif [[ -f "./mvnw" ]]; then MAVEN_CMD_PREFIX=(sh "./mvnw") echo "Using Maven Wrapper via sh: ./mvnw" else echo "Using system Maven from PATH: mvn" fi MVN_CMD=("${MAVEN_CMD_PREFIX[@]}" "${MAVEN_FLAGS[@]}") if ((${#EXTRA_MAVEN_ARGS[@]} > 0)); then MVN_CMD+=("${EXTRA_MAVEN_ARGS[@]}") fi MVN_CMD+=("${GOALS[@]}") echo "Running ta4j full build quietly..." echo "Full log: $LOG_FILE_FOR_DISPLAY" echo format_elapsed() { local total_seconds="$1" local hours=$((total_seconds / 3600)) local minutes=$(((total_seconds % 3600) / 60)) local seconds=$((total_seconds % 60)) if ((hours > 0)); then printf '%dh%02dm%02ds' "$hours" "$minutes" "$seconds" elif ((minutes > 0)); then printf '%dm%02ds' "$minutes" "$seconds" else printf '%ds' "$seconds" fi } should_print_line() { local text="$1" case "$text" in *"[ERROR]"*|*"[WARNING]"*|*"BUILD SUCCESS"*|*"BUILD FAILURE"*|*"Failed to execute goal"*|*"Reactor Summary"*) return 0 ;; *) return 1 ;; esac } trim_whitespace() { local value="$1" value="${value#"${value%%[![:space:]]*}"}" value="${value%"${value##*[![:space:]]}"}" printf '%s' "$value" } run_with_timeout() { local timeout_marker_file="$1" local timeout_seconds="$2" shift 2 : >"$timeout_marker_file" "$@" & local command_pid=$! ( local elapsed=0 while ((elapsed < timeout_seconds)); do sleep 1 if ! kill -0 "$command_pid" >/dev/null 2>&1; then exit 0 fi elapsed=$((elapsed + 1)) done if kill -0 "$command_pid" >/dev/null 2>&1; then echo "timeout" >"$timeout_marker_file" kill -TERM "$command_pid" >/dev/null 2>&1 || true sleep 5 if kill -0 "$command_pid" >/dev/null 2>&1; then kill -KILL "$command_pid" >/dev/null 2>&1 || true fi fi ) & local timeout_watcher_pid=$! set +e wait "$command_pid" local command_status=$? set -e wait "$timeout_watcher_pid" >/dev/null 2>&1 || true if [[ -s "$timeout_marker_file" ]]; then return 124 fi return "$command_status" } update_last_visible() { date +%s >"$LAST_VISIBLE_FILE" } heartbeat_worker() { local build_pid="$1" local start_time="$2" local heartbeat_interval="$3" while kill -0 "$build_pid" >/dev/null 2>&1; do sleep "$heartbeat_interval" if ! kill -0 "$build_pid" >/dev/null 2>&1; then break fi local now now="$(date +%s)" local last_visible="$start_time" if [[ -f "$LAST_VISIBLE_FILE" ]]; then last_visible="$(cat "$LAST_VISIBLE_FILE" 2>/dev/null || echo "$start_time")" fi if ! [[ "$last_visible" =~ ^[0-9]+$ ]]; then last_visible="$start_time" fi if ((now - last_visible >= heartbeat_interval)); then echo "[quiet-build] still running... ($(format_elapsed $((now - start_time))))" update_last_visible fi done } run_maven_build() { if ((BUILD_TIMEOUT_SECONDS > 0)); then run_with_timeout "$TIMEOUT_MARKER_FILE" "$BUILD_TIMEOUT_SECONDS" "${MVN_CMD[@]}" return $? fi "${MVN_CMD[@]}" } emit_suppressed_duplicates() { if [[ ! -s "$SUPPRESSED_DUPLICATES_FILE" ]]; then return 0 fi echo echo "[quiet-build] Suppressed duplicate warnings:" local line while IFS= read -r line || [[ -n "$line" ]]; do if grep -Fqx -- "$line" "$SUPPRESSED_REPORTED_FILE"; then continue fi local count count=$(grep -Fxc -- "$line" "$SUPPRESSED_DUPLICATES_FILE" || true) echo "[quiet-build] (${count} more) $(trim_whitespace "$line")" printf '%s\n' "$line" >>"$SUPPRESSED_REPORTED_FILE" done <"$SUPPRESSED_DUPLICATES_FILE" } extract_test_summary() { local total_run=0 local total_failures=0 local total_errors=0 local total_skipped=0 local has_aggregated="false" local fallback_summary="" local line while IFS= read -r line || [[ -n "$line" ]]; do if [[ "$line" =~ ^\[(INFO|WARNING)\][[:space:]]+Tests\ run:\ ([0-9]+),\ Failures:\ ([0-9]+),\ Errors:\ ([0-9]+),\ Skipped:\ ([0-9]+)[[:space:]]*$ ]]; then has_aggregated="true" total_run=$((total_run + ${BASH_REMATCH[2]})) total_failures=$((total_failures + ${BASH_REMATCH[3]})) total_errors=$((total_errors + ${BASH_REMATCH[4]})) total_skipped=$((total_skipped + ${BASH_REMATCH[5]})) elif [[ "$line" =~ Tests\ run:\ ([0-9]+),\ Failures:\ ([0-9]+),\ Errors:\ ([0-9]+),\ Skipped:\ ([0-9]+) ]]; then fallback_summary="Tests run: ${BASH_REMATCH[1]}, Failures: ${BASH_REMATCH[2]}, Errors: ${BASH_REMATCH[3]}, Skipped: ${BASH_REMATCH[4]}" fi done <"$LOG_FILE" if [[ "$has_aggregated" == "true" ]]; then printf 'Tests run: %d, Failures: %d, Errors: %d, Skipped: %d' "$total_run" "$total_failures" "$total_errors" "$total_skipped" return 0 fi if [[ -n "$fallback_summary" ]]; then printf '%s' "$fallback_summary" fi } LAST_VISIBLE_FILE="$(mktemp "${TMPDIR:-/tmp}/ta4j-quiet-build-last-visible.XXXXXX")" TMP_FILES+=("$LAST_VISIBLE_FILE") BUILD_START_TIME="$(date +%s)" echo "$BUILD_START_TIME" >"$LAST_VISIBLE_FILE" SEEN_VISIBLE_LINES_FILE="$(mktemp "${TMPDIR:-/tmp}/ta4j-quiet-build-seen-visible.XXXXXX")" TMP_FILES+=("$SEEN_VISIBLE_LINES_FILE") SUPPRESSED_DUPLICATES_FILE="$(mktemp "${TMPDIR:-/tmp}/ta4j-quiet-build-suppressed-duplicates.XXXXXX")" TMP_FILES+=("$SUPPRESSED_DUPLICATES_FILE") SUPPRESSED_REPORTED_FILE="$(mktemp "${TMPDIR:-/tmp}/ta4j-quiet-build-suppressed-reported.XXXXXX")" TMP_FILES+=("$SUPPRESSED_REPORTED_FILE") OUTPUT_FIFO="$(mktemp "${TMPDIR:-/tmp}/ta4j-quiet-build-output.XXXXXX")" TMP_FILES+=("$OUTPUT_FIFO") rm -f "$OUTPUT_FIFO" mkfifo "$OUTPUT_FIFO" TIMEOUT_MARKER_FILE="" if ((BUILD_TIMEOUT_SECONDS > 0)); then TIMEOUT_MARKER_FILE="$(mktemp "${TMPDIR:-/tmp}/ta4j-quiet-build-timeout-marker.XXXXXX")" TMP_FILES+=("$TIMEOUT_MARKER_FILE") fi : >"$LOG_FILE" : >"$SEEN_VISIBLE_LINES_FILE" : >"$SUPPRESSED_DUPLICATES_FILE" : >"$SUPPRESSED_REPORTED_FILE" set +o pipefail set +e run_maven_build >"$OUTPUT_FIFO" 2>&1 & build_runner_pid=$! heartbeat_worker "$build_runner_pid" "$BUILD_START_TIME" "$HEARTBEAT_INTERVAL_SECONDS" & heartbeat_pid=$! while IFS= read -r line || [[ -n "$line" ]]; do printf '%s\n' "$line" >>"$LOG_FILE" if ! should_print_line "$line"; then continue fi if [[ "$line" == *"Tests run:"* && "$line" == *"Time elapsed"* ]]; then continue fi if grep -Fqx -- "$line" "$SEEN_VISIBLE_LINES_FILE"; then printf '%s\n' "$line" >>"$SUPPRESSED_DUPLICATES_FILE" else printf '%s\n' "$line" >>"$SEEN_VISIBLE_LINES_FILE" printf '%s\n' "$line" update_last_visible fi done <"$OUTPUT_FIFO" filter_status=$? wait "$build_runner_pid" mvn_status=$? set -e set -o pipefail kill "$heartbeat_pid" >/dev/null 2>&1 || true wait "$heartbeat_pid" >/dev/null 2>&1 || true emit_suppressed_duplicates if ((mvn_status != 0 || filter_status != 0)); then echo if ((mvn_status == 124)); then echo "Maven build timed out after ${BUILD_TIMEOUT_SECONDS}s. Inspect the full log at: $LOG_FILE_FOR_DISPLAY" exit 1 fi echo "Maven build failed (mvn=$mvn_status, filter=$filter_status). Inspect the full log at: $LOG_FILE_FOR_DISPLAY" exit 1 fi summary="$(extract_test_summary)" echo if [[ -n "$summary" ]]; then echo "$summary" else echo "Tests run summary not found in log; see $LOG_FILE_FOR_DISPLAY" fi echo "Full build log saved to: $LOG_FILE_FOR_DISPLAY"