diff --git a/VERSION b/VERSION index 341cf11..5a3f665 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 \ No newline at end of file +0.3.1-rc2 \ No newline at end of file diff --git a/cmd/bedrock_helper/main.go b/cmd/bedrock_helper/main.go index ee1a620..d38822f 100644 --- a/cmd/bedrock_helper/main.go +++ b/cmd/bedrock_helper/main.go @@ -8,6 +8,7 @@ import ( "strings" "text/tabwriter" + "github.com/end2endzone/BedrockHelper/internal/build" lib "github.com/end2endzone/BedrockHelper/minecraftbedrock" ) @@ -39,8 +40,8 @@ func printVersion(verbose bool) { fmt.Fprintf(os.Stdout, "Version %s.\n", GetProductVersionString()) if verbose { - verboseVersion := GetProductVersionVerboseString() - fmt.Fprintf(os.Stdout, "%s", verboseVersion) + metadata := build.GetBuildMetadata() + fmt.Fprintf(os.Stdout, "Build metadata:%s\n", metadata) } } diff --git a/cmd/bedrock_helper/version.go b/cmd/bedrock_helper/version.go index 7c62533..ee56a46 100644 --- a/cmd/bedrock_helper/version.go +++ b/cmd/bedrock_helper/version.go @@ -1,12 +1,13 @@ package main import ( - "encoding/json" "fmt" - "runtime/debug" "strings" "time" + root "github.com/end2endzone/BedrockHelper" + "github.com/end2endzone/BedrockHelper/internal/build" + "golang.org/x/mod/semver" ) @@ -149,34 +150,6 @@ func findGitHashInSemVer(input string) string { } */ -// getVersionControlRevisionFromMetadata get the values from version control system from the metadata injected at build time. -// For example: -// - version : `v0.1.1-0.20260815155314-4ad116a8bdf3` -// - revision: `6cb0ce064a20aeb026d039c12e7ab83b10ad1c63` -// - datetime: `2026-08-16T15:12:22Z` -// -// Returns empty value on error or when metadata is not available. -func getVersionControlValuesFromMetadata() (version, revision, datetime string) { - info, ok := debug.ReadBuildInfo() - if !ok { - // Metadata not available - return - } - - version = info.Main.Version - - for _, setting := range info.Settings { - switch setting.Key { - case "vcs.revision": - revision = setting.Value - case "vcs.time": - datetime = setting.Value - } - } - - return -} - func CreateInvalidProductVersion() ProductVersion { p := ProductVersion{ Version: InvalidVersion, @@ -186,15 +159,16 @@ func CreateInvalidProductVersion() ProductVersion { return p } -// GetPseudoVersionFromMetadata parses the go pseudo-version into a ProductVersion. +// GetProductVersionFromMetadata parses the go pseudo-version into a ProductVersion. // A pseudo-version is a string in format "v[version]-[datetime]-[revision][dirtybit]". // For example: "v1.2.3-20260815131415-de3798c09c08+dirty". -func GetPseudoVersionFromMetadata() ProductVersion { +func GetProductVersionFromMetadata() ProductVersion { p := CreateInvalidProductVersion() - version, revision, datetime := getVersionControlValuesFromMetadata() - if version == "" { - // Metadata not available + version := build.GetVersionFromMetadata() + revision, datetime := build.GetGitHashAndDateFromMetadata() + if version == "" || revision == "" || datetime == "" { + // Metadata not available or not compiled from git version control. // Return an invalid ProductVersion return p } @@ -204,8 +178,17 @@ func GetPseudoVersionFromMetadata() ProductVersion { // For example, if you run your app using `go run .` or compile it locally without tags, Go sets info.Main.Version to the string "(devel)". p.Version = version - // Truncate revision to 12 characters - p.CommitHash = revision[0:12] + // Remove `+dirty` from version and standardizes it + p.Version = semver.Canonical(p.Version) + + // Strip the leading "v" to only get the digits and pre-release labels + p.Version = strings.TrimPrefix(p.Version, "v") + + // Parse revision + if revision != "" { + // Truncate revision to 12 characters or less + p.CommitHash = revision[0:min(12, len(revision))] + } // Parse datetime { @@ -221,12 +204,6 @@ func GetPseudoVersionFromMetadata() ProductVersion { } } - // Remove `+dirty` from version and standardizes it - p.Version = semver.Canonical(p.Version) - - // Strip the leading "v" if you strictly want the digits and pre-release labels - p.Version = strings.TrimPrefix(p.Version, "v") - // Remove datetime from version string { // datetime == "2026-08-16T15:12:22Z" @@ -284,41 +261,43 @@ func GetProductVersion() ProductVersion { } // Get a valid the product version from metadata - metadata := GetPseudoVersionFromMetadata() + metadata := GetProductVersionFromMetadata() if metadata.IsValid() { return metadata } - // Return an invalid version. - return CreateInvalidProductVersion() -} + // Try to build a ProductVersion from the version metadata. + tagName := build.GetVersionFromMetadata() + if tagName != "" && tagName != "(devel)" { + // Strip the leading "v" to only get the digits and pre-release labels + tagName = strings.TrimPrefix(tagName, "v") -func GetProductVersionString() string { - p := GetProductVersion() + // This product version is still invalid because it has no CommitHash and no BuildDate. + // It will require a different formatting + p := CreateInvalidProductVersion() + p.Version = tagName + return p + } - msg := fmt.Sprintf("%s (%s) compiled on %s", p.Version, p.CommitHash, p.BuildDate) - return msg + // Build a ProductVersion from the VERSION file instead. + // This product version is still invalid because it has no CommitHash and no BuildDate. + // It will require a different formatting + p := CreateInvalidProductVersion() + p.Version = root.GetVersionFromVersionFile() + return p } -func GetProductVersionVerboseString() string { - info, ok := debug.ReadBuildInfo() - if !ok { - // Can't do better - return "" - } - - // Add detailed version string - msg := fmt.Sprintf("%s\n", info.Main.Version) +func GetProductVersionString() string { + p := GetProductVersion() - // Serialize BuildSettings to json - jsonData, err := json.MarshalIndent(info.Settings, "", " ") - if err != nil { - msg += fmt.Sprintf("error: %v\n", err) - return msg + var msg string + if p.IsValid() { + // Version 0.3.1-rc1 (820b508a0f53, released 2026-08-18) + msg = fmt.Sprintf("%s (%s, released %s)", p.Version, p.CommitHash, p.BuildDate) + } else { + // Invalid product version. Assume only p.Version is valid. + msg = fmt.Sprintf("%s", p.Version) } - // Add BuildSettings info to string - msg += fmt.Sprintf("%v\n", string(jsonData)) - return msg } diff --git a/cmd/bedrock_helper/version_test.go b/cmd/bedrock_helper/version_test.go new file mode 100644 index 0000000..ba0c206 --- /dev/null +++ b/cmd/bedrock_helper/version_test.go @@ -0,0 +1,17 @@ +package main + +import ( + "testing" + + root "github.com/end2endzone/BedrockHelper" + + "github.com/stretchr/testify/require" +) + +func TestGetProductVersionStringContainsVERSIONFile(t *testing.T) { + productVersion := GetProductVersionString() + versionFileContent := root.GetVersionFromVersionFile() + + // Assert the string returned from GetProductVersionString() contains the actual VERSION string. + require.Contains(t, productVersion, versionFileContent, "GetProductVersionString() which is '%s' does not contains the VERSION which is '%s'. The VERSION file might be outdated.", productVersion, versionFileContent) +} diff --git a/go.mod b/go.mod index 265256b..23e4401 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,13 @@ go 1.26 // Modules. Use `go get ` to add more. require github.com/stretchr/testify v1.11.1 -require github.com/tailscale/hujson v0.0.0-20260727124030-b80ff77dac4f +require ( + github.com/tailscale/hujson v0.0.0-20260727124030-b80ff77dac4f + golang.org/x/mod v0.40.0 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/mod v0.40.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/build/build.go b/internal/build/build.go new file mode 100644 index 0000000..b1a7fe1 --- /dev/null +++ b/internal/build/build.go @@ -0,0 +1,86 @@ +package build + +import ( + "runtime/debug" +) + +// GetVersionFromMetadata get the version value from the metadata injected at build time. +// For example: +// - v0.1.1-0.20260815155314-4ad116a8bdf3 +// - v0.2.0-alpha +// +// When compiled from soure code in git version control, the version will be formattated such as `v0.1.1-0.20260815155314-4ad116a8bdf3`. +// +// When compiled using `go install URL@tag`, the version variable contains the name of the tag used. +// For example, when compiled with command +// `go install github.com/username/reponame/cmd/my-app@v0.2.0-alpha`, the returned version will be `v0.2.0-alpha`. +// +// When using `latest` for tag, the version metadata contains the latest tag that is not `alpha`, `beta`, etc. +// For example, when the following tag exists: +// - v0.2.0 +// - v0.3.0 +// - v0.3.1-alpha +// - v0.3.1-beta +// Go's toolchain/compiler will resolve the latest version as v0.3.0 and will set version metadata to `v0.3.0`. +// +// Warning: version can default to `(devel)` if compilation of source code is make outside of a version control system. +// +// Returns empty values on error or when metadata is not available. +func GetVersionFromMetadata() string { + info, ok := debug.ReadBuildInfo() + if !ok { + // build-info metadata not available + return "" + } + + return info.Main.Version +} + +// GetGitHashAndDateFromMetadata get the values from git's version control system from the metadata injected at build time. +// For example: +// - revision: `6cb0ce064a20aeb026d039c12e7ab83b10ad1c63` +// - datetime: `2026-08-16T15:12:22Z` +// +// Returns empty values on error or when metadata is not available. +func GetGitHashAndDateFromMetadata() (revision, datetime string) { + info, ok := debug.ReadBuildInfo() + if !ok { + // Metadata not available + return + } + + // Is compiled from source code in Git ? + isGitVersionControl := false + var tmp_revision string + var tmp_datetime string + for _, setting := range info.Settings { + switch setting.Key { + case "vcs": + isGitVersionControl = (setting.Value == "git") + case "vcs.revision": + tmp_revision = setting.Value + case "vcs.time": + tmp_datetime = setting.Value + } + } + + if isGitVersionControl { + revision = tmp_revision + datetime = tmp_datetime + } + + return +} + +// GetBuildMetadata get the full metadata json data as a string. +// Returns empty values on error or when metadata is not available. +func GetBuildMetadata() string { + info, ok := debug.ReadBuildInfo() + if !ok { + // Metadata not available + return "" + } + + metadata := info.String() + return metadata +} diff --git a/root.go b/root.go new file mode 100644 index 0000000..f9eef59 --- /dev/null +++ b/root.go @@ -0,0 +1,12 @@ +package root + +import ( + _ "embed" +) + +//go:embed VERSION +var FileVersion string + +func GetVersionFromVersionFile() string { + return FileVersion +}