Skip to content
Snippets Groups Projects
Unverified Commit e49c93d4 authored by sudoforge's avatar sudoforge Committed by GitHub
Browse files

build: reduce complexity for setting the version (#1466)

This change refactors the implementation of how the version is embedded
in the binary to reduce the number of variables necessary to determine
the version information from 3 to 1.

The legacy build variables are still supported, however, a warning will
be emitted instructing users to contact their package maintainer. The
legacy GitExacTag variable, if present, will be used to set main.version
if it is undefined. This ensures that unmigrated package builds will
continue to provide the correct version information.

The legacy build variables will be supported until 0.12.0, giving
package maintainers some time to migrate.

Change-Id: I05fea97169ea1af87b198174afe5b6663f860fd8
parent 01d6899f
No related branches found
No related tags found
No related merge requests found
all: build
GIT_COMMIT:=$(shell git rev-list -1 HEAD)
GIT_LAST_TAG:=$(shell git describe --abbrev=0 --tags 2>/dev/null || true)
GIT_EXACT_TAG:=$(shell git name-rev --name-only --tags HEAD)
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
XARGS:=xargs -r XARGS:=xargs -r
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
...@@ -11,10 +6,10 @@ endif ...@@ -11,10 +6,10 @@ endif
SYSTEM=$(shell nix eval --impure --expr 'builtins.currentSystem' --raw 2>/dev/null || echo '') SYSTEM=$(shell nix eval --impure --expr 'builtins.currentSystem' --raw 2>/dev/null || echo '')
COMMANDS_PATH:=github.com/git-bug/git-bug/commands TAG:=$(shell git name-rev --name-only --tags HEAD)
LDFLAGS:=-X ${COMMANDS_PATH}.GitCommit="${GIT_COMMIT}" \ LDFLAGS:=-X main.version="${TAG}"
-X ${COMMANDS_PATH}.GitLastTag="${GIT_LAST_TAG}" \
-X ${COMMANDS_PATH}.GitExactTag="${GIT_EXACT_TAG}" all: build
.PHONY: list-checks .PHONY: list-checks
list-checks: list-checks:
......
// Package commands contains the CLI commands
package commands package commands
import ( import (
"fmt"
"os" "os"
"runtime/debug"
"github.com/spf13/cobra" "github.com/spf13/cobra"
bridgecmd "github.com/git-bug/git-bug/commands/bridge" "github.com/git-bug/git-bug/commands/bridge"
bugcmd "github.com/git-bug/git-bug/commands/bug" "github.com/git-bug/git-bug/commands/bug"
"github.com/git-bug/git-bug/commands/execenv" "github.com/git-bug/git-bug/commands/execenv"
usercmd "github.com/git-bug/git-bug/commands/user" "github.com/git-bug/git-bug/commands/user"
) )
// These variables are initialized externally during the build. See the Makefile. func NewRootCommand(version string) *cobra.Command {
var GitCommit string
var GitLastTag string
var GitExactTag string
func NewRootCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: execenv.RootCommandName, Use: execenv.RootCommandName,
Short: "A bug tracker embedded in Git", Short: "A bug tracker embedded in Git",
...@@ -33,7 +25,7 @@ the same git remote you are already using to collaborate with other people. ...@@ -33,7 +25,7 @@ the same git remote you are already using to collaborate with other people.
PersistentPreRun: func(cmd *cobra.Command, args []string) { PersistentPreRun: func(cmd *cobra.Command, args []string) {
root := cmd.Root() root := cmd.Root()
root.Version = getVersion() root.Version = version
}, },
// For the root command, force the execution of the PreRun // For the root command, force the execution of the PreRun
...@@ -80,64 +72,3 @@ the same git remote you are already using to collaborate with other people. ...@@ -80,64 +72,3 @@ the same git remote you are already using to collaborate with other people.
return cmd return cmd
} }
func Execute() {
if err := NewRootCommand().Execute(); err != nil {
os.Exit(1)
}
}
func getVersion() string {
if GitExactTag == "undefined" {
GitExactTag = ""
}
if GitExactTag != "" {
// we are exactly on a tag --> release version
return GitLastTag
}
if GitLastTag != "" {
// not exactly on a tag --> dev version
return fmt.Sprintf("%s-dev-%.10s", GitLastTag, GitCommit)
}
// we don't have commit information, try golang build info
if commit, dirty, err := getCommitAndDirty(); err == nil {
if dirty {
return fmt.Sprintf("dev-%.10s-dirty", commit)
}
return fmt.Sprintf("dev-%.10s", commit)
}
return "dev-unknown"
}
func getCommitAndDirty() (commit string, dirty bool, err error) {
info, ok := debug.ReadBuildInfo()
if !ok {
return "", false, fmt.Errorf("unable to read build info")
}
var commitFound bool
// get the commit and modified status
// (that is the flag for repository dirty or not)
for _, kv := range info.Settings {
switch kv.Key {
case "vcs.revision":
commit = kv.Value
commitFound = true
case "vcs.modified":
if kv.Value == "true" {
dirty = true
}
}
}
if !commitFound {
return "", false, fmt.Errorf("no commit found")
}
return commit, dirty, nil
}
package commands package commands
import ( import (
"runtime" "log/slog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/git-bug/git-bug/commands/execenv" "github.com/git-bug/git-bug/commands/execenv"
) )
type versionOptions struct { // TODO: 0.12.0: remove deprecated build vars
number bool var (
commit bool GitCommit string
all bool GitLastTag string
} GitExactTag string
)
func newVersionCommand(env *execenv.Env) *cobra.Command { func newVersionCommand(env *execenv.Env) *cobra.Command {
options := versionOptions{} return &cobra.Command{
cmd := &cobra.Command{
Use: "version", Use: "version",
Short: "Show git-bug version information", Short: "Print version information",
Run: func(cmd *cobra.Command, args []string) { Example: "git bug version",
runVersion(env, options, cmd.Root()) Long: `
}, Print version information.
}
flags := cmd.Flags() Format:
flags.SortFlags = false git-bug <version> [commit[/dirty]] <compiler version> <platform> <arch>
flags.BoolVarP(&options.number, "number", "n", false, Format Description:
"Only show the version number", <version> may be one of:
) - A semantic version string, prefixed with a "v", e.g. v1.2.3
flags.BoolVarP(&options.commit, "commit", "c", false, - "undefined" (if not provided, or built with an invalid version string)
"Only show the commit hash",
)
flags.BoolVarP(&options.all, "all", "a", false,
"Show all version information",
)
return cmd [commit], if present, is the commit hash that was checked out during the
} build. This may be suffixed with '/dirty' if there were local file
modifications. This is indicative of your build being patched, or modified in
some way from the commit.
func runVersion(env *execenv.Env, opts versionOptions, root *cobra.Command) { <compiler version> is the version of the go compiler used for the build.
if opts.all {
env.Out.Printf("%s version: %s\n", execenv.RootCommandName, root.Version)
env.Out.Printf("System version: %s/%s\n", runtime.GOARCH, runtime.GOOS)
env.Out.Printf("Golang version: %s\n", runtime.Version())
return
}
if opts.number { <platform> is the target platform (GOOS).
env.Out.Println(root.Version)
return
}
if opts.commit { <arch> is the target architecture (GOARCH).
env.Out.Println(GitCommit) `,
return Run: func(cmd *cobra.Command, args []string) {
defer warnDeprecated()
env.Out.Printf("%s %s", execenv.RootCommandName, cmd.Root().Version)
},
}
} }
env.Out.Printf("%s version: %s\n", execenv.RootCommandName, root.Version) // warnDeprecated warns about deprecated build variables
// TODO: 0.12.0: remove support for old build tags
func warnDeprecated() {
msg := "please contact your package maintainer"
reason := "deprecated build variable"
if GitLastTag != "" {
slog.Warn(msg, "reason", reason, "name", "GitLastTag", "value", GitLastTag)
}
if GitExactTag != "" {
slog.Warn(msg, "reason", reason, "name", "GitExactTag", "value", GitExactTag)
}
if GitCommit != "" {
slog.Warn(msg, "reason", reason, "name", "GitCommit", "value", GitCommit)
}
} }
...@@ -34,7 +34,7 @@ func main() { ...@@ -34,7 +34,7 @@ func main() {
wg.Add(1) wg.Add(1)
go func(name string, f func(*cobra.Command) error) { go func(name string, f func(*cobra.Command) error) {
defer wg.Done() defer wg.Done()
root := commands.NewRootCommand() root := commands.NewRootCommand("")
err := f(root) err := f(root)
if err != nil { if err != nil {
fmt.Printf(" - %s: FATAL\n", name) fmt.Printf(" - %s: FATAL\n", name)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" "" .TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" ""
.SH NAME .SH NAME
git-bug-version - Show git-bug version information git-bug-version - Print version information
.SH SYNOPSIS .SH SYNOPSIS
...@@ -10,25 +10,44 @@ git-bug-version - Show git-bug version information ...@@ -10,25 +10,44 @@ git-bug-version - Show git-bug version information
.SH DESCRIPTION .SH DESCRIPTION
Show git-bug version information Print version information.
.PP
Format:
git-bug [commit[/dirty]]
.SH OPTIONS .PP
\fB-n\fP, \fB--number\fP[=false] Format Description:
Only show the version number may be one of:
- A semantic version string, prefixed with a "v", e.g. v1.2.3
- "undefined" (if not provided, or built with an invalid version string)
.PP
[commit], if present, is the commit hash that was checked out during the
build. This may be suffixed with '/dirty' if there were local file
modifications. This is indicative of your build being patched, or modified in
some way from the commit.
.PP .PP
\fB-c\fP, \fB--commit\fP[=false] is the version of the go compiler used for the build.
Only show the commit hash
.PP .PP
\fB-a\fP, \fB--all\fP[=false] is the target platform (GOOS).
Show all version information
.PP .PP
is the target architecture (GOARCH).
.SH OPTIONS
\fB-h\fP, \fB--help\fP[=false] \fB-h\fP, \fB--help\fP[=false]
help for version help for version
.SH EXAMPLE
.EX
git bug version
.EE
.SH SEE ALSO .SH SEE ALSO
\fBgit-bug(1)\fP \fBgit-bug(1)\fP
...@@ -31,7 +31,7 @@ git-bug [flags] ...@@ -31,7 +31,7 @@ git-bug [flags]
* [git-bug push](git-bug_push.md) - Push updates to a git remote * [git-bug push](git-bug_push.md) - Push updates to a git remote
* [git-bug termui](git-bug_termui.md) - Launch the terminal UI * [git-bug termui](git-bug_termui.md) - Launch the terminal UI
* [git-bug user](git-bug_user.md) - List identities * [git-bug user](git-bug_user.md) - List identities
* [git-bug version](git-bug_version.md) - Show git-bug version information * [git-bug version](git-bug_version.md) - Print version information
* [git-bug webui](git-bug_webui.md) - Launch the web UI * [git-bug webui](git-bug_webui.md) - Launch the web UI
* [git-bug wipe](git-bug_wipe.md) - Wipe git-bug from the git repository * [git-bug wipe](git-bug_wipe.md) - Wipe git-bug from the git repository
## git-bug version ## git-bug version
Show git-bug version information Print version information
### Synopsis
Print version information.
Format:
git-bug <version> [commit[/dirty]] <compiler version> <platform> <arch>
Format Description:
<version> may be one of:
- A semantic version string, prefixed with a "v", e.g. v1.2.3
- "undefined" (if not provided, or built with an invalid version string)
[commit], if present, is the commit hash that was checked out during the
build. This may be suffixed with '/dirty' if there were local file
modifications. This is indicative of your build being patched, or modified in
some way from the commit.
<compiler version> is the version of the go compiler used for the build.
<platform> is the target platform (GOOS).
<arch> is the target architecture (GOARCH).
``` ```
git-bug version [flags] git-bug version [flags]
``` ```
### Examples
```
git bug version
```
### Options ### Options
``` ```
-n, --number Only show the version number
-c, --commit Only show the commit hash
-a, --all Show all version information
-h, --help help for version -h, --help help for version
``` ```
......
...@@ -4,9 +4,15 @@ ...@@ -4,9 +4,15 @@
package main package main
import ( import (
"os"
"github.com/git-bug/git-bug/commands" "github.com/git-bug/git-bug/commands"
) )
func main() { func main() {
commands.Execute() v, _ := getVersion()
root := commands.NewRootCommand(v)
if err := root.Execute(); err != nil {
os.Exit(1)
}
} }
...@@ -26,7 +26,7 @@ func main() { ...@@ -26,7 +26,7 @@ func main() {
wg.Add(1) wg.Add(1)
go func(name string, f func(*cobra.Command) error) { go func(name string, f func(*cobra.Command) error) {
defer wg.Done() defer wg.Done()
root := commands.NewRootCommand() root := commands.NewRootCommand("")
err := f(root) err := f(root)
if err != nil { if err != nil {
fmt.Printf(" - %s: %v\n", name, err) fmt.Printf(" - %s: %v\n", name, err)
......
package main
import (
"errors"
"fmt"
"runtime/debug"
"strings"
"github.com/git-bug/git-bug/commands"
"golang.org/x/mod/semver"
)
var (
version = "undefined"
)
// getVersion returns a string representing the version information defined when
// the binary was built, or a sane default indicating a local build. a string is
// always returned. an error may be returned along with the string in the event
// that we detect a local build but are unable to get build metadata.
//
// TODO: support validation of the version (that it's a real version)
// TODO: support notifying the user if their version is out of date
func getVersion() (string, error) {
var arch string
var commit string
var modified bool
var platform string
var v strings.Builder
// this supports overriding the default version if the deprecated var used
// for setting the exact version for releases is supplied. we are doing this
// in order to give downstream package maintainers a longer window to
// migrate.
//
// TODO: 0.12.0: remove support for old build tags
if version == "undefined" && commands.GitExactTag != "" {
version = commands.GitExactTag
}
// automatically add the v prefix if it's missing
if version != "undefined" && !strings.HasPrefix(version, "v") {
version = fmt.Sprintf("v%s", version)
}
// reset the version string to undefined if it is invalid
if ok := semver.IsValid(version); !ok {
version = "undefined"
}
v.WriteString(version)
info, ok := debug.ReadBuildInfo()
if !ok {
v.WriteString(fmt.Sprintf(" (no build info)\n"))
return v.String(), errors.New("unable to read build metadata")
}
for _, kv := range info.Settings {
switch kv.Key {
case "GOOS":
platform = kv.Value
case "GOARCH":
arch = kv.Value
case "vcs.modified":
if kv.Value == "true" {
modified = true
}
case "vcs.revision":
commit = kv.Value
}
}
if commit != "" {
v.WriteString(fmt.Sprintf(" %.12s", commit))
}
if modified {
v.WriteString("/dirty")
}
v.WriteString(fmt.Sprintf(" %s", info.GoVersion))
if platform != "" {
v.WriteString(fmt.Sprintf(" %s", platform))
}
if arch != "" {
v.WriteString(fmt.Sprintf(" %s", arch))
}
return fmt.Sprint(v.String(), "\n"), nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment