diff --git a/cmd/link.go b/cmd/link.go index 9551321016..f54b462433 100644 --- a/cmd/link.go +++ b/cmd/link.go @@ -14,10 +14,6 @@ package cmd import ( - "fmt" - "os" - "path/filepath" - "github.com/pingcap/tiup/pkg/environment" "github.com/spf13/cobra" ) @@ -25,9 +21,8 @@ import ( func newLinkCmd() *cobra.Command { cmd := &cobra.Command{ Use: "link [:version]", - Short: "Link component binary to $PATH", - Long: `[experimental feature] -Link component binary to $PATH`, + Short: "Link component binary to $TIUP_HOME/bin/", + Long: `[experimental] Link component binary to $TIUP_HOME/bin/`, RunE: func(cmd *cobra.Command, args []string) error { teleCommand = cmd.CommandPath() env := environment.GlobalEnv() @@ -35,18 +30,7 @@ Link component binary to $PATH`, return cmd.Help() } component, version := environment.ParseCompVersion(args[0]) - if version == "" { - var err error - version, err = env.SelectInstalledVersion(component, version) - if err != nil { - return err - } - } - binPath, _ := env.BinaryPath(component, version) - target := filepath.Join(env.LocalPath("bin"), filepath.Base(binPath)) - fmt.Printf("package %s provides these executables: %s\n", component, filepath.Base(binPath)) - _ = os.Remove(target) - return os.Symlink(binPath, target) + return env.Link(component, version) }, } return cmd diff --git a/cmd/unlink.go b/cmd/unlink.go index 0c9ff26397..d31c74b4cb 100644 --- a/cmd/unlink.go +++ b/cmd/unlink.go @@ -18,23 +18,34 @@ import ( "path/filepath" "github.com/pingcap/tiup/pkg/environment" + "github.com/pingcap/tiup/pkg/tui" "github.com/spf13/cobra" ) func newUnlinkCmd() *cobra.Command { cmd := &cobra.Command{ Use: "unlink ", - Short: "Unlink component binary to $PATH", - Long: `[experimental feature] -Unlink component binary in $PATH`, + Short: "Unlink component binary to $TIUP_HOME/bin/", + Long: `[experimental] Unlink component binary in $TIUP_HOME/bin/`, RunE: func(cmd *cobra.Command, args []string) error { teleCommand = cmd.CommandPath() env := environment.GlobalEnv() if len(args) != 1 { return cmd.Help() } - component, _ := environment.ParseCompVersion(args[0]) - target := filepath.Join(env.LocalPath("bin"), component) + component, version := environment.ParseCompVersion(args[0]) + version, err := env.SelectInstalledVersion(component, version) + if err != nil { + return err + } + binPath, err := env.BinaryPath(component, version) + if err != nil { + return err + } + target := env.LocalPath("bin", filepath.Base(binPath)) + if err := tui.PromptForConfirmOrAbortError("%s will be removed.\n Do you want to continue? [y/N]:", target); err != nil { + return err + } return os.Remove(target) }, } diff --git a/pkg/environment/env.go b/pkg/environment/env.go index 14814669f8..7c9d86bd29 100644 --- a/pkg/environment/env.go +++ b/pkg/environment/env.go @@ -30,11 +30,6 @@ import ( "golang.org/x/mod/semver" ) -// Name of components -const ( - tiupName = "tiup" -) - var ( // ErrInstallFirst indicates that a component/version is not installed ErrInstallFirst = errors.New("component not installed") @@ -209,6 +204,9 @@ func (env *Environment) SelectInstalledVersion(component string, ver utils.Versi }) errInstallFirst := errors.Annotatef(ErrInstallFirst, "use `tiup install %s` to install component `%s` first", component, component) + if !ver.IsEmpty() { + errInstallFirst = errors.Annotatef(ErrInstallFirst, "use `tiup install %s:%s` to install specified version", component, ver.String()) + } if ver.IsEmpty() || string(ver) == utils.NightlyVersionAlias { var selected utils.Version @@ -287,6 +285,45 @@ func (env *Environment) BinaryPath(component string, ver utils.Version) (string, return env.v1Repo.BinaryPath(installPath, component, ver.String()) } +// Link add soft link to $TIUP_HOME/bin/ +func (env *Environment) Link(component string, version utils.Version) error { + version, err := env.SelectInstalledVersion(component, version) + if err != nil { + return err + } + binPath, err := env.BinaryPath(component, version) + if err != nil { + return err + } + + target := env.LocalPath("bin", filepath.Base(binPath)) + backup := target + ".old" + exist := true + _, err = os.Stat(target) + if err != nil { + if !os.IsNotExist(err) { + return err + } + exist = false + } + if exist { + if err := os.Rename(target, backup); err != nil { + fmt.Printf("Backup of `%s` to `%s` failed.\n", target, backup) + return err + } + } + + fmt.Printf("package %s provides these executables: %s\n", component, filepath.Base(binPath)) + + err = os.Symlink(binPath, target) + if err != nil { + defer func() { _ = os.Rename(backup, target) }() + } else { + defer func() { _ = os.Remove(backup) }() + } + return err +} + // ParseCompVersion parses component part from [:version] specification func ParseCompVersion(spec string) (string, utils.Version) { if strings.Contains(spec, ":") {