gbld — Simple Go Builder & Live-Reloader
gbld is a hot-reloader and build-automation utility for Go projects. It automatically tracks build numbers, formats source files using goimports, compiles the binary, manages the running process, and watches the project directory for changes to trigger automatic rebuilding and restarting.
Key Features
- 🔄 Hot Reloader: Automatically watches for file changes (via
fsnotify), recursively including subdirectories, and rebuilds/restarts your application instantly. - 🔢 Build Counter: Increments a build number in
build.goautomatically with every compiled build. - 🧹 Auto-Import: Runs
goimportson save (optional via-i) to keep your imports clean — without touching the rest of your formatting. - 🖥️ Interactive Menu: Uses a select menu (
gbld.menu.json) if run without arguments, letting you define and run custom build tasks. - 🚀 Cross-Compilation: Easily builds binaries for multiple target operating systems (macOS, Linux, Windows) and architectures (amd64, arm64) using a single command.
- 🔐 Verified Self-Update: Installs new versions from the Gitea releases over HTTPS and refuses anything that does not match its published SHA-256 checksum.
Installation
Prerequisites
Make sure you have Go installed and available in your system path. goimports is only required if you use the -i flag:
go install golang.org/x/tools/cmd/goimports@latest
Building from Source
Clone the repository and compile gbld:
git clone https://git.fhi.mpg.de/mike/gbld.git
cd gbld
go build -o /usr/local/bin/gbld
Usage
gbld [flags] [target_name]
Note
If
target_nameis omitted, the name of the current directory is used. The tool expects a main source file matching that name (e.g.,target_name.go). The binary is always built from the package in the current directory, so the module path does not have to match the target name.
CLI Flags
| Flag | Argument | Description |
|---|---|---|
-b |
None | Build-only mode (compiles the binary but does not run it). |
-1 |
None | Run once (does not watch the directory for changes), see Exit Codes. |
-i |
None | Runs goimports to clean up imports before building. |
-x |
None | Exits after the first successful build and leaves the started binary running. |
-o |
<args> |
Space-separated arguments to pass to the compiled binary on execution. |
-c |
<command> |
Command to execute immediately after a successful build (before execution). |
-a |
None | Builds binaries for all supported platforms (Windows, macOS, Linux for AMD64/ARM64). |
-v |
None | Displays the current version of gbld. |
-h |
None | Displays help and usage information. |
--check-update |
None | Looks for a newer release and reports what it finds — installs nothing. |
--update |
None | Downloads the newest release and replaces the running binary with it. |
Environment
| Variable | Description |
|---|---|
GBLD_NO_UPDATE |
Set to any value to silence the background update check (useful offline or in CI). --update and --check-update still work. |
Configuration (gbld.menu.json)
To make running common tasks easier, you can place a gbld.menu.json file in your project directory. If you run gbld without any flags, it will parse this file and present an interactive selection menu (entries are shown in alphabetical order):
{
"Build and Run (hot reload)": ["-i"],
"Build Only": ["-b", "-i", "-1"],
"Show Help": ["-h"]
}
Under the Hood
Build Increment
When compiling, gbld parses build.go to find a pattern matching var build = "<number>". It increments this number by 1 and updates the file automatically, leaving the rest of the line (including trailing comments) untouched. This is useful for stamping build numbers/versions inside your binary. If there is no build.go, this step is silently skipped.
Target Version
-a names its artifacts after the first var version = "x.y.z" found in the *.go files of the project. Ordinary builds do not need this variable.
Automatic Backups
Before gbld runs goimports on a file or modifies build.go, it creates a timestamped backup in a ./tmp/ directory inside the project workspace (the directory is created on demand):
./tmp/gbld.go.20260625145800
If a backup cannot be written, the file is not modified.
File Watching
The project directory is watched recursively; directories created while gbld is running are picked up automatically. Hidden entries (.git, …) as well as bin, tmp, vendor, node_modules and testdata are skipped. Only writes and newly created .go files trigger a rebuild — a pure touch (mtime only) does not. Rebuilds are debounced by 100 ms and never run in parallel.
Process Management
When hot-reloading, gbld handles process termination gracefully:
- It sends an
os.Interrupt(SIGINT) to the running binary. - It waits up to 1 second for the process to exit clean.
- If it does not exit within the timeout, it terminates the process forcefully (
SIGKILL).
gbld itself also traps SIGINT/SIGTERM and stops the started binary before exiting, so no orphaned processes are left behind — except with -x, where detaching is the point.
Exit Codes
With -1, gbld is usable from scripts and CI:
| Situation | Exit code |
|---|---|
| Build failed | 1 |
-b -1, build succeeded |
0 |
-1 without -b |
exit code of the built program (gbld waits for it) |
Cross-Compilation
gbld -a # build bin/<name>_<version>_<os>_<arch> for all 6 targets
-a writes Windows artifacts with an .exe suffix and additionally produces:
bin/checksums.txt— SHA-256 of every artifact, in the format used byshasum -a 256 -cbin/version.txt— the version that was built
If any target fails to build, version.txt is not written and gbld exits with 1, so a half-published version can never be announced.
Note
-ais the path for the programsgbldbuilds.gblditself is released withbuild.sh(see below), which uses a different naming — both writebin/checksums.txt, so whichever ran last is the one that file describes.
Releasing gbld itself
./build.sh # steps the patch version, builds every platform
VERSION=1.22.0 ./build.sh # a minor or major step is named outright
PLATFORMS="linux/amd64" ./build.sh
build.sh writes into bin/ exactly what a release needs:
gbld-<goos>-<goarch>— the binaries,.exefor Windows, built with-trimpath -s -wand the version injected via-ldflagschecksums.txt— their SHA-256, in the format ofshasum -a 256
version.txt holds the version just built and is stepped by 0.0.1 on every run. Upload all of these files to a Gitea release whose tag is the bare version number (1.21.1, a leading v is allowed) — that is the layout --update expects.
Important
gbld -anames its artifacts fromvar versionin the sources,build.shfromversion.txt, and nothing writes to a.gofile. For gbld's own releasesbuild.shis the way —var versiononly shows up in a barego build.
Self-Update
selfupdate.go holds the whole mechanism and is meant to be copied into other programs: adjust the block at its head, hang the two options into the flags, done. It needs nothing but the standard library.
Once a day, in the background, gbld asks the Gitea for the newest release and notes the answer in the cache directory. The next run reads that note and prints one line if something newer exists — the foreground never touches the network, so an unreachable server can never delay a build. The hint appears only on a terminal: in a pipe, a script or under cron, gbld stays quiet, as it does with GBLD_NO_UPDATE set.
--update fetches the asset for the running platform, weighs it against the SHA-256 from the release's checksums.txt, and only then replaces the running binary — after calling the fresh one once with -v to be sure it runs at all. A release without a matching checksum is refused, and a download that does not match is deleted, not installed. The certificate of the Gitea is verified like any other.
Colors
Output uses the Catppuccin Mocha palette as 24-bit color:
| Helper | Palette | Hex |
|---|---|---|
Cr / Crb |
Red | #f38ba8 |
Cg / Cgb |
Green | #a6e3a1 |
Cy / Cyb |
Yellow | #f9e2af |
Cb / Cbb |
Blue | #89b4fa |
Cm / Cmb |
Pink | #f5c2e7 |
Cc / Ccb |
Teal | #94e2d5 |
Cw |
Subtext1 | #bac2de |
Cwb |
Text | #cdd6f4 |
Mocha maps identical values to the normal and bright ANSI slots, so the bold helpers keep their hue and only add weight. The full palette is available as CatMauve, CatPeach, CatLavender, … in tools.go.
The interactive prompts (survey) are themed as well: their templates resolve colors through {{color "green+hb"}}-style names, and tools.go replaces that template function with one that maps those names onto the palette. This covers every prompt including its icons — question mark, select arrow, help and error markers — without copying any template.
Terminals that do not announce 24-bit color via COLORTERM fall back to the basic ANSI colors, so their own theme keeps deciding. NO_COLOR and non-TTY output disable colors entirely.
Development
go test ./... # unit tests (build counter, version detection, update verification, toolbox)
go vet ./...
Note
The sources are deliberately not
gofmt-formatted (2-space indentation). The-iflag only replaces theimport (...)block of a file and leaves the rest of the formatting alone — do not rungofmtover the tree.