From f5c76147686be469efe6385d76b31779c435c282 Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 6 Oct 2026 18:41:51 +0200 Subject: [PATCH] Stamp the git tag or short commit into the binary (closes #4) A plain `docker build .` now stamps bsdaily's version: the VERSION build argument when one is given, otherwise `git describe --tags --always` on the .git in the build context, as the canonical Dockerfile does. The build fails if .git is there and the version still comes out empty, dev or unknown. A host `make` build stamps the same `git describe`, or dev. bsdaily logs the version on the first line of every run and prints it with --version; a build that stamps nothing, or an empty value, reports dev. Judgement call: the build line also takes the canonical -trimpath and -s -w. One //nolint (gochecknoglobals): -X can only set a package-level variable. Model: opus-5-5 Co-authored-by: clawbot --- Dockerfile | 22 +++++++++++++++++++--- Makefile | 8 +++++--- README.md | 18 +++++++++++++++++- TODO.md | 4 ++++ cmd/bsdaily/main.go | 14 ++++++++++++++ 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index e7c0d8b..54b24e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,9 @@ FROM golang:1.26.4-alpine@sha256:3ad57304ad93bbec8548a0437ad9e06a455660655d9af01 COPY --from=lint /src/go.sum /dev/null COPY --from=test /src/go.sum /dev/null -ARG VERSION=dev +RUN apk add --no-cache git +# A tar-stream context keeps the sender's file owners, which git refuses. +RUN git config --system --add safe.directory /src WORKDIR /src @@ -58,8 +60,22 @@ RUN go mod download # Copy source code COPY . . -# Build (pure Go, no CGO required since we use modernc.org/sqlite) -RUN CGO_ENABLED=0 go build -o /bsdaily ./cmd/bsdaily +# Build (pure Go, no CGO required since we use modernc.org/sqlite). +# The VERSION build arg when one is given, otherwise +# `git describe --tags --always` on the .git in the build context. With +# .git present, a version that is still empty, dev or unknown fails the +# build: git is missing or could not read the checkout. +ARG VERSION +RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \ + if [ -e .git ]; then \ + case "$VERSION" in ""|dev|unknown) \ + echo "version is '$VERSION' although .git is present" >&2; \ + exit 1 ;; \ + esac; \ + fi; \ + CGO_ENABLED=0 go build -trimpath \ + -ldflags="-s -w -X main.Version=${VERSION}" \ + -o /bsdaily ./cmd/bsdaily # Runtime stage # alpine:3.21, 2026-06-28 diff --git a/Makefile b/Makefile index 47c8845..84dd3f4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ .PHONY: all bootstrap setup check test lint fmt fmt-check build clean deps test-coverage test-integration install release release-snapshot docker hooks -# Version number -VERSION := 0.1.0-dev +# Stamped into the binary: the same `git describe` a plain `docker build .` +# runs, or dev when it prints nothing (outside a git checkout, or where git is +# missing). ?= so that a VERSION already in the environment takes precedence. +VERSION ?= $(or $(shell git describe --tags --always 2>/dev/null),dev) # Default target all: bsdaily @@ -36,7 +38,7 @@ lint: # Build binary (pure Go; no CGO required since we use modernc.org/sqlite). bsdaily: internal/*/*.go cmd/bsdaily/*.go - CGO_ENABLED=0 go build -o $@ ./cmd/bsdaily + CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -o $@ ./cmd/bsdaily # Clean build artifacts. clean: diff --git a/README.md b/README.md index 0aaecdb..cdf50c1 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,7 @@ A single run proceeds as follows: bsdaily # extract the snapshot date minus one day bsdaily --date 2026-06-27 # extract a single specific day bsdaily --from 2026-06-01 --to 2026-06-27 # extract an inclusive range +bsdaily --version # print the version and exit ``` Flags: @@ -197,9 +198,24 @@ Flags: `--from`/`--to`. - `--from YYYY-MM-DD` — start of an inclusive range (requires `--to`). - `--to YYYY-MM-DD` — end of an inclusive range (requires `--from`). +- `-v`, `--version` — print the version and exit. With no flags, the tool extracts the day before the latest snapshot. All -progress is logged as structured `slog` text to stderr. +progress is logged as structured `slog` text to stderr; the first line of every +run carries the version. + +The version is set at link time and depends on how the binary was built: + +- `docker build .` takes it from the `VERSION` build argument when one is given, + otherwise from `git describe --tags --always` on the `.git` in the build + context. The build fails if `.git` is there and the version still comes out + empty, `dev` or `unknown`. With neither `.git` nor `VERSION`, the binary + reports `dev`. +- `script/docker`, `script/cibuild` and `make docker` pass the host's + `git describe --tags --always --dirty` as `VERSION`, so on a modified tree the + version ends in `-dirty`. When that prints nothing, they pass `unknown`. +- `make` stamps the host's `git describe --tags --always`, without `-dirty`, or + `dev` when that prints nothing. ## Merging dumps back into a database diff --git a/TODO.md b/TODO.md index 96f3ea9..bbc9e59 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,10 @@ verification, and atomic-publish paths. # Completed Steps +- 2026-10-06: A plain `docker build .` and a host `make` build stamp the git tag + or short commit into the binary, which `bsdaily` logs on the first line of + every run and prints with `--version` + (https://git.eeqj.de/sneak/bsdaily/issues/4). - 2026-10-06: Added the canonical `.golangci.yml`, moved the lint phase to golangci-lint v2.14.0, and fixed the code to pass it (https://git.eeqj.de/sneak/bsdaily/issues/6). diff --git a/cmd/bsdaily/main.go b/cmd/bsdaily/main.go index 8a1bdf5..0477df5 100644 --- a/cmd/bsdaily/main.go +++ b/cmd/bsdaily/main.go @@ -20,7 +20,18 @@ var ( errFromAfterTo = errors.New("is after --to") ) +// Version is the git tag or short commit, set at link time with +// -X main.Version=... by the Dockerfile and the Makefile. A build that sets +// nothing, or sets it empty, reports dev. +// +//nolint:gochecknoglobals // -X can only set a package-level variable +var Version string + func main() { + if Version == "" { + Version = "dev" + } + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelInfo, })) @@ -31,8 +42,11 @@ func main() { rootCmd := &cobra.Command{ Use: "bsdaily", Short: "Extract a single day's data from the latest daily snapshot", + Version: Version, SilenceUsage: true, RunE: func(_ *cobra.Command, _ []string) error { + slog.Info("starting", "version", Version) + targetDates, err := parseTargetDates(dateFlag, fromFlag, toFlag) if err != nil { return err