mirror of
https://github.com/asdf-vm/asdf.git
synced 2024-12-23 20:05:09 -07:00
feat(golang-rewrite): implement asdf env command
* Create `CallbackPath` method on `Plugin` struct * Correct behavior of `asdf shimversions` command * Update `shims.FindExecutable` function to return plugin * Create `repotest.WritePluginCallback` function * Create `execenv` package for invoking `exec-env` plugin callback * Make `MapToSlice` a public function * Return resolved version from `shims.FindExecutable` function * Create `shims.ExecutablePaths` function * Enable `shim_env_command.bats` tests * Implement `asdf env` command
This commit is contained in:
parent
0e43521ea7
commit
26a3815948
151
cmd/cmd.go
151
cmd/cmd.go
@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
osexec "os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@ -14,9 +15,12 @@ import (
|
|||||||
|
|
||||||
"asdf/internal/config"
|
"asdf/internal/config"
|
||||||
"asdf/internal/exec"
|
"asdf/internal/exec"
|
||||||
|
"asdf/internal/execenv"
|
||||||
|
"asdf/internal/execute"
|
||||||
"asdf/internal/help"
|
"asdf/internal/help"
|
||||||
"asdf/internal/info"
|
"asdf/internal/info"
|
||||||
"asdf/internal/installs"
|
"asdf/internal/installs"
|
||||||
|
"asdf/internal/paths"
|
||||||
"asdf/internal/plugins"
|
"asdf/internal/plugins"
|
||||||
"asdf/internal/resolve"
|
"asdf/internal/resolve"
|
||||||
"asdf/internal/shims"
|
"asdf/internal/shims"
|
||||||
@ -59,6 +63,15 @@ func Execute(version string) {
|
|||||||
return currentCommand(logger, tool)
|
return currentCommand(logger, tool)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "env",
|
||||||
|
Action: func(cCtx *cli.Context) error {
|
||||||
|
shimmedCommand := cCtx.Args().Get(0)
|
||||||
|
args := cCtx.Args().Slice()
|
||||||
|
|
||||||
|
return envCommand(logger, shimmedCommand, args)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "exec",
|
Name: "exec",
|
||||||
Action: func(cCtx *cli.Context) error {
|
Action: func(cCtx *cli.Context) error {
|
||||||
@ -335,6 +348,83 @@ func formatVersions(versions []string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func envCommand(logger *log.Logger, shimmedCommand string, args []string) error {
|
||||||
|
command := "env"
|
||||||
|
|
||||||
|
if shimmedCommand == "" {
|
||||||
|
logger.Printf("usage: asdf env <command>")
|
||||||
|
return fmt.Errorf("usage: asdf env <command>")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) >= 2 {
|
||||||
|
command = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
realArgs := []string{}
|
||||||
|
if len(args) > 2 {
|
||||||
|
realArgs = args[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("error loading config: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, plugin, version, err := getExecutable(logger, conf, shimmedCommand)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedVersion := toolversions.Parse(version)
|
||||||
|
execPaths, err := shims.ExecutablePaths(conf, plugin, parsedVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
callbackEnv := map[string]string{
|
||||||
|
"ASDF_INSTALL_TYPE": parsedVersion.Type,
|
||||||
|
"ASDF_INSTALL_VERSION": parsedVersion.Value,
|
||||||
|
"ASDF_INSTALL_PATH": installs.InstallPath(conf, plugin, parsedVersion),
|
||||||
|
"PATH": setPath(conf, execPaths),
|
||||||
|
}
|
||||||
|
|
||||||
|
var env map[string]string
|
||||||
|
var fname string
|
||||||
|
|
||||||
|
if parsedVersion.Type == "system" {
|
||||||
|
env = execute.SliceToMap(os.Environ())
|
||||||
|
newPath := paths.RemoveFromPath(env["PATH"], shims.Directory(conf))
|
||||||
|
env["PATH"] = newPath
|
||||||
|
var found bool
|
||||||
|
fname, found = shims.FindSystemExecutable(conf, command)
|
||||||
|
if !found {
|
||||||
|
fmt.Println("not found")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
env, err = execenv.Generate(plugin, callbackEnv)
|
||||||
|
if _, ok := err.(plugins.NoCallbackError); !ok && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fname, err = osexec.LookPath(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = exec.Exec(fname, realArgs, execute.MapToSlice(env))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err %#+v\n", err.Error())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPath(conf config.Config, pathes []string) string {
|
||||||
|
currentPath := os.Getenv("PATH")
|
||||||
|
return strings.Join(pathes, ":") + ":" + paths.RemoveFromPath(currentPath, shims.Directory(conf))
|
||||||
|
}
|
||||||
|
|
||||||
func execCommand(logger *log.Logger, command string, args []string) error {
|
func execCommand(logger *log.Logger, command string, args []string) error {
|
||||||
if command == "" {
|
if command == "" {
|
||||||
logger.Printf("usage: asdf exec <command>")
|
logger.Printf("usage: asdf exec <command>")
|
||||||
@ -347,16 +437,51 @@ func execCommand(logger *log.Logger, command string, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDir, err := os.Getwd()
|
executable, plugin, version, err := getExecutable(logger, conf, command)
|
||||||
|
fmt.Printf("version %#+v\n", version)
|
||||||
|
fmt.Println("here")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("unable to get current directory: %s", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
executable, found, err := shims.FindExecutable(conf, command, currentDir)
|
if len(args) > 1 {
|
||||||
|
args = args[1:]
|
||||||
|
} else {
|
||||||
|
args = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedVersion := toolversions.Parse(version)
|
||||||
|
fmt.Printf("parsedVersion %#+v\n", parsedVersion)
|
||||||
|
paths, err := shims.ExecutablePaths(conf, plugin, parsedVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
callbackEnv := map[string]string{
|
||||||
|
"ASDF_INSTALL_TYPE": parsedVersion.Type,
|
||||||
|
"ASDF_INSTALL_VERSION": parsedVersion.Value,
|
||||||
|
"ASDF_INSTALL_PATH": installs.InstallPath(conf, plugin, parsedVersion),
|
||||||
|
"PATH": setPath(conf, paths),
|
||||||
|
}
|
||||||
|
|
||||||
|
env, _ := execenv.Generate(plugin, callbackEnv)
|
||||||
|
return exec.Exec(executable, args, execute.MapToSlice(env))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExecutable(logger *log.Logger, conf config.Config, command string) (executable string, plugin plugins.Plugin, version string, err error) {
|
||||||
|
currentDir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("unable to get current directory: %s", err)
|
||||||
|
return "", plugins.Plugin{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
executable, plugin, version, found, err := shims.FindExecutable(conf, command, currentDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
|
if _, ok := err.(shims.NoExecutableForPluginError); ok {
|
||||||
|
logger.Printf("No executable %s found for current version. Please select a different version or install %s manually for the current version", command, command)
|
||||||
|
os.Exit(1)
|
||||||
|
return "", plugin, version, err
|
||||||
|
}
|
||||||
shimPath := shims.Path(conf, command)
|
shimPath := shims.Path(conf, command)
|
||||||
toolVersions, _ := shims.GetToolsAndVersionsFromShimFile(shimPath)
|
toolVersions, _ := shims.GetToolsAndVersionsFromShimFile(shimPath)
|
||||||
|
|
||||||
@ -383,21 +508,16 @@ func execCommand(logger *log.Logger, command string, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(126)
|
os.Exit(126)
|
||||||
return err
|
return executable, plugins.Plugin{}, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
logger.Print("executable not found")
|
logger.Print("executable not found")
|
||||||
os.Exit(126)
|
os.Exit(126)
|
||||||
return fmt.Errorf("executable not found")
|
return executable, plugins.Plugin{}, "", fmt.Errorf("executable not found")
|
||||||
}
|
|
||||||
if len(args) > 1 {
|
|
||||||
args = args[1:]
|
|
||||||
} else {
|
|
||||||
args = []string{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return exec.Exec(executable, args, os.Environ())
|
return executable, plugin, version, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func anyInstalled(conf config.Config, toolVersions []toolversions.ToolVersions) bool {
|
func anyInstalled(conf config.Config, toolVersions []toolversions.ToolVersions) bool {
|
||||||
@ -842,6 +962,11 @@ func reshimCommand(logger *log.Logger, tool, version string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func shimVersionsCommand(logger *log.Logger, shimName string) error {
|
func shimVersionsCommand(logger *log.Logger, shimName string) error {
|
||||||
|
if shimName == "" {
|
||||||
|
logger.Printf("usage: asdf shimversions <command>")
|
||||||
|
return fmt.Errorf("usage: asdf shimversions <command>")
|
||||||
|
}
|
||||||
|
|
||||||
conf, err := config.LoadConfig()
|
conf, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("error loading config: %s", err)
|
logger.Printf("error loading config: %s", err)
|
||||||
@ -852,7 +977,7 @@ func shimVersionsCommand(logger *log.Logger, shimName string) error {
|
|||||||
toolVersions, err := shims.GetToolsAndVersionsFromShimFile(shimPath)
|
toolVersions, err := shims.GetToolsAndVersionsFromShimFile(shimPath)
|
||||||
for _, toolVersion := range toolVersions {
|
for _, toolVersion := range toolVersions {
|
||||||
for _, version := range toolVersion.Versions {
|
for _, version := range toolVersion.Versions {
|
||||||
fmt.Printf("%s %s", toolVersion.Name, version)
|
fmt.Printf("%s %s\n", toolVersion.Name, version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -877,7 +1002,7 @@ func whichCommand(logger *log.Logger, command string) error {
|
|||||||
return errors.New("must provide command")
|
return errors.New("must provide command")
|
||||||
}
|
}
|
||||||
|
|
||||||
path, _, err := shims.FindExecutable(conf, command, currentDir)
|
path, _, _, _, err := shims.FindExecutable(conf, command, currentDir)
|
||||||
if _, ok := err.(shims.UnknownCommandError); ok {
|
if _, ok := err.(shims.UnknownCommandError); ok {
|
||||||
logger.Printf("unknown command: %s. Perhaps you have to reshim?", command)
|
logger.Printf("unknown command: %s. Perhaps you have to reshim?", command)
|
||||||
return errors.New("command not found")
|
return errors.New("command not found")
|
||||||
|
@ -20,6 +20,7 @@ versions are supported. The affected commands:
|
|||||||
* `asdf plugin-list-all` -> `asdf plugin list all`
|
* `asdf plugin-list-all` -> `asdf plugin list all`
|
||||||
* `asdf plugin-update` -> `asdf plugin update`
|
* `asdf plugin-update` -> `asdf plugin update`
|
||||||
* `asdf plugin-remove` -> `asdf plugin remove`
|
* `asdf plugin-remove` -> `asdf plugin remove`
|
||||||
|
* `asdf shim-versions` -> `asdf shimversions`
|
||||||
|
|
||||||
### `asdf global` and `asdf local` commands have been replaced by the `asdf set` command
|
### `asdf global` and `asdf local` commands have been replaced by the `asdf set` command
|
||||||
|
|
||||||
|
49
internal/execenv/execenv.go
Normal file
49
internal/execenv/execenv.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Package execenv contains logic for generating execution environing using a plugin's
|
||||||
|
// exec-env callback script if available.
|
||||||
|
package execenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"asdf/internal/execute"
|
||||||
|
"asdf/internal/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
const execEnvCallbackName = "exec-env"
|
||||||
|
|
||||||
|
// Generate runs exec-env callback if available and captures the environment
|
||||||
|
// variables it sets. It then parses them and returns them as a map.
|
||||||
|
func Generate(plugin plugins.Plugin, callbackEnv map[string]string) (env map[string]string, err error) {
|
||||||
|
execEnvPath, err := plugin.CallbackPath(execEnvCallbackName)
|
||||||
|
if err != nil {
|
||||||
|
return callbackEnv, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdout strings.Builder
|
||||||
|
|
||||||
|
// This is done to support the legacy behavior. exec-env is the only asdf
|
||||||
|
// callback that works by exporting environment variables. Because of this,
|
||||||
|
// executing the callback isn't enough. We actually need to source it (.) so
|
||||||
|
// the environment variables get set, and then run `env` so they get printed
|
||||||
|
// to STDOUT.
|
||||||
|
expression := execute.NewExpression(fmt.Sprintf(". \"%s\"; env", execEnvPath), []string{})
|
||||||
|
expression.Env = callbackEnv
|
||||||
|
expression.Stdout = &stdout
|
||||||
|
err = expression.Run()
|
||||||
|
|
||||||
|
return envMap(stdout.String()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func envMap(env string) map[string]string {
|
||||||
|
slice := map[string]string{}
|
||||||
|
|
||||||
|
for _, envVar := range strings.Split(env, "\n") {
|
||||||
|
varValue := strings.Split(envVar, "=")
|
||||||
|
if len(varValue) == 2 {
|
||||||
|
slice[varValue[0]] = varValue[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slice
|
||||||
|
}
|
43
internal/execenv/execenv_test.go
Normal file
43
internal/execenv/execenv_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package execenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"asdf/internal/config"
|
||||||
|
"asdf/internal/plugins"
|
||||||
|
"asdf/repotest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testPluginName = "lua"
|
||||||
|
testPluginName2 = "ruby"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
testDataDir := t.TempDir()
|
||||||
|
|
||||||
|
t.Run("returns map of environment variables", func(t *testing.T) {
|
||||||
|
conf := config.Config{DataDir: testDataDir}
|
||||||
|
_, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
plugin := plugins.New(conf, testPluginName)
|
||||||
|
assert.Nil(t, repotest.WritePluginCallback(plugin.Dir, "exec-env", "#!/usr/bin/env bash\nexport BAZ=bar"))
|
||||||
|
env, err := Generate(plugin, map[string]string{"ASDF_INSTALL_VERSION": "test"})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "bar", env["BAZ"])
|
||||||
|
assert.Equal(t, "test", env["ASDF_INSTALL_VERSION"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns error when plugin lacks exec-env callback", func(t *testing.T) {
|
||||||
|
conf := config.Config{DataDir: testDataDir}
|
||||||
|
_, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName2)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
plugin := plugins.New(conf, testPluginName2)
|
||||||
|
env, err := Generate(plugin, map[string]string{})
|
||||||
|
assert.Equal(t, err.(plugins.NoCallbackError).Error(), "Plugin named ruby does not have a callback named exec-env")
|
||||||
|
_, found := env["FOO"]
|
||||||
|
assert.False(t, found)
|
||||||
|
})
|
||||||
|
}
|
@ -47,7 +47,7 @@ func (c Command) Run() error {
|
|||||||
|
|
||||||
cmd := exec.Command("bash", "-c", command)
|
cmd := exec.Command("bash", "-c", command)
|
||||||
|
|
||||||
cmd.Env = mapToSlice(c.Env)
|
cmd.Env = MapToSlice(c.Env)
|
||||||
cmd.Stdin = c.Stdin
|
cmd.Stdin = c.Stdin
|
||||||
|
|
||||||
// Capture stdout and stderr
|
// Capture stdout and stderr
|
||||||
@ -57,6 +57,29 @@ func (c Command) Run() error {
|
|||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MapToSlice converts an env map to env slice suitable for syscall.Exec
|
||||||
|
func MapToSlice(env map[string]string) (slice []string) {
|
||||||
|
for key, value := range env {
|
||||||
|
slice = append(slice, fmt.Sprintf("%s=%s", key, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceToMap converts an env map to env slice suitable for syscall.Exec
|
||||||
|
func SliceToMap(env []string) map[string]string {
|
||||||
|
envMap := map[string]string{}
|
||||||
|
|
||||||
|
for _, envVar := range env {
|
||||||
|
varValue := strings.Split(envVar, "=")
|
||||||
|
if len(varValue) == 2 {
|
||||||
|
envMap[varValue[0]] = varValue[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return envMap
|
||||||
|
}
|
||||||
|
|
||||||
func formatArgString(args []string) string {
|
func formatArgString(args []string) string {
|
||||||
var newArgs []string
|
var newArgs []string
|
||||||
for _, str := range args {
|
for _, str := range args {
|
||||||
@ -64,11 +87,3 @@ func formatArgString(args []string) string {
|
|||||||
}
|
}
|
||||||
return strings.Join(newArgs, " ")
|
return strings.Join(newArgs, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapToSlice(env map[string]string) (slice []string) {
|
|
||||||
for key, value := range env {
|
|
||||||
slice = append(slice, fmt.Sprintf("%s=%s", key, value))
|
|
||||||
}
|
|
||||||
|
|
||||||
return slice
|
|
||||||
}
|
|
||||||
|
@ -154,11 +154,9 @@ func (p Plugin) Exists() error {
|
|||||||
|
|
||||||
// RunCallback invokes a callback with the given name if it exists for the plugin
|
// RunCallback invokes a callback with the given name if it exists for the plugin
|
||||||
func (p Plugin) RunCallback(name string, arguments []string, environment map[string]string, stdOut io.Writer, errOut io.Writer) error {
|
func (p Plugin) RunCallback(name string, arguments []string, environment map[string]string, stdOut io.Writer, errOut io.Writer) error {
|
||||||
callback := filepath.Join(p.Dir, "bin", name)
|
callback, err := p.CallbackPath(name)
|
||||||
|
if err != nil {
|
||||||
_, err := os.Stat(callback)
|
return err
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return NoCallbackError{callback: name, plugin: p.Name}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := execute.New(fmt.Sprintf("'%s'", callback), arguments)
|
cmd := execute.New(fmt.Sprintf("'%s'", callback), arguments)
|
||||||
@ -170,6 +168,17 @@ func (p Plugin) RunCallback(name string, arguments []string, environment map[str
|
|||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CallbackPath returns the full file path to a callback script
|
||||||
|
func (p Plugin) CallbackPath(name string) (string, error) {
|
||||||
|
path := filepath.Join(p.Dir, "bin", name)
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return "", NoCallbackError{callback: name, plugin: p.Name}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
// List takes config and flags for what to return and builds a list of plugins
|
// List takes config and flags for what to return and builds a list of plugins
|
||||||
// representing the currently installed plugins on the system.
|
// representing the currently installed plugins on the system.
|
||||||
func List(config config.Config, urls, refs bool) (plugins []Plugin, err error) {
|
func List(config config.Config, urls, refs bool) (plugins []Plugin, err error) {
|
||||||
|
@ -183,7 +183,7 @@ func TestRemove(t *testing.T) {
|
|||||||
t.Run("returns error when plugin with name does not exist", func(t *testing.T) {
|
t.Run("returns error when plugin with name does not exist", func(t *testing.T) {
|
||||||
var stdout strings.Builder
|
var stdout strings.Builder
|
||||||
var stderr strings.Builder
|
var stderr strings.Builder
|
||||||
err := Remove(conf, "nonexistant", &stdout, &stderr)
|
err := Remove(conf, "nonexistent", &stdout, &stderr)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.ErrorContains(t, err, "No such plugin")
|
assert.ErrorContains(t, err, "No such plugin")
|
||||||
})
|
})
|
||||||
@ -251,10 +251,10 @@ func TestUpdate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "returns error when plugin with name does not exist",
|
desc: "returns error when plugin with name does not exist",
|
||||||
givenConf: conf,
|
givenConf: conf,
|
||||||
givenName: "nonexistant",
|
givenName: "nonexistent",
|
||||||
givenRef: "",
|
givenRef: "",
|
||||||
wantSomeRef: false,
|
wantSomeRef: false,
|
||||||
wantErrMsg: "no such plugin: nonexistant",
|
wantErrMsg: "no such plugin: nonexistent",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "returns error when plugin repo does not exist",
|
desc: "returns error when plugin repo does not exist",
|
||||||
@ -352,7 +352,7 @@ func TestPluginExists(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns false when plugin dir does not exist", func(t *testing.T) {
|
t.Run("returns false when plugin dir does not exist", func(t *testing.T) {
|
||||||
exists, err := PluginExists(testDataDir, "non-existant")
|
exists, err := PluginExists(testDataDir, "non-existent")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("got %v, expected nil", err)
|
t.Errorf("got %v, expected nil", err)
|
||||||
}
|
}
|
||||||
@ -396,9 +396,9 @@ func TestRunCallback(t *testing.T) {
|
|||||||
var stdout strings.Builder
|
var stdout strings.Builder
|
||||||
var stderr strings.Builder
|
var stderr strings.Builder
|
||||||
|
|
||||||
err = plugin.RunCallback("non-existant", []string{}, emptyEnv, &stdout, &stderr)
|
err = plugin.RunCallback("non-existent", []string{}, emptyEnv, &stdout, &stderr)
|
||||||
|
|
||||||
assert.Equal(t, err.(NoCallbackError).Error(), "Plugin named lua does not have a callback named non-existant")
|
assert.Equal(t, err.(NoCallbackError).Error(), "Plugin named lua does not have a callback named non-existent")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("passes argument to command", func(t *testing.T) {
|
t.Run("passes argument to command", func(t *testing.T) {
|
||||||
@ -432,6 +432,28 @@ func TestRunCallback(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCallbackPath(t *testing.T) {
|
||||||
|
testDataDir := t.TempDir()
|
||||||
|
conf := config.Config{DataDir: testDataDir}
|
||||||
|
_, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
plugin := New(conf, testPluginName)
|
||||||
|
|
||||||
|
t.Run("returns callback path when callback exists", func(t *testing.T) {
|
||||||
|
path, err := plugin.CallbackPath("install")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, filepath.Base(path), "install")
|
||||||
|
assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(path))), plugin.Name)
|
||||||
|
assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(filepath.Dir(path)))), "plugins")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns error when callback does not exist", func(t *testing.T) {
|
||||||
|
path, err := plugin.CallbackPath("non-existent")
|
||||||
|
assert.Equal(t, err.(NoCallbackError).Error(), "Plugin named lua does not have a callback named non-existent")
|
||||||
|
assert.Equal(t, path, "")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestLegacyFilenames(t *testing.T) {
|
func TestLegacyFilenames(t *testing.T) {
|
||||||
testDataDir := t.TempDir()
|
testDataDir := t.TempDir()
|
||||||
conf := config.Config{DataDir: testDataDir}
|
conf := config.Config{DataDir: testDataDir}
|
||||||
@ -488,7 +510,7 @@ func TestParseLegacyVersionFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns error when passed file that doesn't exist", func(t *testing.T) {
|
t.Run("returns error when passed file that doesn't exist", func(t *testing.T) {
|
||||||
versions, err := plugin.ParseLegacyVersionFile("non-existant-file")
|
versions, err := plugin.ParseLegacyVersionFile("non-existent-file")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Empty(t, versions)
|
assert.Empty(t, versions)
|
||||||
})
|
})
|
||||||
|
@ -56,16 +56,16 @@ func (e NoExecutableForPluginError) Error() string {
|
|||||||
|
|
||||||
// FindExecutable takes a shim name and a current directory and returns the path
|
// FindExecutable takes a shim name and a current directory and returns the path
|
||||||
// to the executable that the shim resolves to.
|
// to the executable that the shim resolves to.
|
||||||
func FindExecutable(conf config.Config, shimName, currentDirectory string) (string, bool, error) {
|
func FindExecutable(conf config.Config, shimName, currentDirectory string) (string, plugins.Plugin, string, bool, error) {
|
||||||
shimPath := Path(conf, shimName)
|
shimPath := Path(conf, shimName)
|
||||||
|
|
||||||
if _, err := os.Stat(shimPath); err != nil {
|
if _, err := os.Stat(shimPath); err != nil {
|
||||||
return "", false, UnknownCommandError{shim: shimName}
|
return "", plugins.Plugin{}, "", false, UnknownCommandError{shim: shimName}
|
||||||
}
|
}
|
||||||
|
|
||||||
toolVersions, err := GetToolsAndVersionsFromShimFile(shimPath)
|
toolVersions, err := GetToolsAndVersionsFromShimFile(shimPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", plugins.Plugin{}, "", false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
existingPluginToolVersions := make(map[plugins.Plugin]resolve.ToolVersions)
|
existingPluginToolVersions := make(map[plugins.Plugin]resolve.ToolVersions)
|
||||||
@ -77,7 +77,7 @@ func FindExecutable(conf config.Config, shimName, currentDirectory string) (stri
|
|||||||
|
|
||||||
versions, found, err := resolve.Version(conf, plugin, currentDirectory)
|
versions, found, err := resolve.Version(conf, plugin, currentDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, nil
|
return "", plugins.Plugin{}, "", false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
@ -93,21 +93,21 @@ func FindExecutable(conf config.Config, shimName, currentDirectory string) (stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(existingPluginToolVersions) == 0 {
|
if len(existingPluginToolVersions) == 0 {
|
||||||
return "", false, NoVersionSetError{shim: shimName}
|
return "", plugins.Plugin{}, "", false, NoVersionSetError{shim: shimName}
|
||||||
}
|
}
|
||||||
|
|
||||||
for plugin, toolVersions := range existingPluginToolVersions {
|
for plugin, toolVersions := range existingPluginToolVersions {
|
||||||
for _, version := range toolVersions.Versions {
|
for _, version := range toolVersions.Versions {
|
||||||
if version == "system" {
|
if version == "system" {
|
||||||
if executablePath, found := FindSystemExecutable(conf, shimName); found {
|
if executablePath, found := FindSystemExecutable(conf, shimName); found {
|
||||||
return executablePath, true, nil
|
return executablePath, plugin, version, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
executablePath, err := GetExecutablePath(conf, plugin, shimName, version)
|
executablePath, err := GetExecutablePath(conf, plugin, shimName, version)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return executablePath, true, nil
|
return executablePath, plugin, version, true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ func FindExecutable(conf config.Config, shimName, currentDirectory string) (stri
|
|||||||
versions = append(versions, existingPluginToolVersions[plugin].Versions...)
|
versions = append(versions, existingPluginToolVersions[plugin].Versions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", false, NoExecutableForPluginError{shim: shimName, tools: tools, versions: versions}
|
return "", plugins.Plugin{}, "", false, NoExecutableForPluginError{shim: shimName, tools: tools, versions: versions}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindSystemExecutable returns the path to the system
|
// FindSystemExecutable returns the path to the system
|
||||||
@ -127,7 +127,7 @@ func FindExecutable(conf config.Config, shimName, currentDirectory string) (stri
|
|||||||
func FindSystemExecutable(conf config.Config, executableName string) (string, bool) {
|
func FindSystemExecutable(conf config.Config, executableName string) (string, bool) {
|
||||||
currentPath := os.Getenv("PATH")
|
currentPath := os.Getenv("PATH")
|
||||||
defer os.Setenv("PATH", currentPath)
|
defer os.Setenv("PATH", currentPath)
|
||||||
os.Setenv("PATH", paths.RemoveFromPath(currentPath, shimsDirectory(conf)))
|
os.Setenv("PATH", paths.RemoveFromPath(currentPath, Directory(conf)))
|
||||||
executablePath, err := exec.LookPath(executableName)
|
executablePath, err := exec.LookPath(executableName)
|
||||||
return executablePath, err == nil
|
return executablePath, err == nil
|
||||||
}
|
}
|
||||||
@ -288,7 +288,9 @@ func Path(conf config.Config, shimName string) string {
|
|||||||
return filepath.Join(conf.DataDir, shimDirName, shimName)
|
return filepath.Join(conf.DataDir, shimDirName, shimName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shimsDirectory(conf config.Config) string {
|
// Directory returns the path to the shims directory for the current
|
||||||
|
// configuration.
|
||||||
|
func Directory(conf config.Config) string {
|
||||||
return filepath.Join(conf.DataDir, shimDirName)
|
return filepath.Join(conf.DataDir, shimDirName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,14 +300,11 @@ func ensureShimDirExists(conf config.Config) error {
|
|||||||
|
|
||||||
// ToolExecutables returns a slice of executables for a given tool version
|
// ToolExecutables returns a slice of executables for a given tool version
|
||||||
func ToolExecutables(conf config.Config, plugin plugins.Plugin, versionType, version string) (executables []string, err error) {
|
func ToolExecutables(conf config.Config, plugin plugins.Plugin, versionType, version string) (executables []string, err error) {
|
||||||
dirs, err := ExecutableDirs(plugin)
|
paths, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: versionType, Value: version})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return executables, err
|
return []string{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
installPath := installs.InstallPath(conf, plugin, toolversions.Version{Type: versionType, Value: version})
|
|
||||||
paths := dirsToPaths(dirs, installPath)
|
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
entries, err := os.ReadDir(path)
|
entries, err := os.ReadDir(path)
|
||||||
if _, ok := err.(*os.PathError); err != nil && !ok {
|
if _, ok := err.(*os.PathError); err != nil && !ok {
|
||||||
@ -325,6 +324,18 @@ func ToolExecutables(conf config.Config, plugin plugins.Plugin, versionType, ver
|
|||||||
return executables, err
|
return executables, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExecutablePaths returns a slice of absolute directory paths that tool
|
||||||
|
// executables are contained in.
|
||||||
|
func ExecutablePaths(conf config.Config, plugin plugins.Plugin, version toolversions.Version) ([]string, error) {
|
||||||
|
dirs, err := ExecutableDirs(plugin)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
installPath := installs.InstallPath(conf, plugin, version)
|
||||||
|
return dirsToPaths(dirs, installPath), nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExecutableDirs returns a slice of directory names that tool executables are
|
// ExecutableDirs returns a slice of directory names that tool executables are
|
||||||
// contained in
|
// contained in
|
||||||
func ExecutableDirs(plugin plugins.Plugin) ([]string, error) {
|
func ExecutableDirs(plugin plugins.Plugin) ([]string, error) {
|
||||||
|
@ -30,16 +30,18 @@ func TestFindExecutable(t *testing.T) {
|
|||||||
currentDir := t.TempDir()
|
currentDir := t.TempDir()
|
||||||
|
|
||||||
t.Run("returns error when shim with name does not exist", func(t *testing.T) {
|
t.Run("returns error when shim with name does not exist", func(t *testing.T) {
|
||||||
executable, found, err := FindExecutable(conf, "foo", currentDir)
|
executable, _, version, found, err := FindExecutable(conf, "foo", currentDir)
|
||||||
assert.Empty(t, executable)
|
assert.Empty(t, executable)
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
|
assert.Empty(t, version)
|
||||||
assert.Equal(t, err.(UnknownCommandError).Error(), "unknown command: foo")
|
assert.Equal(t, err.(UnknownCommandError).Error(), "unknown command: foo")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns error when shim is present but no version is set", func(t *testing.T) {
|
t.Run("returns error when shim is present but no version is set", func(t *testing.T) {
|
||||||
executable, found, err := FindExecutable(conf, "dummy", currentDir)
|
executable, _, version, found, err := FindExecutable(conf, "dummy", currentDir)
|
||||||
assert.Empty(t, executable)
|
assert.Empty(t, executable)
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
|
assert.Empty(t, version)
|
||||||
assert.Equal(t, err.(NoVersionSetError).Error(), "no versions set for dummy")
|
assert.Equal(t, err.(NoVersionSetError).Error(), "no versions set for dummy")
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -48,9 +50,11 @@ func TestFindExecutable(t *testing.T) {
|
|||||||
data := []byte("lua 1.1.0")
|
data := []byte("lua 1.1.0")
|
||||||
assert.Nil(t, os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666))
|
assert.Nil(t, os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666))
|
||||||
|
|
||||||
executable, found, err := FindExecutable(conf, "dummy", currentDir)
|
executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir)
|
||||||
assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(executable))), "1.1.0")
|
assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(executable))), "1.1.0")
|
||||||
assert.Equal(t, filepath.Base(executable), "dummy")
|
assert.Equal(t, filepath.Base(executable), "dummy")
|
||||||
|
assert.Equal(t, plugin, gotPlugin)
|
||||||
|
assert.Equal(t, version, "1.1.0")
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
})
|
})
|
||||||
@ -66,7 +70,9 @@ func TestFindExecutable(t *testing.T) {
|
|||||||
assert.Nil(t, os.WriteFile(toolpath, []byte("lua system\n"), 0o666))
|
assert.Nil(t, os.WriteFile(toolpath, []byte("lua system\n"), 0o666))
|
||||||
assert.Nil(t, GenerateAll(conf, &stdout, &stderr))
|
assert.Nil(t, GenerateAll(conf, &stdout, &stderr))
|
||||||
|
|
||||||
executable, found, err := FindExecutable(conf, "ls", currentDir)
|
executable, gotPlugin, version, found, err := FindExecutable(conf, "ls", currentDir)
|
||||||
|
assert.Equal(t, plugin, gotPlugin)
|
||||||
|
assert.Equal(t, version, "system")
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@ -336,6 +342,32 @@ func TestToolExecutables(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExecutablePaths(t *testing.T) {
|
||||||
|
conf, plugin := generateConfig(t)
|
||||||
|
installVersion(t, conf, plugin, "1.2.3")
|
||||||
|
|
||||||
|
t.Run("returns list only containing 'bin' when list-bin-paths callback missing", func(t *testing.T) {
|
||||||
|
executables, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: "version", Value: "1.2.3"})
|
||||||
|
path := executables[0]
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, filepath.Base(filepath.Dir(path)), "1.2.3")
|
||||||
|
assert.Equal(t, filepath.Base(path), "bin")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns list of executable paths for tool version", func(t *testing.T) {
|
||||||
|
data := []byte("echo 'foo bar'")
|
||||||
|
err := os.WriteFile(filepath.Join(plugin.Dir, "bin", "list-bin-paths"), data, 0o777)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
executables, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: "version", Value: "1.2.3"})
|
||||||
|
path1 := executables[0]
|
||||||
|
path2 := executables[1]
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, filepath.Base(path1), "foo")
|
||||||
|
assert.Equal(t, filepath.Base(path2), "bar")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestExecutableDirs(t *testing.T) {
|
func TestExecutableDirs(t *testing.T) {
|
||||||
conf, plugin := generateConfig(t)
|
conf, plugin := generateConfig(t)
|
||||||
installVersion(t, conf, plugin, "1.2.3")
|
installVersion(t, conf, plugin, "1.2.3")
|
||||||
|
@ -75,9 +75,9 @@ func TestBatsTests(t *testing.T) {
|
|||||||
runBatsFile(t, dir, "reshim_command.bats")
|
runBatsFile(t, dir, "reshim_command.bats")
|
||||||
})
|
})
|
||||||
|
|
||||||
//t.Run("shim_env_command", func(t *testing.T) {
|
t.Run("shim_env_command", func(t *testing.T) {
|
||||||
// runBatsFile(t, dir, "shim_env_command.bats")
|
runBatsFile(t, dir, "shim_env_command.bats")
|
||||||
//})
|
})
|
||||||
|
|
||||||
//t.Run("shim_exec", func(t *testing.T) {
|
//t.Run("shim_exec", func(t *testing.T) {
|
||||||
// runBatsFile(t, dir, "shim_exec.bats")
|
// runBatsFile(t, dir, "shim_exec.bats")
|
||||||
|
@ -38,7 +38,8 @@ teardown() {
|
|||||||
echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions"
|
echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions"
|
||||||
run asdf install
|
run asdf install
|
||||||
|
|
||||||
echo "export FOO=bar" >"$ASDF_DIR/plugins/dummy/bin/exec-env"
|
echo '#!/usr/bin/env bash
|
||||||
|
export FOO=bar' >"$ASDF_DIR/plugins/dummy/bin/exec-env"
|
||||||
chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env"
|
chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env"
|
||||||
|
|
||||||
run asdf env dummy
|
run asdf env dummy
|
||||||
@ -46,13 +47,34 @@ teardown() {
|
|||||||
echo "$output" | grep 'FOO=bar'
|
echo "$output" | grep 'FOO=bar'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "asdf env should print error when plugin version lacks the specified executable" {
|
||||||
|
echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions"
|
||||||
|
run asdf install
|
||||||
|
|
||||||
|
echo '#!/usr/bin/env bash
|
||||||
|
export FOO=bar' >"$ASDF_DIR/plugins/dummy/bin/exec-env"
|
||||||
|
chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env"
|
||||||
|
|
||||||
|
echo "dummy system" >"$PROJECT_DIR/.tool-versions"
|
||||||
|
|
||||||
|
run asdf env dummy
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[ "$output" = "No executable dummy found for current version. Please select a different version or install dummy manually for the current version" ]
|
||||||
|
}
|
||||||
|
|
||||||
@test "asdf env should ignore plugin custom environment on system version" {
|
@test "asdf env should ignore plugin custom environment on system version" {
|
||||||
echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions"
|
echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions"
|
||||||
run asdf install
|
run asdf install
|
||||||
|
|
||||||
echo "export FOO=bar" >"$ASDF_DIR/plugins/dummy/bin/exec-env"
|
echo '#!/usr/bin/env bash
|
||||||
|
export FOO=bar' >"$ASDF_DIR/plugins/dummy/bin/exec-env"
|
||||||
chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env"
|
chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env"
|
||||||
|
|
||||||
|
# Create a "system" dummy executable
|
||||||
|
echo '#!/usr/bin/env bash
|
||||||
|
echo "system dummy"' >"$ASDF_BIN/dummy"
|
||||||
|
chmod +x "$ASDF_BIN/dummy"
|
||||||
|
|
||||||
echo "dummy system" >"$PROJECT_DIR/.tool-versions"
|
echo "dummy system" >"$PROJECT_DIR/.tool-versions"
|
||||||
|
|
||||||
run asdf env dummy
|
run asdf env dummy
|
||||||
@ -63,8 +85,10 @@ teardown() {
|
|||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
|
|
||||||
run asdf env dummy which dummy
|
run asdf env dummy which dummy
|
||||||
[ "$output" = "$ASDF_DIR/shims/dummy" ]
|
[ "$output" = "$ASDF_BIN/dummy" ]
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
# Remove "system" dummy executable
|
||||||
|
rm "$ASDF_BIN/dummy"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "asdf env should set PATH correctly" {
|
@test "asdf env should set PATH correctly" {
|
||||||
|
Loading…
Reference in New Issue
Block a user