Skip to content

Commit 1003b0f

Browse files
committed
feat: Add buildinfo package to embed version
This also resolves build time and commit hash using the Go 1.18 debug/buildinfo package. An external URL is outputted on running version as well to easily route the caller to a release or commit.
1 parent ccba2ba commit 1003b0f

File tree

6 files changed

+132
-3
lines changed

6 files changed

+132
-3
lines changed

.goreleaser.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ before:
1616
builds:
1717
- id: coder-slim
1818
dir: cmd/coder
19-
ldflags: ["-s -w"]
19+
ldflags: ["-s -w -X github.com/coder/coder/cli/buildinfo.tag={{ .Version }}"]
2020
env: [CGO_ENABLED=0]
2121
goos: [darwin, linux, windows]
2222
goarch: [amd64]
@@ -28,7 +28,7 @@ builds:
2828
- id: coder
2929
dir: cmd/coder
3030
flags: [-tags=embed]
31-
ldflags: ["-s -w"]
31+
ldflags: ["-s -w -X github.com/coder/coder/cli/buildinfo.tag={{ .Version }}"]
3232
env: [CGO_ENABLED=0]
3333
goos: [darwin, linux, windows]
3434
goarch: [amd64, arm64]
@@ -58,3 +58,6 @@ nfpms:
5858

5959
release:
6060
ids: [coder, packages]
61+
62+
snapshot:
63+
name_template: '{{ .Version }}-devel+{{ .ShortCommit }}'

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"coderd",
66
"coderdtest",
77
"codersdk",
8+
"devel",
89
"drpc",
910
"drpcconn",
1011
"drpcmux",

cli/buildinfo/buildinfo.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package buildinfo
2+
3+
import (
4+
"runtime/debug"
5+
"sync"
6+
"time"
7+
8+
"golang.org/x/mod/semver"
9+
)
10+
11+
var (
12+
buildInfo *debug.BuildInfo
13+
buildInfoValid bool
14+
readBuildInfo sync.Once
15+
16+
// Injected with ldflags at build!
17+
tag string
18+
)
19+
20+
// Version returns the semantic version of the build.
21+
// Use golang.org/x/mod/semver to compare versions.
22+
func Version() string {
23+
revision, valid := Revision()
24+
if valid {
25+
revision = "+" + revision[:7]
26+
}
27+
if tag == "" {
28+
return "v0.0.0-devel" + revision
29+
}
30+
if semver.Build(tag) == "" {
31+
tag += revision
32+
}
33+
return "v" + tag
34+
}
35+
36+
// ExternalURL returns a URL referencing the current Coder version.
37+
// For production builds, this will link directly to a release.
38+
// For development builds, this will link to a commit.
39+
func ExternalURL() string {
40+
repo := "https://github.com/coder/coder"
41+
revision, valid := Revision()
42+
if !valid {
43+
return repo
44+
}
45+
return repo + "/commit/" + revision
46+
}
47+
48+
// Revision returns the Git hash of the build.
49+
func Revision() (string, bool) {
50+
return find("vcs.revision")
51+
}
52+
53+
// Time returns when the Git revision was published.
54+
func Time() (time.Time, bool) {
55+
value, valid := find("vcs.time")
56+
if !valid {
57+
return time.Time{}, false
58+
}
59+
parsed, err := time.Parse(time.RFC3339, value)
60+
if err != nil {
61+
panic("couldn't parse time: " + err.Error())
62+
}
63+
return parsed, true
64+
}
65+
66+
// find panics if a setting with the specific key was not
67+
// found in the build info.
68+
func find(key string) (string, bool) {
69+
readBuildInfo.Do(func() {
70+
buildInfo, buildInfoValid = debug.ReadBuildInfo()
71+
})
72+
if !buildInfoValid {
73+
panic("couldn't read build info")
74+
}
75+
for _, setting := range buildInfo.Settings {
76+
if setting.Key != key {
77+
continue
78+
}
79+
return setting.Value, true
80+
}
81+
return "", false
82+
}

cli/buildinfo/buildinfo_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package buildinfo_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
"golang.org/x/mod/semver"
8+
9+
"github.com/coder/coder/cli/buildinfo"
10+
)
11+
12+
func TestBuildInfo(t *testing.T) {
13+
t.Run("Version", func(t *testing.T) {
14+
version := buildinfo.Version()
15+
require.True(t, semver.IsValid(version))
16+
prerelease := semver.Prerelease(version)
17+
require.Equal(t, "-devel", prerelease)
18+
require.Equal(t, "v0", semver.Major(version))
19+
})
20+
t.Run("ExternalURL", func(t *testing.T) {
21+
require.Equal(t, "https://github.com/coder/coder", buildinfo.ExternalURL())
22+
})
23+
// Tests don't include Go build info.
24+
t.Run("NoTime", func(t *testing.T) {
25+
_, valid := buildinfo.Time()
26+
require.False(t, valid)
27+
})
28+
}

cli/root.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import (
44
"net/url"
55
"os"
66
"strings"
7+
"time"
78

89
"github.com/charmbracelet/lipgloss"
910
"github.com/kirsle/configdir"
1011
"github.com/mattn/go-isatty"
1112
"github.com/spf13/cobra"
1213

14+
"github.com/coder/coder/cli/buildinfo"
1315
"github.com/coder/coder/cli/cliui"
1416
"github.com/coder/coder/cli/config"
1517
"github.com/coder/coder/codersdk"
@@ -28,6 +30,7 @@ const (
2830
func Root() *cobra.Command {
2931
cmd := &cobra.Command{
3032
Use: "coder",
33+
Version: buildinfo.Version(),
3134
SilenceUsage: true,
3235
Long: ` ▄█▀ ▀█▄
3336
▄▄ ▀▀▀ █▌ ██▀▀█▄ ▐█
@@ -55,6 +58,7 @@ func Root() *cobra.Command {
5558
`Flags:`, header.Render("Flags:"),
5659
`Additional help topics:`, header.Render("Additional help:"),
5760
).Replace(cmd.UsageTemplate()))
61+
cmd.SetVersionTemplate(versionTemplate())
5862

5963
cmd.AddCommand(
6064
configSSH(),
@@ -142,3 +146,14 @@ func isTTY(cmd *cobra.Command) bool {
142146
}
143147
return isatty.IsTerminal(file.Fd())
144148
}
149+
150+
func versionTemplate() string {
151+
template := `Coder {{printf "%s" .Version}}`
152+
buildTime, valid := buildinfo.Time()
153+
if valid {
154+
template += " " + buildTime.Format(time.UnixDate)
155+
}
156+
template += "\r\n" + buildinfo.ExternalURL()
157+
template += "\r\n"
158+
return template
159+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ require (
234234
github.com/zclconf/go-cty v1.10.0 // indirect
235235
github.com/zeebo/errs v1.2.2 // indirect
236236
go.opencensus.io v0.23.0 // indirect
237-
golang.org/x/mod v0.5.1 // indirect
237+
golang.org/x/mod v0.5.1
238238
golang.org/x/net v0.0.0-20220325170049-de3da57026de // indirect
239239
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
240240
golang.org/x/text v0.3.7 // indirect

0 commit comments

Comments
 (0)