From 56960585aff62b66c5176cfa6fc6e9cadf773db5 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 9 Mar 2026 23:07:33 +0200 Subject: [PATCH] build(Makefile): add per-target timing via SHELL wrapper (#22862) pre-commit and pre-push only reported total elapsed time at the end, making it hard to identify which jobs are slow. Add a `MAKE_TIMED=1` mode that replaces `SHELL` with a wrapper (`scripts/lib/timed-shell.sh`) to print wall-clock time for each recipe. pre-commit and pre-push enable this on their sub-makes. Ad-hoc use: `make MAKE_TIMED=1 test` --- Makefile | 18 +++++++++++++---- scripts/lib/timed-shell.sh | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100755 scripts/lib/timed-shell.sh diff --git a/Makefile b/Makefile index cc4a02edf4..dcf0eb801b 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,16 @@ SHELL := bash .SHELLFLAGS := -ceu .ONESHELL: +# When MAKE_TIMED=1, replace SHELL with a wrapper that prints +# elapsed wall-clock time for each recipe. pre-commit and pre-push +# set this on their sub-makes so every parallel job reports its +# duration. Ad-hoc usage: make MAKE_TIMED=1 test +ifdef MAKE_TIMED +SHELL := $(CURDIR)/scripts/lib/timed-shell.sh +.SHELLFLAGS = $@ -ceu +export MAKE_TIMED +endif + # This doesn't work on directories. # See https://stackoverflow.com/questions/25752543/make-delete-on-error-for-directory-targets .DELETE_ON_ERROR: @@ -753,10 +763,10 @@ endef pre-commit: start=$$(date +%s) echo "=== Phase 1/2: gen + fmt ===" - $(MAKE) -j$(PARALLEL_JOBS) --output-sync=target gen fmt + $(MAKE) -j$(PARALLEL_JOBS) --output-sync=target MAKE_TIMED=1 gen fmt $(check-unstaged) echo "=== Phase 2/2: lint + build ===" - $(MAKE) -j$(PARALLEL_JOBS) --output-sync=target \ + $(MAKE) -j$(PARALLEL_JOBS) --output-sync=target MAKE_TIMED=1 \ lint \ lint/typos \ build/coder-slim_$(GOOS)_$(GOARCH)$(GOOS_BIN_EXT) @@ -767,10 +777,10 @@ pre-commit: pre-push: start=$$(date +%s) echo "=== Phase 1/2: gen + fmt + postgres ===" - $(MAKE) -j$(PARALLEL_JOBS) --output-sync=target gen fmt test-postgres-docker + $(MAKE) -j$(PARALLEL_JOBS) --output-sync=target MAKE_TIMED=1 gen fmt test-postgres-docker $(check-unstaged) echo "=== Phase 2/2: lint + build + test ===" - $(MAKE) -j$(PARALLEL_JOBS) --output-sync=target \ + $(MAKE) -j$(PARALLEL_JOBS) --output-sync=target MAKE_TIMED=1 \ lint \ lint/typos \ build/coder-slim_$(GOOS)_$(GOARCH)$(GOOS_BIN_EXT) \ diff --git a/scripts/lib/timed-shell.sh b/scripts/lib/timed-shell.sh new file mode 100755 index 0000000000..b82ba882e1 --- /dev/null +++ b/scripts/lib/timed-shell.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# timed-shell.sh wraps bash with per-target wall-clock timing. +# +# Recipe invocation: timed-shell.sh -ceu +# $(shell ...) calls: timed-shell.sh -c +# +# Enable via Makefile: +# SHELL := $(CURDIR)/scripts/lib/timed-shell.sh +# .SHELLFLAGS = $@ -ceu +# +# $(shell ...) uses SHELL but passes -c directly, not .SHELLFLAGS. +# Detect this and delegate to bash without timing output. +if [[ $1 == -* ]]; then + exec bash "$@" +fi + +set -eu + +target=$1 +shift + +bold=$(tput bold 2>/dev/null) || true +green=$(tput setaf 2 2>/dev/null) || true +red=$(tput setaf 1 2>/dev/null) || true +reset=$(tput sgr0 2>/dev/null) || true + +start=$(date +%s) +echo "${bold}==> ${target}${reset}" + +set +e +bash "$@" +rc=$? +set -e + +elapsed=$(($(date +%s) - start)) +if ((rc == 0)); then + echo "${bold}${green}==> ${target} completed in ${elapsed}s${reset}" +else + echo "${bold}${red}==> ${target} FAILED after ${elapsed}s${reset}" >&2 + exit $rc +fi