From 1c27f081e0a769b669b63994fb84e841ee069553 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 3 Feb 2023 15:38:53 +0100 Subject: [PATCH] Include build information and add version command (#194) Includes relevant fields listed on https://goreleaser.com/customization/templates/ into build artifacts. The version command outputs the version by default: ``` $ bricks version 0.0.21-devel ``` Or all build information if `--json` is specified: ``` $ bricks version --json { "ProjectName": "bricks", "Version": "0.0.21-devel", "Branch": "version-info", "Tag": "v0.0.20", "ShortCommit": "193b56b", "FullCommit": "193b56b0929128c0836d35e913c46fd66fa2a93c", "CommitTime": "2023-02-02T22:04:42+01:00", "Summary": "v0.0.20-5-g193b56b", "Major": 0, "Minor": 0, "Patch": 20, "Prerelease": "", "IsSnapshot": true, "BuildTime": "2023-02-02T22:07:36+01:00" } ``` --- .goreleaser.yaml | 19 ++++++ cmd/version/version.go | 34 +++++++++++ internal/build/info.go | 117 ++++++++++++++++++++++++++++++++++++ internal/build/info_test.go | 9 +++ internal/build/variables.go | 18 ++++++ main.go | 1 + 6 files changed, 198 insertions(+) create mode 100644 cmd/version/version.go create mode 100644 internal/build/info.go create mode 100644 internal/build/info_test.go create mode 100644 internal/build/variables.go diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 3effb5b3..6880dafe 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -9,6 +9,25 @@ builds: - -trimpath ldflags: - '-s -w' + - -X github.com/databricks/bricks/internal/build.buildProjectName={{ .ProjectName }} + - -X github.com/databricks/bricks/internal/build.buildVersion={{ .Version }} + + # Git information + - -X github.com/databricks/bricks/internal/build.buildBranch={{ .Branch }} + - -X github.com/databricks/bricks/internal/build.buildTag={{ .Tag }} + - -X github.com/databricks/bricks/internal/build.buildShortCommit={{ .ShortCommit }} + - -X github.com/databricks/bricks/internal/build.buildFullCommit={{ .FullCommit }} + - -X github.com/databricks/bricks/internal/build.buildCommitTimestamp={{ .CommitTimestamp }} + - -X github.com/databricks/bricks/internal/build.buildSummary={{ .Summary }} + + # Version information + - -X github.com/databricks/bricks/internal/build.buildMajor={{ .Major }} + - -X github.com/databricks/bricks/internal/build.buildMinor={{ .Minor }} + - -X github.com/databricks/bricks/internal/build.buildPatch={{ .Patch }} + - -X github.com/databricks/bricks/internal/build.buildPrerelease={{ .Prerelease }} + - -X github.com/databricks/bricks/internal/build.buildIsSnapshot={{ .IsSnapshot }} + - -X github.com/databricks/bricks/internal/build.buildTimestamp={{ .Timestamp }} + goos: - windows - linux diff --git a/cmd/version/version.go b/cmd/version/version.go new file mode 100644 index 00000000..6ae56e60 --- /dev/null +++ b/cmd/version/version.go @@ -0,0 +1,34 @@ +package version + +import ( + "encoding/json" + "fmt" + + "github.com/databricks/bricks/cmd/root" + "github.com/databricks/bricks/internal/build" + "github.com/spf13/cobra" +) + +var detail = false + +var versionCmd = &cobra.Command{ + Use: "version", + Args: cobra.NoArgs, + + RunE: func(cmd *cobra.Command, args []string) error { + info := build.GetInfo() + if detail { + enc := json.NewEncoder(cmd.OutOrStdout()) + enc.SetIndent("", " ") + return enc.Encode(info) + } + + fmt.Fprintln(cmd.OutOrStdout(), info.Version) + return nil + }, +} + +func init() { + versionCmd.Flags().BoolVar(&detail, "detail", false, "output detailed version information as JSON") + root.RootCmd.AddCommand(versionCmd) +} diff --git a/internal/build/info.go b/internal/build/info.go new file mode 100644 index 00000000..41b2600e --- /dev/null +++ b/internal/build/info.go @@ -0,0 +1,117 @@ +package build + +import ( + "fmt" + "runtime/debug" + "strconv" + "sync" + "time" + + "golang.org/x/mod/semver" +) + +type Info struct { + ProjectName string + Version string + + Branch string + Tag string + ShortCommit string + FullCommit string + CommitTime time.Time + Summary string + + Major int64 + Minor int64 + Patch int64 + Prerelease string + IsSnapshot bool + BuildTime time.Time +} + +var info Info + +var once sync.Once + +// getDefaultBuildVersion uses build information stored by Go itself +// to synthesize a build version if one wasn't set. +// This is necessary if the binary was not built through goreleaser. +func getDefaultBuildVersion() string { + bi, ok := debug.ReadBuildInfo() + if !ok { + panic("unable to read build info") + } + + m := make(map[string]string) + for _, s := range bi.Settings { + m[s.Key] = s.Value + } + + out := "0.0.0-dev" + + // Append revision as build metadata. + if v, ok := m["vcs.revision"]; ok { + // First 12 characters of the commit SHA is plenty to identify one. + out = fmt.Sprintf("%s+%s", out, v[0:12]) + } + + return out +} + +func initialize() { + // If buildVersion is empty it means the binary was NOT built through goreleaser. + // We try to pull version information from debug.BuildInfo(). + if buildVersion == "" { + buildVersion = getDefaultBuildVersion() + } + + // Confirm that buildVersion is valid semver. + // Note that the semver package requires a leading 'v'. + if !semver.IsValid("v" + buildVersion) { + panic(fmt.Sprintf(`version is not a valid semver string: "%s"`, buildVersion)) + } + + info = Info{ + ProjectName: buildProjectName, + Version: buildVersion, + + Branch: buildBranch, + Tag: buildTag, + ShortCommit: buildShortCommit, + FullCommit: buildFullCommit, + CommitTime: parseTime(buildCommitTimestamp), + Summary: buildSummary, + + Major: parseInt(buildMajor), + Minor: parseInt(buildMinor), + Patch: parseInt(buildPatch), + Prerelease: buildPrerelease, + IsSnapshot: parseBool(buildIsSnapshot), + BuildTime: parseTime(buildTimestamp), + } +} + +func GetInfo() Info { + once.Do(initialize) + return info +} + +func parseInt(s string) int64 { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + panic(err) + } + return i +} + +func parseBool(s string) bool { + b, err := strconv.ParseBool(s) + if err != nil { + panic(err) + } + return b +} + +func parseTime(s string) time.Time { + return time.Unix(parseInt(s), 0) +} diff --git a/internal/build/info_test.go b/internal/build/info_test.go new file mode 100644 index 00000000..1ae94fbc --- /dev/null +++ b/internal/build/info_test.go @@ -0,0 +1,9 @@ +package build + +import ( + "testing" +) + +func TestGetDetails(t *testing.T) { + GetInfo() +} diff --git a/internal/build/variables.go b/internal/build/variables.go new file mode 100644 index 00000000..31e7d813 --- /dev/null +++ b/internal/build/variables.go @@ -0,0 +1,18 @@ +package build + +var buildProjectName string = "bricks" +var buildVersion string = "" + +var buildBranch string = "undefined" +var buildTag string = "undefined" +var buildShortCommit string = "00000000" +var buildFullCommit string = "0000000000000000000000000000000000000000" +var buildCommitTimestamp string = "0" +var buildSummary string = "v0.0.0" + +var buildMajor string = "0" +var buildMinor string = "0" +var buildPatch string = "0" +var buildPrerelease string = "" +var buildIsSnapshot string = "false" +var buildTimestamp string = "0" diff --git a/main.go b/main.go index 1a6958fb..a3131c4d 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "github.com/databricks/bricks/cmd/root" _ "github.com/databricks/bricks/cmd/sync" _ "github.com/databricks/bricks/cmd/test" + _ "github.com/databricks/bricks/cmd/version" ) func main() {