Skip to content

Commit

Permalink
Merge pull request #160 from tonidy/version-flag
Browse files Browse the repository at this point in the history
Add version flag
  • Loading branch information
janpfeifer authored Jan 29, 2025
2 parents 05c5124 + f8a08d2 commit 1724d57
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 34 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Include version information on `git archive'
/version/version.go export-subst
14 changes: 5 additions & 9 deletions internal/specialcmd/specialcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ package specialcmd
import (
_ "embed"
"fmt"
"github.com/janpfeifer/gonb/internal/jpyexec"
"golang.org/x/exp/slices"
"os"
"strings"
"time"

"github.com/janpfeifer/gonb/internal/jpyexec"
"github.com/janpfeifer/gonb/version"
"golang.org/x/exp/slices"

. "github.com/janpfeifer/gonb/common"
"github.com/janpfeifer/gonb/gonbui/protocol"
"github.com/janpfeifer/gonb/internal/goexec"
Expand Down Expand Up @@ -239,13 +241,7 @@ func execSpecialConfig(msg kernel.Message, goExec *goexec.State, cmdStr string,
klog.Errorf("Failed publishing help contents: %+v", err)
}
case "version":
gitHash := os.Getenv(protocol.GONB_GIT_COMMIT)
gitVersion := os.Getenv(protocol.GONB_VERSION)
gitCommitURL := fmt.Sprintf("https://github.com/janpfeifer/gonb/tree/%s", gitHash)
gitTagURL := fmt.Sprintf("https://github.com/janpfeifer/gonb/releases/tag/%s", gitVersion)

err := kernel.PublishMarkdown(msg, fmt.Sprintf(
"**GoNB** version [%s](%s) / Commit: [%s](%s)\n", gitVersion, gitTagURL, gitHash, gitCommitURL))
err := kernel.PublishMarkdown(msg, version.AppVersion.Markdown())
if err != nil {
klog.Errorf("Failed publishing version contents: %+v", err)
}
Expand Down
136 changes: 136 additions & 0 deletions internal/version/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package version

import (
"fmt"
"runtime"
"runtime/debug"
"strconv"
"strings"
)

type VersionInfo struct {
Version string
Commit string
CommitLink string
ReleaseLink string
}

const (
BaseVersionControlURL string = "https://github.com/janpfeifer/gonb"
)

// AppVersion determines version and commit information based on multiple data sources:
// - AppVersion information dynamically added by `git archive` in the remaining to parameters.
// - A hardcoded version number passed as first parameter.
// - Commit information added to the binary by `go build`.
//
// It's supposed to be called like this in combination with setting the `export-subst` attribute for the corresponding
// file in .gitattributes:
//
// var AppVersion = version.AppVersion("1.0.0-rc1", "$Format:%(describe)$", "$Format:%H$")
//
// When exported using `git archive`, the placeholders are replaced in the file and this version information is
// preferred. Otherwise the hardcoded version is used and augmented with commit information from the build metadata.
//
// Source: https://github.com/Icinga/icingadb/blob/51068fff46364385f3c0165aab7b7393fa6a303b/pkg/version/version.go
func AppVersion(version, gitVersion, gitHash string) *VersionInfo {
if !strings.HasPrefix(gitVersion, "$") && !strings.HasPrefix(gitHash, "$") {
versionInfo := &VersionInfo{
Version: gitVersion,
Commit: gitHash,
ReleaseLink: fmt.Sprintf("%s/release/%s", BaseVersionControlURL, gitVersion),
}
if len(gitHash) > 0 {
versionInfo.CommitLink = fmt.Sprintf("%s/tree/%s", BaseVersionControlURL, gitHash)
}

return versionInfo
} else {
var commit string
var releaseVersion string

if info, ok := debug.ReadBuildInfo(); ok {
modified := false

for _, setting := range info.Settings {
switch setting.Key {
case "vcs.revision":
commit = setting.Value
case "vcs.modified":
modified, _ = strconv.ParseBool(setting.Value)
}
if strings.Contains(setting.Key, "ldflags") &&
strings.Contains(setting.Value, "git.tag") {

start := strings.Index(setting.Value, "git.tag=") + 8
end := strings.Index(setting.Value[start:], "'") + start
version = setting.Value[start:end]
}
}

// Same truncation length for the commit hash
const hashLen = 7
releaseVersion = version

if len(commit) >= hashLen {
if modified {
version += "-dirty"
commit += " (modified)"
}
}
}

versionInfo := &VersionInfo{
Version: version,
Commit: commit,
ReleaseLink: fmt.Sprintf("%s/release/%s", BaseVersionControlURL, releaseVersion),
}
if len(commit) > 0 {
versionInfo.CommitLink = fmt.Sprintf("%s/tree/%s", BaseVersionControlURL, commit)
}

return versionInfo
}
}

// GetInfo Get version info
func (v *VersionInfo) GetInfo() VersionInfo {
return *v
}

// String Get version as a string
func (v *VersionInfo) String() string {
return v.Version
}

// Print writes verbose version output to stdout.
func (v *VersionInfo) Print() {
fmt.Println("GoNB version:", v.Version)
fmt.Println()

if len(v.CommitLink) > 0 {
fmt.Println("Version control info:")
fmt.Printf(" Commit: %s \n", v.CommitLink)
fmt.Printf(" Release: %s \n", v.ReleaseLink)
fmt.Println()
}

fmt.Println("Build info:")
fmt.Printf(" Go version: %s (OS: %s, arch: %s)\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
}

func (v *VersionInfo) Markdown() string {
var markdown string
markdown += fmt.Sprintf("## GoNB version: `%s`\n\n", v.Version)

if len(v.CommitLink) > 0 {
markdown += "### Version Control Info\n"
markdown += fmt.Sprintf("- Commit: [%s](%s)\n", v.Commit, v.CommitLink)
markdown += fmt.Sprintf("- Release: [%s](%s)\n\n", v.Version, v.ReleaseLink)
}

markdown += "### Build Info\n"
markdown += fmt.Sprintf("- Go version: %s (OS: %s, Arch: %s)\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)

return markdown
}
133 changes: 133 additions & 0 deletions internal/version/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package version

import (
"bytes"
"os"
"runtime"
"strings"
"testing"
)

func TestAppVersion(t *testing.T) {
tests := []struct {
name string
version string
gitDescribe string
gitHash string
want *VersionInfo
}{
{
name: "With git information",
version: "1.0.0",
gitDescribe: "v1.0.0",
gitHash: "abc1234",
want: &VersionInfo{
Version: "v1.0.0",
Commit: "abc1234",
CommitLink: "https://github.com/janpfeifer/gonb/tree/abc1234",
},
},
{
name: "Without git information",
version: "1.0.0",
gitDescribe: "$Format:%(describe)$",
gitHash: "$Format:%H$",
want: &VersionInfo{
Version: "1.0.0",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := AppVersion(tt.version, tt.gitDescribe, tt.gitHash)

if got.Version != tt.want.Version {
t.Errorf("AppVersion().Version = %v, want %v", got.Version, tt.want.Version)
}

if got.Commit != tt.want.Commit {
t.Errorf("AppVersion().Commit = %v, want %v", got.Commit, tt.want.Commit)
}

if got.CommitLink != tt.want.CommitLink {
t.Errorf("AppVersion().VersionControlLink = %v, want %v", got.CommitLink, tt.want.CommitLink)
}
})
}
}

func TestVersionInfo_GetInfo(t *testing.T) {
v := &VersionInfo{
Version: "1.0.0",
Commit: "abc123",
CommitLink: "https://github.com/janpfeifer/gonb/tree/abc123",
}

got := v.GetInfo()
if got != *v {
t.Errorf("VersionInfo.GetInfo() = %v, want %v", got, *v)
}
}

func TestVersionInfo_String(t *testing.T) {
v := &VersionInfo{
Version: "1.0.0",
Commit: "abc123",
CommitLink: "https://github.com/janpfeifer/gonb/tree/abc123",
}

got := v.String()
if got != v.Version {
t.Errorf("VersionInfo.String() = %v, want %v", got, v.Version)
}
}

func TestVersionInfo_Print(t *testing.T) {
v := &VersionInfo{
Version: "1.0.0",
Commit: "abc123",
CommitLink: "https://github.com/janpfeifer/gonb/tree/abc123",
}

// Capture output to verify it contains expected information
output := captureOutput(func() {
v.Print()
})

// Check if output contains expected information
expectedStrings := []string{
"GoNB version: 1.0.0",
"Version control info:",
v.CommitLink,
"Build info:",
runtime.Version(),
runtime.GOOS,
runtime.GOARCH,
}

for _, expected := range expectedStrings {
if !strings.Contains(output, expected) {
t.Errorf("Print() output missing expected string: %s", expected)
}
}
}

// Helper function to capture stdout dynamically
func captureOutput(f func()) string {
// Redirect stdout to a buffer
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

// Call the function
f()

// Restore stdout and read buffer
w.Close()
os.Stdout = old

var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
return buf.String()
}
Loading

0 comments on commit 1724d57

Please sign in to comment.