From cde3170368fdbec33ea3d315f8d356e22ff6ee9b Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Mon, 17 Nov 2025 12:05:10 -0600 Subject: [PATCH] SERVER-113309 direct bazel wrapper output to a logfile (#43855) GitOrigin-RevId: 403948b8ec293e41032aef2b5b5685fac00db6db --- bazel/wrapper_hook/engflow_check.py | 53 ++++++++- tools/bazel | 94 ++++++++++++---- tools/bazel.bat | 166 ++++++++++++++++++++-------- 3 files changed, 246 insertions(+), 67 deletions(-) diff --git a/bazel/wrapper_hook/engflow_check.py b/bazel/wrapper_hook/engflow_check.py index 12d19f64acb..a3d5ed7e327 100644 --- a/bazel/wrapper_hook/engflow_check.py +++ b/bazel/wrapper_hook/engflow_check.py @@ -10,10 +10,61 @@ sys.path.append(REPO_ROOT) from bazel.wrapper_hook.wrapper_debug import wrapper_debug +def get_terminal_stream(fd_env_var: str): + """Return a Python file object for the original terminal FD.""" + fd_str = os.environ.get(fd_env_var) + if not fd_str: + return None + + # Handle Windows CON device + if fd_str == "CON": + # On Windows, open CON device for console output + # Use the appropriate stream based on the variable name + if "STDOUT" in fd_env_var: + try: + return open("CON", "w", buffering=1) + except (OSError, IOError): + return None + elif "STDERR" in fd_env_var: + try: + return open("CON", "w", buffering=1) + except (OSError, IOError): + return None + return None + + # Handle Unix file descriptors + if fd_str.isdigit(): + fd = int(fd_str) + try: + return os.fdopen(fd, "w", buffering=1) + except (OSError, ValueError): + return None + + return None + + def setup_auth_wrapper(): from buildscripts.bazel_rules_mongo.engflow_auth.engflow_auth import setup_auth - setup_auth(verbose=False) + term_out = get_terminal_stream("MONGO_WRAPPER_STDOUT_FD") + term_err = get_terminal_stream("MONGO_WRAPPER_STDERR_FD") + + # Save current stdout/stderr + old_stdout = sys.stdout + old_stderr = sys.stderr + + try: + if term_out: + sys.stdout = term_out + if term_err: + sys.stderr = term_err + + setup_auth(verbose=False) + + finally: + # Restore original stdout/stderr to whatever wrapper has + sys.stdout = old_stdout + sys.stderr = old_stderr def engflow_auth(args): diff --git a/tools/bazel b/tools/bazel index af30a7eae01..7c203b2d759 100755 --- a/tools/bazel +++ b/tools/bazel @@ -119,6 +119,12 @@ done if [ -z $current_bazel_command ]; then skip_python=1 fi +SLOW_PATH=0 +if [[ "$skip_python" == "0" ]]; then + # We'll do wrapper hook / python installation + SLOW_PATH=1 +fi + if [ "$skip_python" == "0" ]; then # known list of commands to skip @@ -133,9 +139,49 @@ if [ "$skip_python" == "0" ]; then done fi -if [ "$skip_python" == "1" ]; then - exec "$bazel_real" $@ -fi +if [ "$skip_python" == "1" ]; then + # Fast path: no wrapper output, run Bazel directly to terminal + exec "$bazel_real" "$@" +fi + + +if [[ "$SLOW_PATH" == "1" ]]; then + ORIGINAL_ARGS=("$@") + + # Save original terminal output FDs + exec 3>&1 4>&2 + export MONGO_WRAPPER_STDOUT_FD=3 + export MONGO_WRAPPER_STDERR_FD=4 + + LOG_DIR=${MONGO_BAZEL_LOG_DIR:-"$REPO_ROOT/.bazel_logs"} + mkdir -p "$LOG_DIR" + LOGFILE="${LOG_DIR}/bazel_wrapper_$(date +%Y%m%d_%H%M%S)_$$.log" + + # Redirect stdout/stderr to logfile + exec >"$LOGFILE" 2>&1 + + WRAPPER_START_EPOCH=$(date +%s) + + GREEN='\033[0;32m' + RED='\033[1;31m' + NO_COLOR='\033[0m' + + echo -e "${GREEN}INFO:${NO_COLOR} running wrapper hook..." >&4 + + print_summary() { + local exit_code=$? + local end_epoch=$(date +%s) + local duration=$(( end_epoch - WRAPPER_START_EPOCH )) + if [[ $exit_code -ne 0 ]]; then + echo -e "${RED}ERROR:${NO_COLOR} wrapper hook failed: " >&4 + cat "$LOGFILE" >&4 + else + echo "Bazel wrapper finished. Exit code: ${exit_code}. Duration: ${duration}s" >&4 + fi + } + + trap 'print_summary' EXIT +fi # find existing python installs cur_dir=$(basename $REPO_ROOT) @@ -154,11 +200,15 @@ if [[ "$python" = "" ]] || [ ! -f $python ]; then >&2 echo "python prereq missing, using bazel to install python..." >&2 $bazel_real build --bes_backend= --bes_results_url= @py_${os}_${ARCH}//:all if [[ $? != 0 ]]; then - if [[ ! -z "$CI" ]] || [[ $MONGO_BAZEL_WRAPPER_FALLBACK == 1 ]]; then - >&2 echo "wrapper script failed to install python! falling back to normal bazel call..." - exec "$bazel_real" $@ - else - exit $? + >&2 $bazel_real build --config=local @py_${os}_${ARCH}//:all + if [[ $? != 0 ]]; then + if [[ ! -z "$CI" ]] || [[ $MONGO_BAZEL_WRAPPER_FALLBACK == 1 ]]; then + >&2 echo "wrapper script failed to install python! falling back to normal bazel call..." + "$bazel_real" "$@" + exit $? + else + exit $? + fi fi fi fi @@ -201,7 +251,8 @@ fi if [[ $exit_code != 0 ]]; then if [[ ! -z "$CI" ]] || [[ $MONGO_BAZEL_WRAPPER_FALLBACK == 1 ]]; then >&2 echo "wrapper script failed! falling back to normal bazel call..." - exec "$bazel_real" $@ + "$bazel_real" "$@" + exit $? else exit $? fi @@ -221,14 +272,17 @@ if [[ $MONGO_BAZEL_WRAPPER_DEBUG == 1 ]] && [[ $autocomplete_query == 0 ]]; then >&2 echo "[WRAPPER_HOOK_DEBUG]: wrapper hook script took $runtime seconds" fi -if [[ $autocomplete_query == 1 ]]; then - plus_targets=$(&2 $python $REPO_ROOT/bazel/wrapper_hook/post_bazel_hook.py $bazel_real) - exit $bazel_exit_code -fi +if [[ $autocomplete_query == 1 ]]; then + plus_targets=$(&3 +else + trap - EXIT + # Slow path: restore stdout/stderr so Bazel prints normally + exec 1>&3 2>&4 + + $bazel_real "${new_args[@]}" + bazel_exit_code=$? + ( >&2 $python $REPO_ROOT/bazel/wrapper_hook/post_bazel_hook.py $bazel_real ) + exit $bazel_exit_code +fi diff --git a/tools/bazel.bat b/tools/bazel.bat index edc5e1c04ba..a81de9ba5b8 100644 --- a/tools/bazel.bat +++ b/tools/bazel.bat @@ -1,6 +1,36 @@ @echo off setlocal EnableDelayedExpansion +rem Enable ANSI escape codes for colors (Windows 10+) +rem Get ESC character for ANSI colors (set once at the start) +for /f %%A in ('echo prompt $E ^| cmd') do set "ESC=%%A" + +rem Enable virtual terminal processing for ANSI escape codes (Windows 10+) +rem Create a temporary PowerShell script to enable VT processing +set "VT_SCRIPT=%TEMP%\bazel_vt_%RANDOM%.ps1" +( + echo [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + echo $signature = @' + echo [DllImport("kernel32.dll", SetLastError=true^)] + echo public static extern IntPtr GetStdHandle(int nStdHandle^); + echo [DllImport("kernel32.dll", SetLastError=true^)] + echo public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode^); + echo [DllImport("kernel32.dll", SetLastError=true^)] + echo public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode^); + echo '@ + echo $type = Add-Type -MemberDefinition $signature -Name Win32Utils -Namespace Console -PassThru + echo $STD_OUTPUT_HANDLE = -11 + echo $STD_ERROR_HANDLE = -12 + echo $ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + echo $hOut = $type::GetStdHandle($STD_OUTPUT_HANDLE^) + echo $hErr = $type::GetStdHandle($STD_ERROR_HANDLE^) + echo $mode = 0 + echo if ($type::GetConsoleMode($hOut, [ref]$mode^)^) { $null = $type::SetConsoleMode($hOut, $mode -bor $ENABLE_VIRTUAL_TERMINAL_PROCESSING^) } + echo if ($type::GetConsoleMode($hErr, [ref]$mode^)^) { $null = $type::SetConsoleMode($hErr, $mode -bor $ENABLE_VIRTUAL_TERMINAL_PROCESSING^) } +) > "%VT_SCRIPT%" +>nul 2>&1 powershell -NoProfile -ExecutionPolicy Bypass -File "%VT_SCRIPT%" +del "%VT_SCRIPT%" >nul 2>&1 + echo common --//bazel/config:running_through_bazelisk > .bazelrc.bazelisk set REPO_ROOT=%~dp0.. @@ -40,22 +70,51 @@ if !skip_python!=="0" if !current_bazel_command!=="info" set skip_python="1" if !skip_python!=="1" ( "%BAZEL_REAL%" %* - exit %ERRORLEVEL% + exit /b %ERRORLEVEL% ) + +rem === Set up logging for SLOW_PATH (equivalent to bash SLOW_PATH=1) === +rem Where the log will be stored +set "LOG_DIR=%REPO_ROOT%\.bazel_logs" +if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" +set "LOGFILE=%LOG_DIR%\bazel_wrapper_%DATE:/=_%_%TIME::=_%_%RANDOM%.log" + +rem Set up environment variables for terminal output (for engflow_check.py) +rem On Windows, we use CON device for console output +rem Note: Windows doesn't support file descriptor duplication like Unix, +rem so we'll set these to indicate console output should go to CON +set "MONGO_WRAPPER_STDOUT_FD=CON" +set "MONGO_WRAPPER_STDERR_FD=CON" + +rem === Start timing === +set STARTTIME=%TIME% + +rem === Capture output to logfile starting now === +rem Note: We redirect Python installation and wrapper_hook.py output to logfile + REM find existing python installs set python="" if exist %REPO_ROOT%\bazel-%cur_dir% ( call :find_pyhon ) if not exist "!python!" ( - echo python prereq missing, using bazel to install python... 1>&2 - "%BAZEL_REAL%" build --bes_backend= --bes_results_url= @py_windows_x86_64//:all 1>&2 - - if %ERRORLEVEL% NEQ 0 ( - if "%CI%"=="" if "%MONGO_BAZEL_WRAPPER_FALLBACK%"=="" exit %ERRORLEVEL% - echo wrapper script failed to install python! falling back to normal bazel call... 1>&2 - "%BAZEL_REAL%" %* - exit %ERRORLEVEL% + ( + echo python prereq missing, using bazel to install python... + "%BAZEL_REAL%" build --bes_backend= --bes_results_url= @py_windows_x86_64//:all + if !ERRORLEVEL! NEQ 0 ( + "%BAZEL_REAL%" build --config=local @py_windows_x86_64//:all + if !ERRORLEVEL! NEQ 0 ( + if "%CI%"=="" if "%MONGO_BAZEL_WRAPPER_FALLBACK%"=="" exit /b !ERRORLEVEL! + echo wrapper script failed to install python! falling back to normal bazel call... + "%BAZEL_REAL%" %* + exit /b !ERRORLEVEL! + ) + ) + ) > "%LOGFILE%" 2>&1 + if !ERRORLEVEL! NEQ 0 ( + echo %ESC%[1;31mERROR:%ESC%[0m Python installation failed: + type "%LOGFILE%" + exit /b !ERRORLEVEL! ) ) @@ -63,18 +122,55 @@ if not exist "!python!" ( call :find_pyhon ) -SET STARTTIME=%TIME% - +rem === Call Python wrapper, log to file === set "MONGO_BAZEL_WRAPPER_ARGS=%tmp%\bat~%RANDOM%.tmp" echo "" > %MONGO_BAZEL_WRAPPER_ARGS% -%python% %REPO_ROOT%/bazel/wrapper_hook/wrapper_hook.py "%BAZEL_REAL%" %* 1>&2 -if %ERRORLEVEL% NEQ 0 ( - if "%CI%"=="" if "%MONGO_BAZEL_WRAPPER_FALLBACK%"=="" exit %ERRORLEVEL% - echo wrapper script failed! falling back to normal bazel call... 1>&2 - "%BAZEL_REAL%" %* - exit %ERRORLEVEL% + +rem Print info message to terminal (equivalent to bash echo to FD 4) +echo %ESC%[0;32mINFO:%ESC%[0m running wrapper hook... 1>&2 + +( + %python% %REPO_ROOT%/bazel/wrapper_hook/wrapper_hook.py "%BAZEL_REAL%" %* +) >> "%LOGFILE%" 2>&1 + +set "exit_code=%ERRORLEVEL%" + +rem Linter fails preempt bazel run (exit code 3) +if %exit_code% EQU 3 ( + echo %ESC%[0;31mERROR:%ESC%[0m Linter run failed, see details above 1>&2 + echo %ESC%[0;32mINFO:%ESC%[0m Run the following to try to auto-fix the errors: 1>&2 + echo. 1>&2 + echo bazel run lint --fix 1>&2 + exit /b %exit_code% ) +rem Calculate duration for summary (equivalent to bash print_summary) +set ENDTIME=%TIME% +FOR /F "tokens=1-4 delims=:.," %%a IN ("%STARTTIME%") DO ( + SET /A "start=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100" +) +FOR /F "tokens=1-4 delims=:.," %%a IN ("%ENDTIME%") DO ( + SET /A "end=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100" +) +SET /A elapsed=end-start +SET /A hh=elapsed/(60*60*100), rest=elapsed%%(60*60*100), mm=rest/(60*100), rest%%=60*100, ss=rest/100, cc=rest%%100 +IF %hh% lss 10 SET hh=0%hh% +IF %mm% lss 10 SET mm=0%mm% +IF %ss% lss 10 SET ss=0%ss% +IF %cc% lss 10 SET cc=0%cc% + +if %exit_code% NEQ 0 ( + echo %ESC%[1;31mERROR:%ESC%[0m wrapper hook failed: 1>&2 + type "%LOGFILE%" 1>&2 + + if "%CI%"=="" if "%MONGO_BAZEL_WRAPPER_FALLBACK%"=="" exit /b %exit_code% + echo wrapper script failed! falling back to normal bazel call... 1>&2 + "%BAZEL_REAL%" %* + exit /b %ERRORLEVEL% +) + +rem === Read new args back in === +set "new_args=" for /F "delims=" %%a in (%MONGO_BAZEL_WRAPPER_ARGS%) do ( set str="%%a" call set str=!str: =^ ! @@ -82,43 +178,21 @@ for /F "delims=" %%a in (%MONGO_BAZEL_WRAPPER_ARGS%) do ( ) del %MONGO_BAZEL_WRAPPER_ARGS% -REM Final Calculations -SET ENDTIME=%TIME% -FOR /F "tokens=1-4 delims=:.," %%a IN ("%STARTTIME%") DO ( - SET /A "start=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100" -) - -FOR /F "tokens=1-4 delims=:.," %%a IN ("%ENDTIME%") DO ( - SET /A "end=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100" -) - -REM Calculate the elapsed time by subtracting values -SET /A elapsed=end-start - -REM Format the results for output -SET /A hh=elapsed/(60*60*100), rest=elapsed%%(60*60*100), mm=rest/(60*100), rest%%=60*100, ss=rest/100, cc=rest%%100 -IF %hh% lss 10 SET hh=0%hh% -IF %mm% lss 10 SET mm=0%mm% -IF %ss% lss 10 SET ss=0%ss% -IF %cc% lss 10 SET cc=0%cc% -SET DURATION=%mm%m and %ss%.%cc%s - -if "%MONGO_BAZEL_WRAPPER_DEBUG%"=="1" ( - ECHO [WRAPPER_HOOK_DEBUG]: wrapper hook script input args: %* 1>&2 - ECHO [WRAPPER_HOOK_DEBUG]: wrapper hook script new args: !new_args! 1>&2 - ECHO [WRAPPER_HOOK_DEBUG]: wrapper hook script took %DURATION% 1>&2 +if "%MONGO_BAZEL_WRAPPER_DEBUG%"=="1" ( + echo [WRAPPER_HOOK_DEBUG]: wrapper hook script input args: %* 1>&2 + echo [WRAPPER_HOOK_DEBUG]: wrapper hook script new args: !new_args! 1>&2 + echo [WRAPPER_HOOK_DEBUG]: wrapper hook script took %mm%m and %ss%.%cc%s 1>&2 ) "%BAZEL_REAL%" !new_args! +exit /b %ERRORLEVEL% -EXIT /B %ERRORLEVEL% :: Functions - :find_pyhon dir %REPO_ROOT% | C:\Windows\System32\find.exe "bazel-%cur_dir%" > %REPO_ROOT%\tmp_bazel_symlink_dir.txt for /f "tokens=2 delims=[" %%i in (%REPO_ROOT%\tmp_bazel_symlink_dir.txt) do set bazel_real_dir=%%i del %REPO_ROOT%\tmp_bazel_symlink_dir.txt set bazel_real_dir=!bazel_real_dir:~0,-1! -set python="!bazel_real_dir!\..\..\external\_main~setup_mongo_python_toolchains~py_windows_x86_64\dist\python.exe" -EXIT /B 0 \ No newline at end of file +set python="!bazel_real_dir!\..\..\external\_main~setup_mongo_python_toolchains~py_windows_x86_64\dist\python.exe" +exit /b 0 \ No newline at end of file