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() {