diff --git a/cmd/link.go b/cmd/link.go new file mode 100644 index 0000000000..f54b462433 --- /dev/null +++ b/cmd/link.go @@ -0,0 +1,37 @@ +// Copyright 2021 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "github.com/pingcap/tiup/pkg/environment" + "github.com/spf13/cobra" +) + +func newLinkCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "link [:version]", + 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() + if len(args) != 1 { + return cmd.Help() + } + component, version := environment.ParseCompVersion(args[0]) + return env.Link(component, version) + }, + } + return cmd +} diff --git a/cmd/root.go b/cmd/root.go index abd7edfd52..173c645c31 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -225,6 +225,8 @@ the latest stable version will be downloaded from the repository.`, newTelemetryCmd(), newEnvCmd(), newHistoryCmd(), + newLinkCmd(), + newUnlinkCmd(), ) } diff --git a/cmd/unlink.go b/cmd/unlink.go new file mode 100644 index 0000000000..d31c74b4cb --- /dev/null +++ b/cmd/unlink.go @@ -0,0 +1,53 @@ +// Copyright 2021 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "os" + "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 $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, 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) + }, + } + return cmd +} diff --git a/pkg/environment/env.go b/pkg/environment/env.go index 4bb55dd368..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") @@ -143,9 +138,6 @@ func (env *Environment) UpdateComponents(specs []string, nightly, force bool) er var v1specs []repository.ComponentSpec for _, spec := range specs { component, v := ParseCompVersion(spec) - if component == tiupName { - continue - } if v == "" && nightly { v = utils.NightlyVersionAlias } @@ -212,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 @@ -290,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, ":") {