# 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.go` automatically with every compiled build. - ๐Ÿงน **Auto-Import**: Runs `goimports` on 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](https://go.dev/) installed and available in your system path. `goimports` is only required if you use the `-i` flag: ```bash go install golang.org/x/tools/cmd/goimports@latest ``` ### Building from Source Clone the repository and compile `gbld`: ```bash git clone https://git.fhi.mpg.de/mike/gbld.git cd gbld go build -o /usr/local/bin/gbld ``` --- ## Usage ```bash gbld [flags] [target_name] ``` > [!NOTE] > If `target_name` is 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](#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` | `` | Space-separated arguments to pass to the compiled binary on execution. | | `-c` | `` | 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): ```json { "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 = ""`. 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: 1. It sends an `os.Interrupt` (SIGINT) to the running binary. 2. It waits up to 1 second for the process to exit clean. 3. 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 ```bash gbld -a # build bin/___ 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 by `shasum -a 256 -c` - `bin/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] > `-a` is the path for the programs `gbld` builds. `gbld` itself is released with `build.sh` (see below), which uses a different naming โ€” both write `bin/checksums.txt`, so whichever ran last is the one that file describes. --- ## Releasing gbld itself ```bash ./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--` โ€” the binaries, `.exe` for Windows, built with `-trimpath -s -w` and the version injected via `-ldflags` - `checksums.txt` โ€” their SHA-256, in the format of `shasum -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 -a` names its artifacts from `var version` in the sources, `build.sh` from `version.txt`, and nothing writes to a `.go` file. For gbld's own releases `build.sh` is the way โ€” `var version` only shows up in a bare `go 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](https://terminalcolors.com/themes/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 ```bash 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 `-i` flag only replaces the `import (...)` block of a file and leaves the rest of the formatting alone โ€” do not run `gofmt` over the tree.