Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output and consider Go toolchain version, too #156

Merged
merged 2 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cmd/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ func newCheckCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "check",
Short: "Check the latest version of the binary installed by 'go install'",
Long: `Check the latest version of the binary installed by 'go install'
Long: `Check the latest version and build toolchain of the binary installed by 'go install'

check subcommand checks if the binary is the latest version
and if it has been built with the current version of go installed,
and displays the name of the binary that needs to be updated.
However, do not update`,
ValidArgsFunction: completePathBinaries,
Expand Down Expand Up @@ -92,7 +93,7 @@ func doCheck(pkgs []goutil.Package, cpus int) int {
err = fmt.Errorf(" %s %w", p.Name, err)
}
p.Version.Latest = latestVer
if !goutil.IsAlreadyUpToDate(*p.Version) {
if !p.IsAlreadyUpToDate() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure the mutex lock is held for the shortest time necessary to avoid potential performance bottlenecks.

Consider moving the mu.Unlock() call immediately after the append operation to minimize the critical section:

mu.Lock()
needUpdatePkgs = append(needUpdatePkgs, p)
mu.Unlock()
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if !p.IsAlreadyUpToDate() {
if !p.IsAlreadyUpToDate() {
mu.Lock()
needUpdatePkgs = append(needUpdatePkgs, p)
mu.Unlock()
}

mu.Lock()
needUpdatePkgs = append(needUpdatePkgs, p)
mu.Unlock()
Expand Down
3 changes: 2 additions & 1 deletion cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ func newUpdateCmd() *cobra.Command {
Long: `Update binaries installed by 'go install'

If you execute '$ gup update', gup gets the package path of all commands
under $GOPATH/bin and automatically updates commands to the latest version.`,
under $GOPATH/bin and automatically updates commands to the latest version,
using the current installed Go toolchain.`,
Run: func(cmd *cobra.Command, args []string) {
OsExit(gup(cmd, args))
},
Expand Down
13 changes: 9 additions & 4 deletions internal/goutil/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,20 +161,25 @@ func ExampleInstall() {
}

func ExampleIsAlreadyUpToDate() {
// Create Version object with Current and Latest package version
// Create Version object with Current and Latest package and Go versions
ver := goutil.Version{
Current: "v1.9.0",
Latest: "v1.9.1",
}
goVer := goutil.Version{
Current: "go1.21.1",
Latest: "go1.22.4",
}
pkg := goutil.Package{Version: &ver, GoVersion: &goVer}

// Check if Current is already up to date (expected: false)
if goutil.IsAlreadyUpToDate(ver) {
if pkg.IsAlreadyUpToDate() {
fmt.Println("Example IsAlreadyUpToDate: already up to date.")
} else {
fmt.Println("Example IsAlreadyUpToDate: outdated. Newer latest version exists.")
fmt.Println("Example IsAlreadyUpToDate: outdated. Newer latest version or installed Go toolchain exists.")
}

// Output: Example IsAlreadyUpToDate: outdated. Newer latest version exists.
// Output: Example IsAlreadyUpToDate: outdated. Newer latest version or installed Go toolchain exists.
}

func ExampleNewGoPaths() {
Expand Down
94 changes: 82 additions & 12 deletions internal/goutil/goutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

"github.com/fatih/color"
Expand Down Expand Up @@ -48,6 +49,8 @@ type Package struct {
ModulePath string
// Version store Package version (current and latest).
Version *Version
// GoVersion stores version of Go toolchain
GoVersion *Version
}

// Version is package version information.
Expand All @@ -73,30 +76,70 @@ func (p *Package) SetLatestVer() {

// CurrentToLatestStr returns string about the current version and the latest version
func (p *Package) CurrentToLatestStr() string {
if IsAlreadyUpToDate(*p.Version) {
return "Already up-to-date: " + color.GreenString(p.Version.Latest)
if p.IsAlreadyUpToDate() {
return "Already up-to-date: " + color.GreenString(p.Version.Latest) + " / " + color.GreenString(p.GoVersion.Current)
}
return color.GreenString(p.Version.Current) + " to " + color.YellowString(p.Version.Latest)
var ret string
if p.Version.Current != p.Version.Latest {
ret += color.GreenString(p.Version.Current) + " to " + color.YellowString(p.Version.Latest)
}
if p.GoVersion.Current != p.GoVersion.Latest {
if len(ret) != 0 {
ret += ", "
}
ret += color.GreenString(p.GoVersion.Current) + " to " + color.YellowString(p.GoVersion.Latest)
}
return ret
Comment on lines +79 to +92
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enhanced the string representation functions to include both package and Go toolchain versions.

However, consider simplifying the logic to improve readability and maintainability.

Refactor the repeated conditional logic into a helper function:

func formatVersionChange(current, latest string) string {
	if current == latest {
		return color.GreenString(current)
	}
	return "current: " + color.GreenString(current) + ", latest: " + color.YellowString(latest)
}

Also applies to: 97-123

}

// VersionCheckResultStr returns string about command version check.
func (p *Package) VersionCheckResultStr() string {
if IsAlreadyUpToDate(*p.Version) {
return "Already up-to-date: " + color.GreenString(p.Version.Latest)
if p.IsAlreadyUpToDate() {
return "Already up-to-date: " + color.GreenString(p.Version.Latest) + " / " + color.GreenString(p.GoVersion.Current)
}
var ret string
// TODO: yellow only if latest > current
if p.Version.Current == p.Version.Latest {
ret += color.GreenString(p.Version.Current)
} else {
ret += "current: " + color.GreenString(p.Version.Current) + ", latest: "
if versionUpToDate(p.Version.Current, p.Version.Latest) {
ret += color.GreenString(p.Version.Latest)
} else {
ret += color.YellowString(p.Version.Latest)
}
}
return "current: " + color.GreenString(p.Version.Current) + ", latest: " + color.YellowString(p.Version.Latest)
ret += " / "
if p.GoVersion.Current == p.GoVersion.Latest {
ret += color.GreenString(p.GoVersion.Current)
} else {
ret += "current: " + color.GreenString(p.GoVersion.Current) + ", installed: "
if versionUpToDate(p.GoVersion.Current, p.GoVersion.Latest) {
ret += color.GreenString(p.GoVersion.Latest)
} else {
ret += color.YellowString(p.GoVersion.Latest)
}
}
return ret
}

// IsAlreadyUpToDate return whether binary is already up to date or not.
func IsAlreadyUpToDate(ver Version) bool {
if ver.Current == ver.Latest {
func (p *Package) IsAlreadyUpToDate() bool {
if p.Version.Current == p.Version.Latest && p.GoVersion.Current == p.GoVersion.Latest {
return true
}

return strings.Compare(
strings.TrimLeft(ver.Current, "v"),
strings.TrimLeft(ver.Latest, "v"),
) >= 0
return versionUpToDate(
strings.TrimLeft(p.Version.Current, "v"),
strings.TrimLeft(p.Version.Latest, "v"),
) && versionUpToDate(
strings.TrimLeft(p.GoVersion.Current, "go"),
strings.TrimLeft(p.GoVersion.Latest, "go"),
)
}

func versionUpToDate(current, available string) bool {
return current >= available
}

// NewGoPaths return GoPaths instance.
Expand Down Expand Up @@ -288,6 +331,10 @@ func BinaryPathList(path string) ([]string, error) {
// GetPackageInformation return golang package information.
func GetPackageInformation(binList []string) []Package {
pkgs := []Package{}
goVer, err := GetInstalledGoVersion()
if err != nil {
goVer = "unknown"
}
for _, v := range binList {
info, err := buildinfo.ReadFile(v)
if err != nil {
Expand All @@ -299,8 +346,11 @@ func GetPackageInformation(binList []string) []Package {
ImportPath: info.Path,
ModulePath: info.Main.Path,
Version: NewVersion(),
GoVersion: NewVersion(),
}
pkg.Version.Current = info.Main.Version
pkg.GoVersion.Current = info.GoVersion
pkg.GoVersion.Latest = goVer
pkgs = append(pkgs, pkg)
}
return pkgs
Expand All @@ -318,3 +368,23 @@ func GetPackageVersion(cmdName string) string {
}
return info.Main.Version
}

var goVersionRegex = regexp.MustCompile(`(^|\s)(go[1-9]\S+)`)

func GetInstalledGoVersion() (string, error) {
var stdout, stderr bytes.Buffer
cmd := exec.Command(goExe, "version")
cmd.Stdout = &stdout
cmd.Stderr = &stderr

err := cmd.Run()
if err != nil {
return "", fmt.Errorf("can't check go version:\n%s", stderr.String())
}

if m := goVersionRegex.FindStringSubmatch(stdout.String()); m != nil {
return m[2], nil
}

return "", fmt.Errorf("can't find go version string in %q", strings.TrimSpace(stdout.String()))
}
84 changes: 64 additions & 20 deletions internal/goutil/goutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,46 +283,58 @@ func TestInstallMaster_golden(t *testing.T) {

func TestIsAlreadyUpToDate_golden(t *testing.T) {
for i, test := range []struct {
curr string
latest string
expect bool
curr string
latest string
currGo string
latestGo string
expect bool
}{
// Regular cases
{curr: "v1.9.0", latest: "v1.9.1", expect: false},
{curr: "v1.9.0", latest: "v1.9.0", expect: true},
{curr: "v1.9.1", latest: "v1.9.0", expect: true},
{curr: "v1.9.0", latest: "v1.9.1", currGo: "go1.22.4", latestGo: "go1.22.4", expect: false},
{curr: "v1.9.0", latest: "v1.9.0", currGo: "go1.22.4", latestGo: "go1.22.4", expect: true},
{curr: "v1.9.1", latest: "v1.9.0", currGo: "go1.22.4", latestGo: "go1.22.4", expect: true},
{curr: "v1.9.0", latest: "v1.9.0", currGo: "go1.22.1", latestGo: "go1.22.4", expect: false},
// Irregular cases (untagged versions)
{
curr: "v0.0.0-20220913151710-7c6e287988f3",
latest: "v0.0.0-20210608161538-9736a4bde949",
expect: true,
curr: "v0.0.0-20220913151710-7c6e287988f3",
latest: "v0.0.0-20210608161538-9736a4bde949",
currGo: "go1.22.4",
latestGo: "go1.22.4",
expect: true,
},
{
curr: "v0.0.0-20210608161538-9736a4bde949",
latest: "v0.0.0-20220913151710-7c6e287988f3",
expect: false,
curr: "v0.0.0-20210608161538-9736a4bde949",
latest: "v0.0.0-20220913151710-7c6e287988f3",
currGo: "go1.22.4",
latestGo: "go1.22.4",
expect: false,
},
// Compatibility between go-style semver and pure-semver
{curr: "v1.9.0", latest: "1.9.1", expect: false},
{curr: "v1.9.1", latest: "1.9.0", expect: true},
{curr: "1.9.0", latest: "v1.9.1", expect: false},
{curr: "1.9.1", latest: "v1.9.0", expect: true},
{curr: "v1.9.0", latest: "1.9.1", currGo: "go1.22.4", latestGo: "go1.22.4", expect: false},
{curr: "v1.9.1", latest: "1.9.0", currGo: "go1.22.4", latestGo: "go1.22.4", expect: true},
{curr: "1.9.0", latest: "v1.9.1", currGo: "go1.22.4", latestGo: "go1.22.4", expect: false},
{curr: "1.9.1", latest: "v1.9.0", currGo: "go1.22.4", latestGo: "go1.22.4", expect: true},
// Issue #36
{curr: "v1.9.1-0.20220908165354-f7355b5d2afa", latest: "v1.9.0", expect: true},
{curr: "v1.9.1-0.20220908165354-f7355b5d2afa", latest: "v1.9.0", currGo: "go1.22.4", latestGo: "go1.22.4", expect: true},
} {
verTmp := Version{
Current: test.curr,
Latest: test.latest,
}
goVerTmp := Version{
Current: test.currGo,
Latest: test.latestGo,
}
pkg := Package{Version: &verTmp, GoVersion: &goVerTmp}

want := test.expect
got := IsAlreadyUpToDate(verTmp)
got := pkg.IsAlreadyUpToDate()

// Assert to be equal
if want != got {
t.Errorf(
"case #%v failed. got: (\"%v\" >= \"%v\") = %v, want: %v",
i, test.curr, test.latest, got, want,
"case #%v failed. got: (\"%v\" >= \"%v\" / \"%v\" >= \"%v\") = %v, want: %v",
i, test.curr, test.latest, test.currGo, test.latestGo, got, want,
)
}
}
Expand Down Expand Up @@ -545,6 +557,10 @@ func TestPackage_CurrentToLatestStr_not_up_to_date(t *testing.T) {
Current: "v0.0.1",
Latest: "v1.9.1",
},
GoVersion: &Version{
Current: "go1.22.4",
Latest: "go1.22.4",
},
}

// Assert to contain the expected message
Expand All @@ -565,6 +581,10 @@ func TestPackage_VersionCheckResultStr_not_up_to_date(t *testing.T) {
Current: "v0.0.1",
Latest: "v1.9.1",
},
GoVersion: &Version{
Current: "go1.22.4",
Latest: "go1.22.4",
},
}

// Assert to contain the expected message
Expand All @@ -575,3 +595,27 @@ func TestPackage_VersionCheckResultStr_not_up_to_date(t *testing.T) {
t.Errorf("got: %v, want: %v", got, wantContain)
}
}

func TestPackage_VersionCheckResultStr_go_not_up_to_date(t *testing.T) {
pkgInfo := Package{
Name: "foo",
ImportPath: "github.com/dummy_name/dummy",
ModulePath: "github.com/dummy_name/dummy/foo",
Version: &Version{
Current: "v1.9.1",
Latest: "v1.9.1",
},
GoVersion: &Version{
Current: "go1.22.1",
Latest: "go1.22.4",
},
}

// Assert to contain the expected message
wantContain := "current: go1.22.1, installed: go1.22.4"
got := pkgInfo.VersionCheckResultStr()

if !strings.Contains(got, wantContain) {
t.Errorf("got: %v, want: %v", got, wantContain)
}
}
Loading