diff --git a/cmd/main.go b/cmd/main.go index d62dbe8..99697a0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,6 +5,8 @@ import ( "os" "github.com/spf13/cobra" + + "git.offline-twitter.com/offline-labs/gas-stack/pkg/version" ) const ( @@ -16,12 +18,16 @@ const ( func main() { root_cmd := &cobra.Command{ Use: "gas", + Version: version.String(), SilenceErrors: true, SilenceUsage: true, } + // Print a bare version string ("v0.0.4"), rather than cobra's "gas version v0.0.4". + root_cmd.SetVersionTemplate("{{.Version}}\n") root_cmd.AddCommand(sqlite_lint) root_cmd.AddCommand(cmd_init) root_cmd.AddCommand(generate_model) + root_cmd.AddCommand(cmd_version) if err := root_cmd.Execute(); err != nil { fmt.Println(RED + err.Error() + RESET) os.Exit(1) diff --git a/cmd/subcmd_version.go b/cmd/subcmd_version.go new file mode 100644 index 0000000..0e6503e --- /dev/null +++ b/cmd/subcmd_version.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" + + "git.offline-twitter.com/offline-labs/gas-stack/pkg/version" +) + +var cmd_version = &cobra.Command{ + Use: "version", + Short: "Print the version of `gas`", + Args: cobra.NoArgs, + + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version.String()) + }, +} diff --git a/ops/compile.sh b/ops/compile.sh index 488281c..7939b25 100755 --- a/ops/compile.sh +++ b/ops/compile.sh @@ -1,3 +1,10 @@ #!/bin/sh -go build -tags fts5 -o gas ./cmd +# Stamp the version into the binary. `git describe` fails when this isn't a git checkout (e.g. a +# source export with no .git dir); in that case leave it empty, and pkg/version falls back to the +# build info that the Go toolchain embeds. +VERSION=$(git describe --tags --always --dirty 2>/dev/null) + +go build -tags fts5 \ + -ldflags "-X git.offline-twitter.com/offline-labs/gas-stack/pkg/version.Version=$VERSION" \ + -o gas ./cmd diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 0000000..3cd0546 --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,55 @@ +package version + +import ( + "fmt" + "runtime/debug" +) + +// Version is stamped in at compile time by `ops/compile.sh`, via: +// +// -ldflags "-X git.offline-twitter.com/offline-labs/gas-stack/pkg/version.Version=v0.0.4" +// +// It's empty for builds that don't go through that script-- most importantly +// `go install git.offline-twitter.com/offline-labs/gas-stack/cmd@v0.0.4`, which is the normal way +// to install `gas`. In that case, String() recovers the version from the build info that the Go +// toolchain embeds in every binary. +var Version = "" + +// String returns the version of this binary, e.g. "v0.0.4". +func String() string { + if Version != "" { + return Version + } + + info, ok := debug.ReadBuildInfo() + if !ok { + return "unknown" + } + + // Set by `go install module@version`, and (as of Go 1.24) by `go build` in a git checkout + // that has the tags available. It's "(devel)" if the toolchain couldn't work out a version. + if info.Main.Version != "" && info.Main.Version != "(devel)" { + return info.Main.Version + } + + // No module version, so fall back to the commit. This happens when building from a source + // tree with no usable VCS metadata: a git worktree, a `.git`-less export, or `-buildvcs=false`. + var revision, dirty string + for _, setting := range info.Settings { + switch setting.Key { + case "vcs.revision": + revision = setting.Value + case "vcs.modified": + if setting.Value == "true" { + dirty = "-dirty" + } + } + } + if revision == "" { + return "unknown" + } + if len(revision) > 12 { + revision = revision[:12] + } + return fmt.Sprintf("devel-%s%s", revision, dirty) +}