diff --git a/CHANGELOG.md b/CHANGELOG.md index f0e3aefa7..3192281bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - ``tt instances`` command to print a list of enabled applications. - SSL options for ``tt connect`` command. - An ability to pass arguments to a connect command. +- ``tt binaries`` command. It shows a list of installed binaries and their versions. ### Changed diff --git a/README.rst b/README.rst index 9db7df415..d9e8989a0 100644 --- a/README.rst +++ b/README.rst @@ -462,3 +462,4 @@ Common description. For a detailed description, use ``tt help command`` . * ``cfg dump`` - print tt environment configuration. * ``pack`` - pack an environment into a tarball/RPM/Deb. * ``instances`` - show enabled applications. +* ``binaries`` - show a list of installed binaries and their versions. diff --git a/cli/cmd/binaries.go b/cli/cmd/binaries.go new file mode 100644 index 000000000..cd4b23297 --- /dev/null +++ b/cli/cmd/binaries.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/tarantool/tt/cli/cmdcontext" + "github.com/tarantool/tt/cli/list" + "github.com/tarantool/tt/cli/modules" +) + +// NewBinariesCmd creates binaries command. +func NewBinariesCmd() *cobra.Command { + var binariesCmd = &cobra.Command{ + Use: "binaries", + Short: "Show a list of installed binaries and their versions.", + Run: func(cmd *cobra.Command, args []string) { + err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo, + internalBinariesModule, args) + handleCmdErr(cmd, err) + }, + } + + return binariesCmd +} + +// internalBinariesModule is a default binaries module. +func internalBinariesModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { + return list.ListBinaries(cmdCtx, cliOpts) +} diff --git a/cli/cmd/install.go b/cli/cmd/install.go index 39bdaa7da..4b44a21d2 100644 --- a/cli/cmd/install.go +++ b/cli/cmd/install.go @@ -7,7 +7,7 @@ import ( "github.com/tarantool/tt/cli/cmdcontext" "github.com/tarantool/tt/cli/install" "github.com/tarantool/tt/cli/modules" - "github.com/tarantool/tt/cli/search" + "github.com/tarantool/tt/cli/version" ) var ( @@ -40,7 +40,7 @@ func NewInstallCmd() *cobra.Command { # Install Tarantool 2.10.5 with limit number of simultaneous jobs for make. - $ MAKEFLAGS="-j2" tt install tarantool` + search.VersionCliSeparator + "2.10.5", + $ MAKEFLAGS="-j2" tt install tarantool` + version.CliSeparator + "2.10.5", ValidArgs: []string{"tt", "tarantool", "tarantool-ee"}, } installCmd.Flags().BoolVarP(&force, "force", "f", false, diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 79a0e6bac..b73097a6e 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -149,6 +149,7 @@ func NewCmdRoot() *cobra.Command { NewDaemonCmd(), NewCfgCmd(), NewInstancesCmd(), + NewBinariesCmd(), ) if err := injectCmds(rootCmd); err != nil { panic(err.Error()) diff --git a/cli/cmd/uninstall.go b/cli/cmd/uninstall.go index e81cf0ae9..cd8c13264 100644 --- a/cli/cmd/uninstall.go +++ b/cli/cmd/uninstall.go @@ -7,8 +7,8 @@ import ( "github.com/spf13/cobra" "github.com/tarantool/tt/cli/cmdcontext" "github.com/tarantool/tt/cli/modules" - "github.com/tarantool/tt/cli/search" "github.com/tarantool/tt/cli/uninstall" + "github.com/tarantool/tt/cli/version" ) // NewUninstallCmd creates uninstall command. @@ -44,8 +44,8 @@ func NewUninstallCmd() *cobra.Command { // InternalUninstallModule is a default uninstall module. func InternalUninstallModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { - if !strings.Contains(args[0], search.VersionCliSeparator) { - return fmt.Errorf("incorrect usage.\n e.g program%sversion", search.VersionCliSeparator) + if !strings.Contains(args[0], version.CliSeparator) { + return fmt.Errorf("incorrect usage.\n e.g program%sversion", version.CliSeparator) } err := uninstall.UninstallProgram(args[0], cliOpts.App.BinDir, cliOpts.App.IncludeDir+"/include", cmdCtx) diff --git a/cli/install/install.go b/cli/install/install.go index 8475dffe4..78d59b6c7 100644 --- a/cli/install/install.go +++ b/cli/install/install.go @@ -380,14 +380,14 @@ func copyBuildedTT(binDir, path, version string, installCtx InstallCtx, } // installTt installs selected version of tt. -func installTt(version string, binDir string, installCtx InstallCtx, distfiles string) error { +func installTt(versionStr string, binDir string, installCtx InstallCtx, distfiles string) error { versions, err := getTTVersions(installCtx.Local, distfiles) if err != nil { return err } // Get latest version if it was not specified. - _, ttVersion, _ := strings.Cut(version, search.VersionCliSeparator) + _, ttVersion, _ := strings.Cut(versionStr, version.CliSeparator) if ttVersion == "" { log.Infof("Getting latest tt version..") if len(versions) == 0 { @@ -435,12 +435,12 @@ func installTt(version string, binDir string, installCtx InstallCtx, distfiles s } } - version = "tt" + search.VersionFsSeparator + ttVersion + versionStr = "tt" + version.FsSeparator + ttVersion // Check if that version is already installed. log.Infof("Checking existing...") - if checkExisting(version, binDir) && !installCtx.Reinstall { - log.Infof("%s version of tt already exists, updating symlink...", version) - err := util.CreateSymlink(version, filepath.Join(binDir, "tt"), true) + if checkExisting(versionStr, binDir) && !installCtx.Reinstall { + log.Infof("%s version of tt already exists, updating symlink...", versionStr) + err := util.CreateSymlink(versionStr, filepath.Join(binDir, "tt"), true) log.Infof("Done") return err } @@ -488,14 +488,14 @@ func installTt(version string, binDir string, installCtx InstallCtx, distfiles s // Copy binary. log.Infof("Copying executable...") - err = copyBuildedTT(binDir, path, version, installCtx, logFile) + err = copyBuildedTT(binDir, path, versionStr, installCtx, logFile) if err != nil { printLog(logFile.Name()) return err } // Set symlink. - err = util.CreateSymlink(version, filepath.Join(binDir, "tt"), true) + err = util.CreateSymlink(versionStr, filepath.Join(binDir, "tt"), true) if err != nil { printLog(logFile.Name()) return err @@ -531,7 +531,7 @@ func patchTarantool(srcPath string, tarVersion string, return nil } - ver, err := version.GetVersionDetails(tarVersion) + ver, err := version.Parse(tarVersion) if err != nil { return err } @@ -581,7 +581,7 @@ func buildTarantool(srcPath string, tarVersion string, // This feature is not supported by a backported static build. btFlag := "ON" if tarVersion != "master" { - version, err := version.GetVersionDetails(tarVersion) + version, err := version.Parse(tarVersion) if err != nil { return err } @@ -713,7 +713,7 @@ func installTarantoolInDocker(tntVersion, binDir, incDir string, installCtx Inst tntInstallCommandLine = append(tntInstallCommandLine, "-V") } tntInstallCommandLine = append(tntInstallCommandLine, "install", - "tarantool"+search.VersionCliSeparator+tntVersion, "-f") + "tarantool"+version.CliSeparator+tntVersion, "-f") if installCtx.Reinstall { tntInstallCommandLine = append(tntInstallCommandLine, "--reinstall") } @@ -742,7 +742,7 @@ func installTarantoolInDocker(tntVersion, binDir, incDir string, installCtx Inst } // installTarantool installs selected version of tarantool. -func installTarantool(version string, binDir string, incDir string, +func installTarantool(versionStr string, binDir string, incDir string, installCtx InstallCtx, distfiles string) error { // Check bin and header dirs. if binDir == "" { @@ -758,7 +758,7 @@ func installTarantool(version string, binDir string, incDir string, } // Get latest version if it was not specified. - _, tarVersion, _ := strings.Cut(version, search.VersionCliSeparator) + _, tarVersion, _ := strings.Cut(versionStr, version.CliSeparator) if tarVersion == "" { log.Infof("Getting latest tarantool version..") if len(versions) == 0 { @@ -783,11 +783,11 @@ func installTarantool(version string, binDir string, incDir string, } } - version = "tarantool" + search.VersionFsSeparator + tarVersion + versionStr = "tarantool" + version.FsSeparator + tarVersion // Check if program is already installed. if !installCtx.Reinstall { log.Infof("Checking existing...") - versionExists, err := checkExistingTarantool(version, + versionExists, err := checkExistingTarantool(versionStr, binDir, incDir, installCtx) if err != nil || versionExists { return err @@ -854,15 +854,15 @@ func installTarantool(version string, binDir string, incDir string, } // Copy binary and headers. if installCtx.Reinstall { - if checkExisting(version, binDir) { + if checkExisting(versionStr, binDir) { log.Infof("%s version of tarantool already exists, removing files...", - version) - err = os.RemoveAll(filepath.Join(binDir, version)) + versionStr) + err = os.RemoveAll(filepath.Join(binDir, versionStr)) if err != nil { printLog(logFile.Name()) return err } - err = os.RemoveAll(filepath.Join(incDir, version)) + err = os.RemoveAll(filepath.Join(incDir, versionStr)) } } if err != nil { @@ -872,7 +872,7 @@ func installTarantool(version string, binDir string, incDir string, buildPath := filepath.Join(path, "/static-build/build") binPath := filepath.Join(buildPath, "/tarantool-prefix/bin/tarantool") incPath := filepath.Join(buildPath, "/tarantool-prefix/include/tarantool") + "/" - err = copyBuildedTarantool(binPath, incPath, binDir, incDir, version, installCtx, + err = copyBuildedTarantool(binPath, incPath, binDir, incDir, versionStr, installCtx, logFile) if err != nil { printLog(logFile.Name()) @@ -880,12 +880,12 @@ func installTarantool(version string, binDir string, incDir string, } // Set symlinks. log.Infof("Changing symlinks...") - err = util.CreateSymlink(version, filepath.Join(binDir, "tarantool"), true) + err = util.CreateSymlink(versionStr, filepath.Join(binDir, "tarantool"), true) if err != nil { printLog(logFile.Name()) return err } - err = util.CreateSymlink(version, filepath.Join(incDir, "tarantool"), true) + err = util.CreateSymlink(versionStr, filepath.Join(incDir, "tarantool"), true) if err != nil { printLog(logFile.Name()) return err @@ -898,7 +898,7 @@ func installTarantool(version string, binDir string, incDir string, } // installTarantoolEE installs selected version of tarantool-ee. -func installTarantoolEE(version string, binDir string, includeDir string, installCtx InstallCtx, +func installTarantoolEE(versionStr string, binDir string, includeDir string, installCtx InstallCtx, distfiles string, cliOpts *config.CliOpts) error { var err error @@ -916,7 +916,8 @@ func installTarantoolEE(version string, binDir string, includeDir string, instal } } - _, tarVersion, _ := strings.Cut(version, search.VersionCliSeparator) + // Get latest version if it was not specified. + _, tarVersion, _ := strings.Cut(versionStr, version.CliSeparator) if tarVersion == "" { log.Infof("Getting latest tarantool-ee version..") } @@ -955,10 +956,10 @@ func installTarantoolEE(version string, binDir string, includeDir string, instal bundleName := ver.Version.Tarball bundleSource := ver.Prefix - version = "tarantool-ee" + search.VersionFsSeparator + tarVersion + versionStr = "tarantool-ee" + version.FsSeparator + tarVersion if !installCtx.Reinstall { log.Infof("Checking existing...") - versionExists, err := checkExistingTarantool(version, + versionExists, err := checkExistingTarantool(versionStr, binDir, includeDir, installCtx) if err != nil || versionExists { return err @@ -1010,15 +1011,15 @@ func installTarantoolEE(version string, binDir string, includeDir string, instal // Copy binary and headers. if installCtx.Reinstall { - if checkExisting(version, binDir) { + if checkExisting(versionStr, binDir) { log.Infof("%s version of tarantool-ee already exists, removing files...", - version) - err = os.RemoveAll(filepath.Join(binDir, version)) + versionStr) + err = os.RemoveAll(filepath.Join(binDir, versionStr)) if err != nil { printLog(logFile.Name()) return err } - err = os.RemoveAll(filepath.Join(includeDir, version)) + err = os.RemoveAll(filepath.Join(includeDir, versionStr)) } } if err != nil { @@ -1027,7 +1028,7 @@ func installTarantoolEE(version string, binDir string, includeDir string, instal } binPath := filepath.Join(path, "/tarantool-enterprise/tarantool") incPath := filepath.Join(path, "/tarantool-enterprise/include/tarantool") + "/" - err = copyBuildedTarantool(binPath, incPath, binDir, includeDir, version, installCtx, + err = copyBuildedTarantool(binPath, incPath, binDir, includeDir, versionStr, installCtx, logFile) if err != nil { printLog(logFile.Name()) @@ -1036,11 +1037,11 @@ func installTarantoolEE(version string, binDir string, includeDir string, instal // Set symlinks. log.Infof("Changing symlinks...") - err = util.CreateSymlink(version, filepath.Join(binDir, "tarantool"), true) + err = util.CreateSymlink(versionStr, filepath.Join(binDir, "tarantool"), true) if err != nil { return err } - err = util.CreateSymlink(version, filepath.Join(includeDir, "tarantool"), true) + err = util.CreateSymlink(versionStr, filepath.Join(includeDir, "tarantool"), true) if err != nil { printLog(logFile.Name()) return err @@ -1080,7 +1081,7 @@ func FillCtx(cmdCtx *cmdcontext.CmdCtx, installCtx *InstallCtx, args []string) e } re := regexp.MustCompile( - "^(?Ptt|tarantool|tarantool-ee)(?:" + search.VersionCliSeparator + ".*)?$", + "^(?Ptt|tarantool|tarantool-ee)(?:" + version.CliSeparator + ".*)?$", ) matches := util.FindNamedMatches(re, args[0]) if len(matches) == 0 { diff --git a/cli/list/list.go b/cli/list/list.go index e756450a2..da41dc4e6 100644 --- a/cli/list/list.go +++ b/cli/list/list.go @@ -2,7 +2,11 @@ package list import ( "fmt" + "io/fs" + "math" "os" + "path/filepath" + "sort" "strings" "github.com/apex/log" @@ -11,6 +15,7 @@ import ( "github.com/tarantool/tt/cli/config" "github.com/tarantool/tt/cli/running" "github.com/tarantool/tt/cli/util" + "github.com/tarantool/tt/cli/version" ) // ListInstances shows enabled applications. @@ -54,3 +59,80 @@ func ListInstances(cmdCtx *cmdcontext.CmdCtx, cliOpts *config.CliOpts) error { return nil } + +// printBinaries outputs installed versions of the program. +func printVersion(versionString string) { + if strings.HasSuffix(versionString, "[active]") { + fmt.Printf(" %s\n", util.Bold(color.GreenString(versionString))) + } else { + fmt.Printf(" %s\n", color.YellowString(versionString)) + } +} + +// parseBinaries seeks through fileList returning array of found versions of program. +func parseBinaries(fileList []fs.DirEntry, programName string, + binDir string) ([]version.Version, error) { + var binaryVersions []version.Version + binActive, err := util.ResolveSymlink(filepath.Join(binDir, programName)) + if err != nil && !os.IsNotExist(err) { + return binaryVersions, err + } + binActive = filepath.Base(binActive) + + versionPrefix := programName + version.FsSeparator + for _, f := range fileList { + if strings.HasPrefix(f.Name(), versionPrefix) { + versionStr := strings.TrimPrefix(strings.TrimPrefix(f.Name(), versionPrefix), "v") + if versionStr == "master" { + binaryVersions = append(binaryVersions, version.Version{ + Major: math.MaxUint, // Small hack to make master the newest version. + Str: "master", + }) + } else { + ver, err := version.Parse(versionStr) + if err != nil { + return binaryVersions, err + } + if binActive == f.Name() { + ver.Str += " [active]" + } + binaryVersions = append(binaryVersions, ver) + } + } + } + + return binaryVersions, nil +} + +// ListBinaries outputs installed versions of programs from bin_dir. +func ListBinaries(cmdCtx *cmdcontext.CmdCtx, cliOpts *config.CliOpts) (err error) { + binDir := cliOpts.App.BinDir + binDirFilesList, err := os.ReadDir(binDir) + if err != nil { + return fmt.Errorf("error reading directory %q: %s", binDir, err) + } + + if len(binDirFilesList) == 0 { + return fmt.Errorf("there are no installed binaries") + } + + programs := [...]string{"tt", "tarantool"} + fmt.Println("List of installed binaries:") + for _, programName := range programs { + binaryVersions, err := parseBinaries(binDirFilesList, programName, binDir) + if err != nil { + return err + } + + if len(binaryVersions) > 0 { + sort.Stable(sort.Reverse(version.VersionSlice(binaryVersions))) + log.Infof(programName + ":") + for _, binVersion := range binaryVersions { + printVersion(binVersion.Str) + } + } + + } + + return err +} diff --git a/cli/list/list_test.go b/cli/list/list_test.go new file mode 100644 index 000000000..bed787b21 --- /dev/null +++ b/cli/list/list_test.go @@ -0,0 +1,24 @@ +package list + +import ( + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tarantool/tt/cli/version" +) + +func TestParseBinaries(t *testing.T) { + fileList, err := os.ReadDir("./testdata/bin") + require.NoError(t, err) + versions, err := parseBinaries(fileList, "tarantool", "./testdata/bin") + require.NoError(t, err) + sort.Stable(sort.Reverse(version.VersionSlice(versions))) + expectedSortedVersions := []string{"master", "2.10.5", "2.8.6 [active]", "1.10.0"} + require.Equal(t, len(expectedSortedVersions), len(versions)) + for i := 0; i < len(expectedSortedVersions); i++ { + assert.Equal(t, expectedSortedVersions[i], versions[i].Str) + } +} diff --git a/cli/list/testdata/bin/tarantool b/cli/list/testdata/bin/tarantool new file mode 120000 index 000000000..d1a032223 --- /dev/null +++ b/cli/list/testdata/bin/tarantool @@ -0,0 +1 @@ +tarantool_v2.8.6 \ No newline at end of file diff --git a/cli/list/testdata/bin/tarantool_1.10.0 b/cli/list/testdata/bin/tarantool_1.10.0 new file mode 100644 index 000000000..e69de29bb diff --git a/cli/list/testdata/bin/tarantool_2.10.5 b/cli/list/testdata/bin/tarantool_2.10.5 new file mode 100644 index 000000000..e69de29bb diff --git a/cli/list/testdata/bin/tarantool_master b/cli/list/testdata/bin/tarantool_master new file mode 100644 index 000000000..e69de29bb diff --git a/cli/list/testdata/bin/tarantool_v2.8.6 b/cli/list/testdata/bin/tarantool_v2.8.6 new file mode 100644 index 000000000..e69de29bb diff --git a/cli/pack/deb.go b/cli/pack/deb.go index 180e17c5f..62790f80c 100644 --- a/cli/pack/deb.go +++ b/cli/pack/deb.go @@ -192,7 +192,7 @@ func getTntTTAsDeps(cmdCtx *cmdcontext.CmdCtx) (PackDependencies, error) { if err != nil { return nil, err } - tntVerParsed, err := version.GetVersionDetails(tntVerRaw) + tntVerParsed, err := version.Parse(tntVerRaw) if err != nil { return nil, err } diff --git a/cli/pack/rpm_header.go b/cli/pack/rpm_header.go index f76d7a2c1..28e39c73c 100644 --- a/cli/pack/rpm_header.go +++ b/cli/pack/rpm_header.go @@ -124,7 +124,7 @@ func genRpmHeader(relPaths []string, cpioPath, compresedCpioPath, packageFilesDi versionString := getVersion(packCtx, opts, defaultVersion) - ver, err := version.GetVersionDetails(versionString) + ver, err := version.Parse(versionString) if err != nil { return nil, err } diff --git a/cli/search/bundle.go b/cli/search/bundle.go index 6c420d899..b41ab2327 100644 --- a/cli/search/bundle.go +++ b/cli/search/bundle.go @@ -1,6 +1,7 @@ package search import ( + "github.com/tarantool/tt/cli/util" "github.com/tarantool/tt/cli/version" ) @@ -30,5 +31,34 @@ func (bundles BundleInfoSlice) Len() int { func (bundles BundleInfoSlice) Less(i, j int) bool { verLeft := bundles[i].Version verRight := bundles[j].Version - return version.Less(verLeft, verRight) + return Less(verLeft, verRight) +} + +// Less is a common function-comparator using for the Version type. +func Less(verLeft, verRight version.Version) bool { + left := []uint64{verLeft.Major, verLeft.Minor, + verLeft.Patch, uint64(verLeft.Release.Type), + verLeft.Release.Num, verLeft.Additional, verLeft.Revision} + right := []uint64{verRight.Major, verRight.Minor, + verRight.Patch, uint64(verRight.Release.Type), + verRight.Release.Num, verRight.Additional, verRight.Revision} + + largestLen := util.Max(len(left), len(right)) + + for i := 0; i < largestLen; i++ { + var valLeft, valRight uint64 = 0, 0 + if i < len(left) { + valLeft = left[i] + } + + if i < len(right) { + valRight = right[i] + } + + if valLeft != valRight { + return valLeft < valRight + } + } + + return false } diff --git a/cli/search/search.go b/cli/search/search.go index 490ef3aa3..a30252d9f 100644 --- a/cli/search/search.go +++ b/cli/search/search.go @@ -31,13 +31,6 @@ const ( GitRepoTT = "https://github.com/tarantool/tt.git" ) -const ( - // VersionCliSeparator is used in commands to specify version. E.g: program=version. - VersionCliSeparator = "=" - // VersionFsSeparator is used in file names to specify version. E.g: program_version. - VersionFsSeparator = "_" -) - // isMasked function checks that the given version of tarantool is masked. func isMasked(version version.Version) bool { // Mask all versions below 1.10: deprecated. @@ -96,7 +89,7 @@ func GetVersionsFromGitRemote(repo string) ([]version.Version, error) { slashIdx += 1 } ver := line[slashIdx:] - version, err := version.GetVersionDetails(ver) + version, err := version.Parse(ver) if err != nil { continue } @@ -106,7 +99,7 @@ func GetVersionsFromGitRemote(repo string) ([]version.Version, error) { versions = append(versions, version) } - version.SortVersions(versions) + sort.Stable(version.VersionSlice(versions)) return versions, nil } @@ -132,7 +125,7 @@ func GetVersionsFromGitLocal(repo string) ([]version.Version, error) { } for _, line := range lines { - version, err := version.GetVersionDetails(line) + version, err := version.Parse(line) if err != nil { continue } @@ -142,7 +135,7 @@ func GetVersionsFromGitLocal(repo string) ([]version.Version, error) { versions = append(versions, version) } - version.SortVersions(versions) + sort.Stable(version.VersionSlice(versions)) return versions, nil } @@ -150,8 +143,9 @@ func GetVersionsFromGitLocal(repo string) ([]version.Version, error) { // printVersion prints the version and labels: // * if the package is installed: [installed] // * if the package is installed and in use: [active] -func printVersion(bindir string, program string, version string) { - if _, err := os.Stat(filepath.Join(bindir, program+VersionFsSeparator+version)); err == nil { +func printVersion(bindir string, program string, versionStr string) { + if _, err := os.Stat(filepath.Join(bindir, + program+version.FsSeparator+versionStr)); err == nil { target := "" if program == "tarantool-ee" { target, _ = util.ResolveSymlink(filepath.Join(bindir, "tarantool")) @@ -159,13 +153,13 @@ func printVersion(bindir string, program string, version string) { target, _ = util.ResolveSymlink(filepath.Join(bindir, program)) } - if path.Base(target) == program+VersionFsSeparator+version { - fmt.Printf("%s [active]\n", version) + if path.Base(target) == program+version.FsSeparator+versionStr { + fmt.Printf("%s [active]\n", versionStr) } else { - fmt.Printf("%s [installed]\n", version) + fmt.Printf("%s [installed]\n", versionStr) } } else { - fmt.Println(version) + fmt.Println(versionStr) } } @@ -383,7 +377,7 @@ func getBundles(rawBundleInfoList []string) (BundleInfoSlice, error) { } for _, bundleInfo := range parsedBundleInfo { - ver, err := version.GetVersionDetails(bundleInfo["version"]) + ver, err := version.Parse(bundleInfo["version"]) if err != nil { return nil, err } @@ -415,7 +409,7 @@ func FetchBundlesInfoLocal(files []string) ([]BundleInfo, error) { continue } - version, err := version.GetVersionDetails(parsedData[4]) + version, err := version.Parse(parsedData[4]) if err != nil { return nil, err } diff --git a/cli/uninstall/uninstall.go b/cli/uninstall/uninstall.go index 9a11eb2a4..dff3e5077 100644 --- a/cli/uninstall/uninstall.go +++ b/cli/uninstall/uninstall.go @@ -10,8 +10,8 @@ import ( "github.com/apex/log" "github.com/tarantool/tt/cli/cmdcontext" "github.com/tarantool/tt/cli/config" - "github.com/tarantool/tt/cli/search" "github.com/tarantool/tt/cli/util" + "github.com/tarantool/tt/cli/version" ) // remove removes binary/directory and symlinks from directory. @@ -20,7 +20,7 @@ func remove(program string, directory string, cmdCtx *cmdcontext.CmdCtx) error { var err error re := regexp.MustCompile( - "^(?Ptt|tarantool|tarantool-ee)(?:" + search.VersionCliSeparator + "(?P.*))?$", + "^(?Ptt|tarantool|tarantool-ee)(?:" + version.CliSeparator + "(?P.*))?$", ) matches := util.FindNamedMatches(re, program) @@ -40,7 +40,7 @@ func remove(program string, directory string, cmdCtx *cmdcontext.CmdCtx) error { return fmt.Errorf("there was some problem with %s directory", directory) } - fileName := matches["prog"] + search.VersionFsSeparator + matches["ver"] + fileName := matches["prog"] + version.FsSeparator + matches["ver"] path := filepath.Join(directory, fileName) if _, err := os.Stat(path); os.IsNotExist(err) { @@ -89,7 +89,7 @@ func GetList(cliOpts *config.CliOpts) []string { list := []string{} re := regexp.MustCompile( "^(?P(?:tarantool)|(?:tarantool-ee)|(?:tt))" + - search.VersionFsSeparator + + version.FsSeparator + "(?P.*)$", ) @@ -105,7 +105,7 @@ func GetList(cliOpts *config.CliOpts) []string { for _, file := range installedPrograms { matches := util.FindNamedMatches(re, file.Name()) if len(matches) != 0 { - list = append(list, matches["prog"]+search.VersionCliSeparator+matches["ver"]) + list = append(list, matches["prog"]+version.CliSeparator+matches["ver"]) } } diff --git a/cli/uninstall/uninstall_test.go b/cli/uninstall/uninstall_test.go index d4eff4869..79b35f26a 100644 --- a/cli/uninstall/uninstall_test.go +++ b/cli/uninstall/uninstall_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tarantool/tt/cli/configure" - "github.com/tarantool/tt/cli/search" + "github.com/tarantool/tt/cli/version" ) const testDirName = "uninstall-test-dir" @@ -31,9 +31,9 @@ func TestGetList(t *testing.T) { require.NoError(t, err) files := []string{ - "tt" + search.VersionFsSeparator + "1.2.3", - "tarantool" + search.VersionFsSeparator + "1.2.10", - "tarantool-ee" + search.VersionFsSeparator + "master", + "tt" + version.FsSeparator + "1.2.3", + "tarantool" + version.FsSeparator + "1.2.10", + "tarantool-ee" + version.FsSeparator + "master", } for _, file := range files { f, err := os.Create(filepath.Join(binDir, file)) @@ -42,9 +42,9 @@ func TestGetList(t *testing.T) { } expected := []string{ - "tt" + search.VersionCliSeparator + "1.2.3", - "tarantool" + search.VersionCliSeparator + "1.2.10", - "tarantool-ee" + search.VersionCliSeparator + "master", + "tt" + version.CliSeparator + "1.2.3", + "tarantool" + version.CliSeparator + "1.2.10", + "tarantool-ee" + version.CliSeparator + "master", } cliOpts, _, err := configure.GetCliOpts(cfgPath) diff --git a/cli/version/version_tools.go b/cli/version/version_tools.go index 0f63db586..5645bd4f6 100644 --- a/cli/version/version_tools.go +++ b/cli/version/version_tools.go @@ -3,7 +3,6 @@ package version import ( "fmt" "regexp" - "sort" "github.com/tarantool/tt/cli/util" ) @@ -16,6 +15,11 @@ const ( TypeBeta TypeRC TypeRelease + + // CliSeparator is used in commands to specify version. E.g: program=version. + CliSeparator = "=" + // FsSeparator is used in file names to specify version. E.g: program_version. + FsSeparator = "_" ) type Release struct { @@ -36,8 +40,8 @@ type Version struct { GCSuffix string // GC suffix. } -// GetVersionDetails collects information about all version details. -func GetVersionDetails(verStr string) (Version, error) { +// Parse parses a version string and return the version value it represents. +func Parse(verStr string) (Version, error) { version := Version{} var err error @@ -55,7 +59,7 @@ func GetVersionDetails(verStr string) (Version, error) { matches := util.FindNamedMatches(re, verStr) if len(matches) == 0 { - return version, fmt.Errorf("failed to parse version: format is not valid") + return version, fmt.Errorf("failed to parse version %q: format is not valid", verStr) } if matches["gc"] != "" { @@ -114,17 +118,19 @@ func GetVersionDetails(verStr string) (Version, error) { return version, nil } -// SortVersions sorts versions from oldest to newest. -func SortVersions(versions []Version) { - sort.SliceStable(versions, func(i, j int) bool { - verLeft := versions[i] - verRight := versions[j] - return Less(verLeft, verRight) - }) +// VersionSlice attaches the methods of sort.Interface to []Version, sorting from oldest to newest. +type VersionSlice []Version + +// sort.Interface Len implementation +func (v VersionSlice) Len() int { + return len(v) } -// Less is a common function-comparator using for the Version type. -func Less(verLeft, verRight Version) bool { +// sort.Interface Less implementation, sorts from oldest to newest +func (v VersionSlice) Less(i, j int) bool { + verLeft := v[i] + verRight := v[j] + left := []uint64{verLeft.Major, verLeft.Minor, verLeft.Patch, uint64(verLeft.Release.Type), verLeft.Release.Num, verLeft.Additional, verLeft.Revision} @@ -151,3 +157,8 @@ func Less(verLeft, verRight Version) bool { return false } + +// sort.Interface Swap implementation +func (v VersionSlice) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} diff --git a/cli/version/version_tools_test.go b/cli/version/version_tools_test.go index dc8e009e4..6b93d8d74 100644 --- a/cli/version/version_tools_test.go +++ b/cli/version/version_tools_test.go @@ -116,16 +116,16 @@ func TestParseVersion(t *testing.T) { testCases["2.8"] = returnValueParseVersion{ Version{}, - fmt.Errorf("failed to parse version: format is not valid"), + fmt.Errorf("failed to parse version \"2.8\": format is not valid"), } testCases["42"] = returnValueParseVersion{ Version{}, - fmt.Errorf("failed to parse version: format is not valid"), + fmt.Errorf("failed to parse version \"42\": format is not valid"), } for input, output := range testCases { - version, err := GetVersionDetails(input) + version, err := Parse(input) if output.err == nil { assert.Nil(err) diff --git a/test/integration/binaries/bin/tarantool b/test/integration/binaries/bin/tarantool new file mode 120000 index 000000000..9b336d7e3 --- /dev/null +++ b/test/integration/binaries/bin/tarantool @@ -0,0 +1 @@ +tarantool_2.8.1 \ No newline at end of file diff --git a/test/integration/binaries/bin/tarantool_2.10.3 b/test/integration/binaries/bin/tarantool_2.10.3 new file mode 100755 index 000000000..e69de29bb diff --git a/test/integration/binaries/bin/tarantool_2.8.1 b/test/integration/binaries/bin/tarantool_2.8.1 new file mode 100755 index 000000000..e69de29bb diff --git a/test/integration/binaries/bin/tt_v0.1.0 b/test/integration/binaries/bin/tt_v0.1.0 new file mode 100755 index 000000000..e69de29bb diff --git a/test/integration/binaries/test_binaries.py b/test/integration/binaries/test_binaries.py new file mode 100644 index 000000000..5ee3dc021 --- /dev/null +++ b/test/integration/binaries/test_binaries.py @@ -0,0 +1,80 @@ +import os +import shutil +import subprocess + +from utils import config_name + + +def test_binaries(tt_cmd, tmpdir): + # Copy the test bin_dir to the "run" directory. + test_app_path = os.path.join(os.path.dirname(__file__), "bin") + shutil.copytree(test_app_path, tmpdir + "/bin", True) + + configPath = os.path.join(tmpdir, config_name) + # Create test config + with open(configPath, 'w') as f: + f.write('tt:\n app:\n bin_dir: "./bin"\n inc_dir:\n') + + # Print binaries + binaries_cmd = [tt_cmd, "--cfg", configPath, "binaries"] + binaries_process = subprocess.Popen( + binaries_cmd, + cwd=tmpdir, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + text=True + ) + + binaries_process.wait() + output = binaries_process.stdout.read() + + assert "tt" in output + assert "0.1.0" in output + assert "tarantool" in output + assert "2.10.3" in output + assert "2.8.1 [active]" in output + + +def test_binaries_no_directory(tt_cmd, tmpdir): + configPath = os.path.join(tmpdir, config_name) + # Create test config + with open(configPath, 'w') as f: + f.write('tt:\n app:\n bin_dir: "./bin"\n inc_dir:\n') + + # Print binaries + binaries_cmd = [tt_cmd, "--cfg", configPath, "binaries"] + binaries_process = subprocess.Popen( + binaries_cmd, + cwd=tmpdir, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + text=True + ) + + binaries_process.wait() + output = binaries_process.stdout.read() + + assert "error reading directory" in output + + +def test_binaries_empty_directory(tt_cmd, tmpdir): + configPath = os.path.join(tmpdir, config_name) + os.mkdir(tmpdir+"/bin") + # Create test config + with open(configPath, 'w') as f: + f.write('tt:\n app:\n bin_dir: "./bin"\n inc_dir:\n') + + # Print binaries + binaries_cmd = [tt_cmd, "--cfg", configPath, "binaries"] + binaries_process = subprocess.Popen( + binaries_cmd, + cwd=tmpdir, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + text=True + ) + + binaries_process.wait() + output = binaries_process.stdout.read() + + assert "there are no installed binaries" in output