Created
January 17, 2026 12:33
-
-
Save cumulus13/b99c0adda42f83bbd578acd0fccf24bb to your computer and use it in GitHub Desktop.
Robust Go project build script with notifications
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| :: File: GoBuild.bat | |
| :: Author: Hadi Cahyadi <cumulus13@gmail.com> | |
| :: Date: 2026-01-17 | |
| :: Description: Robust Go project build script with notifications | |
| :: License: MIT | |
| :: Usage: | |
| :: gobuild main.go -o app.exe | |
| :: gobuild mydir\ -o app.exe | |
| :: gobuild -o app.exe main.go | |
| :: gobuild --output=app.exe --tags=prod --ldflags="-s -w" | |
| :: gobuild -h | |
| @echo off | |
| setlocal enabledelayedexpansion | |
| :: Jump to main immediately to skip function definitions | |
| GOTO :main | |
| :: ============================================================================ | |
| :: Helper Functions | |
| :: ============================================================================ | |
| :show_help | |
| echo GoBuild - Robust Go Build Script v1.0.0 | |
| echo. | |
| echo Usage: | |
| echo gobuild [source] [options] | |
| echo. | |
| echo Arguments: | |
| echo source Source file or directory (default: current directory) | |
| echo. | |
| echo Options: | |
| echo -o, --output FILE Output binary name (required) | |
| echo -t, --tags TAGS Build tags (comma-separated) | |
| echo -l, --ldflags FLAGS Linker flags | |
| echo --os OS Target OS (windows, linux, darwin) | |
| echo --arch ARCH Target architecture (amd64, 386, arm64) | |
| echo -c, --clean Clean build cache before building | |
| echo -r, --run-tests Run tests before building | |
| echo -v, --verbose Verbose output | |
| echo -z, --compress Compress binary with UPX after build | |
| echo -h, --help Show this help message | |
| echo. | |
| echo Examples: | |
| echo gobuild main.go -o app.exe | |
| echo gobuild mydir\ -o app.exe | |
| echo gobuild -o app.exe --tags=prod --ldflags="-s -w" | |
| echo gobuild --os=linux --arch=amd64 -o myapp | |
| echo gobuild -c -r -o app.exe | |
| GOTO :EOF | |
| :get_timestamp | |
| for /f "tokens=* delims=" %%a in ('date /t') do set current_date=%%a | |
| for /f "tokens=* delims=" %%b in ('time /t') do set current_time=%%b | |
| GOTO :EOF | |
| :log_info | |
| if %VERBOSE%==1 echo [INFO] %~1 | |
| GOTO :EOF | |
| :log_error | |
| echo [ERROR] %~1 | |
| GOTO :EOF | |
| :send_notification | |
| set notif_type=%~1 | |
| set notif_title=%~2 | |
| set notif_message=%~3 | |
| set notif_priority=%~4 | |
| call :get_timestamp | |
| if %NTFY_ENABLED%==1 ( | |
| if exist "%NTFY_PATH%" ( | |
| "%NTFY_PATH%" pub -t "Go: %notif_type% %current_date% %current_time%" -m "[%current_date% %current_time%] %notif_message%" -i "%NTFY_ICON%" %NTFY_URL% >nul 2>&1 | |
| ) | |
| ) | |
| if %GROWL_ENABLED%==1 ( | |
| where sendgrowl >nul 2>&1 | |
| if !errorlevel!==0 ( | |
| sendgrowl GoBuild %notif_type% "%notif_title%" "%notif_message%" -p %notif_priority% >nul 2>&1 | |
| ) | |
| ) | |
| GOTO :EOF | |
| :check_go_installation | |
| where go >nul 2>&1 | |
| if errorlevel 1 ( | |
| call :log_error "Go is not installed or not in PATH" | |
| call :send_notification "FAILED" "Build Failed" "❌ Go is not installed or not in PATH" 2 | |
| exit /b 1 | |
| ) | |
| GOTO :EOF | |
| :clean_build_cache | |
| call :log_info "Cleaning build cache..." | |
| go clean -cache -modcache -testcache 2>nul | |
| if errorlevel 1 ( | |
| call :log_error "Failed to clean build cache" | |
| exit /b 1 | |
| ) | |
| call :log_info "Build cache cleaned successfully" | |
| GOTO :EOF | |
| :run_tests | |
| call :log_info "Running tests..." | |
| if "%INPUT_SOURCE%"=="" ( | |
| go test ./... -v | |
| ) else ( | |
| go test "%INPUT_SOURCE%" -v | |
| ) | |
| if errorlevel 1 ( | |
| call :log_error "Tests failed" | |
| call :send_notification "FAILED" "Test Failed" "❌ Tests failed before building" 2 | |
| exit /b 1 | |
| ) | |
| call :log_info "All tests passed" | |
| GOTO :EOF | |
| :compress_binary | |
| if not exist "%OUTPUT_FILE%" ( | |
| call :log_error "Output file not found for compression" | |
| exit /b 1 | |
| ) | |
| where upx >nul 2>&1 | |
| if errorlevel 1 ( | |
| call :log_error "UPX is not installed or not in PATH, skipping compression" | |
| exit /b 0 | |
| ) | |
| call :log_info "Compressing binary with UPX..." | |
| upx --best --lzma "%OUTPUT_FILE%" >nul 2>&1 | |
| if errorlevel 1 ( | |
| call :log_error "Compression failed, but build was successful" | |
| exit /b 0 | |
| ) | |
| call :log_info "Binary compressed successfully" | |
| GOTO :EOF | |
| :: ============================================================================ | |
| :: Argument Parsing | |
| :: ============================================================================ | |
| :parse_args | |
| :parse_loop | |
| if "%~1"=="" GOTO :parse_done | |
| set "arg=%~1" | |
| :: Help | |
| if /i "%arg%"=="-h" ( | |
| call :show_help | |
| exit /b 0 | |
| ) | |
| if /i "%arg%"=="--help" ( | |
| call :show_help | |
| exit /b 0 | |
| ) | |
| :: Output (-o requires next argument) | |
| if /i "%arg%"=="-o" ( | |
| if "%~2"=="" ( | |
| call :log_error "Option -o requires an argument" | |
| exit /b 1 | |
| ) | |
| set "OUTPUT_FILE=%~2" | |
| shift | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Output (--output=value) | |
| if /i "%arg:~0,9%"=="--output=" ( | |
| set "OUTPUT_FILE=%arg:~9%" | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Output (--output requires next argument) | |
| if /i "%arg%"=="--output" ( | |
| if "%~2"=="" ( | |
| call :log_error "Option --output requires an argument" | |
| exit /b 1 | |
| ) | |
| set "OUTPUT_FILE=%~2" | |
| shift | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Build tags (-t requires next argument) | |
| if /i "%arg%"=="-t" ( | |
| if "%~2"=="" ( | |
| call :log_error "Option -t requires an argument" | |
| exit /b 1 | |
| ) | |
| set "BUILD_TAGS=%~2" | |
| shift | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Build tags (--tags=value) | |
| if /i "%arg:~0,7%"=="--tags=" ( | |
| set "BUILD_TAGS=%arg:~7%" | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Build tags (--tags requires next argument) | |
| if /i "%arg%"=="--tags" ( | |
| if "%~2"=="" ( | |
| call :log_error "Option --tags requires an argument" | |
| exit /b 1 | |
| ) | |
| set "BUILD_TAGS=%~2" | |
| shift | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Linker flags (-l requires next argument) | |
| if /i "%arg%"=="-l" ( | |
| if "%~2"=="" ( | |
| call :log_error "Option -l requires an argument" | |
| exit /b 1 | |
| ) | |
| set "LD_FLAGS=%~2" | |
| shift | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Linker flags (--ldflags=value) | |
| if /i "%arg:~0,10%"=="--ldflags=" ( | |
| set "LD_FLAGS=%arg:~10%" | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Linker flags (--ldflags requires next argument) | |
| if /i "%arg%"=="--ldflags" ( | |
| if "%~2"=="" ( | |
| call :log_error "Option --ldflags requires an argument" | |
| exit /b 1 | |
| ) | |
| set "LD_FLAGS=%~2" | |
| shift | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Target OS (--os=value) | |
| if /i "%arg:~0,5%"=="--os=" ( | |
| set "GO_OS=%arg:~5%" | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Target OS (--os requires next argument) | |
| if /i "%arg%"=="--os" ( | |
| if "%~2"=="" ( | |
| call :log_error "Option --os requires an argument" | |
| exit /b 1 | |
| ) | |
| set "GO_OS=%~2" | |
| shift | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Target Architecture (--arch=value) | |
| if /i "%arg:~0,7%"=="--arch=" ( | |
| set "GO_ARCH=%arg:~7%" | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Target Architecture (--arch requires next argument) | |
| if /i "%arg%"=="--arch" ( | |
| if "%~2"=="" ( | |
| call :log_error "Option --arch requires an argument" | |
| exit /b 1 | |
| ) | |
| set "GO_ARCH=%~2" | |
| shift | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Verbose flag | |
| if /i "%arg%"=="-v" ( | |
| set VERBOSE=1 | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| if /i "%arg%"=="--verbose" ( | |
| set VERBOSE=1 | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Clean flag | |
| if /i "%arg%"=="-c" ( | |
| set CLEAN_BUILD=1 | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| if /i "%arg%"=="--clean" ( | |
| set CLEAN_BUILD=1 | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Run tests flag | |
| if /i "%arg%"=="-r" ( | |
| set RUN_TESTS=1 | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| if /i "%arg%"=="--run-tests" ( | |
| set RUN_TESTS=1 | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: Compress flag | |
| if /i "%arg%"=="-z" ( | |
| set COMPRESS=1 | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| if /i "%arg%"=="--compress" ( | |
| set COMPRESS=1 | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: If starts with -, it's unknown option | |
| if "%arg:~0,1%"=="-" ( | |
| call :log_error "Unknown option: %arg%" | |
| echo. | |
| call :show_help | |
| exit /b 1 | |
| ) | |
| :: Otherwise, treat as input source | |
| if "%INPUT_SOURCE%"=="" ( | |
| set "INPUT_SOURCE=%arg%" | |
| shift | |
| GOTO :parse_loop | |
| ) | |
| :: If we already have input source, this is extra argument | |
| call :log_error "Extra argument: %arg%" | |
| echo. | |
| call :show_help | |
| exit /b 1 | |
| :parse_done | |
| GOTO :EOF | |
| :: ============================================================================ | |
| :: Main Build Process | |
| :: ============================================================================ | |
| :main | |
| :: Configuration | |
| set SCRIPT_VERSION=1.0.0 | |
| set CURRENT_DIR=%CD% | |
| set BUILD_TAGS= | |
| set LD_FLAGS= | |
| set OUTPUT_FILE= | |
| set INPUT_SOURCE= | |
| set GO_OS= | |
| set GO_ARCH= | |
| set VERBOSE=0 | |
| set CLEAN_BUILD=0 | |
| set RUN_TESTS=0 | |
| set COMPRESS=0 | |
| :: Notification settings | |
| set NTFY_ENABLED=1 | |
| set GROWL_ENABLED=1 | |
| set NTFY_PATH=c:\TOOLS\ntfy_2.11.0_windows_amd64\ntfy.exe | |
| set NTFY_URL=http://222.222.222.5:89/androcall | |
| set NTFY_ICON=https://go.dev/blog/go-brand/Go-Logo/PNG/Go-Logo_Blue.png | |
| :: Parse arguments | |
| call :parse_args %* | |
| if errorlevel 1 exit /b 1 | |
| :: Validate required arguments | |
| if "%OUTPUT_FILE%"=="" ( | |
| call :log_error "Output file is required. Use -o or --output to specify." | |
| echo. | |
| call :show_help | |
| exit /b 1 | |
| ) | |
| :: Check Go installation | |
| call :check_go_installation | |
| if errorlevel 1 exit /b 1 | |
| :: Set default input source if not provided | |
| if "%INPUT_SOURCE%"=="" ( | |
| set "INPUT_SOURCE=." | |
| call :log_info "No source specified, using current directory" | |
| ) | |
| :: Validate input source | |
| if not exist "%INPUT_SOURCE%" ( | |
| call :log_error "Source not found: %INPUT_SOURCE%" | |
| exit /b 1 | |
| ) | |
| :: Get project name from current directory | |
| for %%A in ("%CD%") do set "PROJECT_NAME=%%~nxA" | |
| echo. | |
| echo ============================================================ | |
| echo GoBuild - Building Go Project | |
| echo ============================================================ | |
| echo Project: %PROJECT_NAME% | |
| echo Source: %INPUT_SOURCE% | |
| echo Output: %OUTPUT_FILE% | |
| if not "%BUILD_TAGS%"=="" echo Tags: %BUILD_TAGS% | |
| if not "%LD_FLAGS%"=="" echo Flags: %LD_FLAGS% | |
| if not "%GO_OS%"=="" echo Target OS: %GO_OS% | |
| if not "%GO_ARCH%"=="" echo Target Arch: %GO_ARCH% | |
| echo ============================================================ | |
| echo. | |
| :: Clean build cache if requested | |
| if %CLEAN_BUILD%==1 ( | |
| call :clean_build_cache | |
| if errorlevel 1 exit /b 1 | |
| ) | |
| :: Run tests if requested | |
| if %RUN_TESTS%==1 ( | |
| call :run_tests | |
| if errorlevel 1 exit /b 1 | |
| ) | |
| :: Build command construction | |
| set "BUILD_CMD=go build" | |
| :: Add build tags | |
| if not "%BUILD_TAGS%"=="" ( | |
| set "BUILD_CMD=!BUILD_CMD! -tags=%BUILD_TAGS%" | |
| ) | |
| :: Add linker flags | |
| if not "%LD_FLAGS%"=="" ( | |
| set "BUILD_CMD=!BUILD_CMD! -ldflags=%LD_FLAGS%" | |
| ) | |
| :: Add verbose flag | |
| if %VERBOSE%==1 ( | |
| set "BUILD_CMD=!BUILD_CMD! -v" | |
| ) | |
| :: Add output file | |
| set "BUILD_CMD=!BUILD_CMD! -o %OUTPUT_FILE%" | |
| :: Add source | |
| set "BUILD_CMD=!BUILD_CMD! %INPUT_SOURCE%" | |
| :: Set cross-compilation environment | |
| if not "%GO_OS%"=="" ( | |
| set GOOS=%GO_OS% | |
| ) | |
| if not "%GO_ARCH%"=="" ( | |
| set GOARCH=%GO_ARCH% | |
| ) | |
| :: Execute build | |
| call :log_info "Building..." | |
| call :log_info "Command: %BUILD_CMD%" | |
| %BUILD_CMD% | |
| if errorlevel 1 ( | |
| call :log_error "Build failed!" | |
| call :send_notification "FAILED" "Build Failed" "❌ Build failed for %PROJECT_NAME%" 2 | |
| cd /d "%CURRENT_DIR%" | |
| exit /b 1 | |
| ) | |
| :: Compress if requested | |
| if %COMPRESS%==1 ( | |
| call :compress_binary | |
| ) | |
| :: Get file size | |
| if exist "%OUTPUT_FILE%" ( | |
| for %%A in ("%OUTPUT_FILE%") do set FILE_SIZE=%%~zA | |
| set /a FILE_SIZE_KB=!FILE_SIZE! / 1024 | |
| call :log_info "Output size: !FILE_SIZE_KB! KB" | |
| ) | |
| echo. | |
| echo ============================================================ | |
| echo Build Successful! | |
| echo ============================================================ | |
| echo Output: %OUTPUT_FILE% | |
| if defined FILE_SIZE_KB echo Size: !FILE_SIZE_KB! KB | |
| echo ============================================================ | |
| echo. | |
| call :send_notification "SUCCESS" "Build Success" "✅ Build successful: %OUTPUT_FILE%" 0 | |
| cd /d "%CURRENT_DIR%" | |
| exit /b 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment