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 all commits
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
2 changes: 1 addition & 1 deletion cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Finally, you execute the export subcommand in this state.`,
return cmd
}

func runImport(cmd *cobra.Command, args []string) int {
func runImport(cmd *cobra.Command, _ []string) int {
dryRun, err := cmd.Flags().GetBool("dry-run")
if err != nil {
print.Err(fmt.Errorf("%s: %w", "can not parse command line argument (--dry-run)", err))
Expand Down
6 changes: 3 additions & 3 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ func TestExecute_Import_WithInputOption(t *testing.T) {

contain := false
for _, v := range got {
if strings.Contains(v, "gup:INFO : [1/1] github.com/nao1215/gup") {
if strings.Contains(v, "[1/1] github.com/nao1215/gup") {
contain = true
}
}
Expand Down Expand Up @@ -694,7 +694,7 @@ func TestExecute_Update(t *testing.T) {

contain := false
for _, v := range got {
if strings.Contains(v, "gup:INFO : [1/1] github.com/nao1215/gal/cmd/gal") {
if strings.Contains(v, "[1/1] github.com/nao1215/gal/cmd/gal") {
contain = true
}
}
Expand Down Expand Up @@ -791,7 +791,7 @@ func TestExecute_Update_DryRunAndNotify(t *testing.T) {

contain := false
for _, v := range got {
if strings.Contains(v, "gup:INFO : [1/1] github.com/nao1215/posixer") {
if strings.Contains(v, "[1/1] github.com/nao1215/posixer") {
contain = true
}
}
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/mattn/go-colorable v0.1.13
github.com/nao1215/gorky v0.2.1
github.com/pkg/errors v0.9.1
github.com/shogo82148/pointer v1.3.0
github.com/spf13/cobra v1.8.1
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/sync v0.7.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shogo82148/pointer v1.3.0 h1:LW5V2jUAjFNjS8e7k/PgFoh3EavOSB/vvN85aGue5+I=
github.com/shogo82148/pointer v1.3.0/go.mod h1:agZ5JFpavFPXznbWonIvbG78NDfvDTFppe+7o53up5w=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
Expand Down
7 changes: 5 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/adrg/xdg"
"github.com/nao1215/gup/internal/cmdinfo"
"github.com/nao1215/gup/internal/goutil"
"github.com/shogo82148/pointer"
)

// ConfigFileName is gup command configuration file
Expand All @@ -40,7 +41,8 @@ func ReadConfFile(path string) ([]goutil.Package, error) {
pkgs := []goutil.Package{}
for _, v := range contents {
pkg := goutil.Package{}
ver := goutil.Version{Current: "<from gup.conf>", Latest: ""}
binVer := goutil.Version{Current: "<from gup.conf>", Latest: ""}
goVer := goutil.Version{Current: "<from gup.conf>", Latest: ""}

v = deleteComment(v)
if isBlank(v) {
Expand All @@ -55,7 +57,8 @@ func ReadConfFile(path string) ([]goutil.Package, error) {
equalIdx := strings.Index(v, "=")
pkg.Name = strings.TrimSpace(v[:equalIdx-1])
pkg.ImportPath = strings.TrimSpace(v[equalIdx+1:])
pkg.Version = &ver
pkg.Version = pointer.Ptr(binVer)
pkg.GoVersion = pointer.Ptr(goVer)
pkgs = append(pkgs, pkg)
}

Expand Down
65 changes: 12 additions & 53 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 Expand Up @@ -251,33 +256,10 @@ func ExampleGoPaths_StartDryRunMode() {
}

// ----------------------------------------------------------------------------
// Type: Package
//
// Type: Package
//
// ----------------------------------------------------------------------------

func ExamplePackage_CurrentToLatestStr() {
// Set the paths of the target binary
packages := goutil.GetPackageInformation([]string{"../../cmd/testdata/check_success/gal"})
if len(packages) == 0 {
log.Fatal("example GetPackageInformation failed. The returned package information is nil")
}

// test with the first package found
pkgInfo := packages[0]

wantContain := "Already up-to-date"
got := pkgInfo.CurrentToLatestStr()

if !strings.Contains(got, wantContain) {
log.Fatalf(
"example Package.CurrentToLatestStr failed. \nwant contain: %s\n got: %s",
wantContain, got,
)
}

fmt.Println("Example Package.CurrentToLatestStr: OK")
// Output: Example Package.CurrentToLatestStr: OK
}

func ExamplePackage_SetLatestVer() {
packages := goutil.GetPackageInformation([]string{"../../cmd/testdata/check_success/gal"})
if len(packages) == 0 {
Expand Down Expand Up @@ -308,26 +290,3 @@ func ExamplePackage_SetLatestVer() {
fmt.Println("Example Package.SetLatestVer: OK")
// Output: Example Package.SetLatestVer: OK
}

func ExamplePackage_VersionCheckResultStr() {
packages := goutil.GetPackageInformation([]string{"../../cmd/testdata/check_success/gal"})
if len(packages) == 0 {
log.Fatal("example GetPackageInformation failed. The returned package information is nil")
}

// test with the first package found
pkgInfo := packages[0]

wantContain := "Already up-to-date"
got := pkgInfo.VersionCheckResultStr()

if !strings.Contains(got, wantContain) {
log.Fatalf(
"example Package.VersionCheckResultStr failed. \nwant contain: %s\n got: %s",
wantContain, got,
)
}

fmt.Println("Example Package.VersionCheckResultStr: OK")
// Output: Example Package.VersionCheckResultStr: OK
}
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()))
}
Loading