diff --git a/cmd/cmd.go b/cmd/cmd.go index 98365b32..d108e5c5 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -7,6 +7,7 @@ import ( "io" "log" "os" + osexec "os/exec" "path/filepath" "slices" "strings" @@ -14,9 +15,12 @@ import ( "asdf/internal/config" "asdf/internal/exec" + "asdf/internal/execenv" + "asdf/internal/execute" "asdf/internal/help" "asdf/internal/info" "asdf/internal/installs" + "asdf/internal/paths" "asdf/internal/plugins" "asdf/internal/resolve" "asdf/internal/shims" @@ -59,6 +63,15 @@ func Execute(version string) { 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", 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 { if command == "" { logger.Printf("usage: asdf exec <command>") @@ -347,16 +437,51 @@ func execCommand(logger *log.Logger, command string, args []string) error { 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 { - logger.Printf("unable to get current directory: %s", 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 _, 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) toolVersions, _ := shims.GetToolsAndVersionsFromShimFile(shimPath) @@ -383,21 +508,16 @@ func execCommand(logger *log.Logger, command string, args []string) error { } os.Exit(126) - return err + return executable, plugins.Plugin{}, "", err } if !found { logger.Print("executable not found") os.Exit(126) - return fmt.Errorf("executable not found") - } - if len(args) > 1 { - args = args[1:] - } else { - args = []string{} + return executable, plugins.Plugin{}, "", fmt.Errorf("executable not found") } - return exec.Exec(executable, args, os.Environ()) + return executable, plugin, version, nil } 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 { + if shimName == "" { + logger.Printf("usage: asdf shimversions <command>") + return fmt.Errorf("usage: asdf shimversions <command>") + } + conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) @@ -852,7 +977,7 @@ func shimVersionsCommand(logger *log.Logger, shimName string) error { toolVersions, err := shims.GetToolsAndVersionsFromShimFile(shimPath) for _, toolVersion := range toolVersions { for _, version := range toolVersion.Versions { - fmt.Printf("%s %s", toolVersion.Name, version) + fmt.Printf("%s %s\n", toolVersion.Name, version) } } return err @@ -877,7 +1002,7 @@ func whichCommand(logger *log.Logger, command string) error { 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 { logger.Printf("unknown command: %s. Perhaps you have to reshim?", command) return errors.New("command not found") diff --git a/docs/guide/upgrading-from-v0-14-to-v0-15.md b/docs/guide/upgrading-from-v0-14-to-v0-15.md index 2335147b..e7288c15 100644 --- a/docs/guide/upgrading-from-v0-14-to-v0-15.md +++ b/docs/guide/upgrading-from-v0-14-to-v0-15.md @@ -20,6 +20,7 @@ versions are supported. The affected commands: * `asdf plugin-list-all` -> `asdf plugin list all` * `asdf plugin-update` -> `asdf plugin update` * `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 diff --git a/internal/execenv/execenv.go b/internal/execenv/execenv.go new file mode 100644 index 00000000..eeb8bcf2 --- /dev/null +++ b/internal/execenv/execenv.go @@ -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 +} diff --git a/internal/execenv/execenv_test.go b/internal/execenv/execenv_test.go new file mode 100644 index 00000000..d705f66c --- /dev/null +++ b/internal/execenv/execenv_test.go @@ -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) + }) +} diff --git a/internal/execute/execute.go b/internal/execute/execute.go index d6e98c4b..561c647b 100644 --- a/internal/execute/execute.go +++ b/internal/execute/execute.go @@ -47,7 +47,7 @@ func (c Command) Run() error { cmd := exec.Command("bash", "-c", command) - cmd.Env = mapToSlice(c.Env) + cmd.Env = MapToSlice(c.Env) cmd.Stdin = c.Stdin // Capture stdout and stderr @@ -57,6 +57,29 @@ func (c Command) Run() error { 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 { var newArgs []string for _, str := range args { @@ -64,11 +87,3 @@ func formatArgString(args []string) string { } 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 -} diff --git a/internal/plugins/plugins.go b/internal/plugins/plugins.go index a721cb39..12ab3a97 100644 --- a/internal/plugins/plugins.go +++ b/internal/plugins/plugins.go @@ -154,11 +154,9 @@ func (p Plugin) Exists() error { // 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 { - callback := filepath.Join(p.Dir, "bin", name) - - _, err := os.Stat(callback) - if errors.Is(err, os.ErrNotExist) { - return NoCallbackError{callback: name, plugin: p.Name} + callback, err := p.CallbackPath(name) + if err != nil { + return err } 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() } +// 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 // representing the currently installed plugins on the system. func List(config config.Config, urls, refs bool) (plugins []Plugin, err error) { diff --git a/internal/plugins/plugins_test.go b/internal/plugins/plugins_test.go index a0d53052..98c5622e 100644 --- a/internal/plugins/plugins_test.go +++ b/internal/plugins/plugins_test.go @@ -183,7 +183,7 @@ func TestRemove(t *testing.T) { t.Run("returns error when plugin with name does not exist", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder - err := Remove(conf, "nonexistant", &stdout, &stderr) + err := Remove(conf, "nonexistent", &stdout, &stderr) assert.NotNil(t, err) 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", givenConf: conf, - givenName: "nonexistant", + givenName: "nonexistent", givenRef: "", wantSomeRef: false, - wantErrMsg: "no such plugin: nonexistant", + wantErrMsg: "no such plugin: nonexistent", }, { 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) { - exists, err := PluginExists(testDataDir, "non-existant") + exists, err := PluginExists(testDataDir, "non-existent") if err != nil { t.Errorf("got %v, expected nil", err) } @@ -396,9 +396,9 @@ func TestRunCallback(t *testing.T) { var stdout 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) { @@ -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) { testDataDir := t.TempDir() 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) { - versions, err := plugin.ParseLegacyVersionFile("non-existant-file") + versions, err := plugin.ParseLegacyVersionFile("non-existent-file") assert.Error(t, err) assert.Empty(t, versions) }) diff --git a/internal/shims/shims.go b/internal/shims/shims.go index 941986e6..9c187ed5 100644 --- a/internal/shims/shims.go +++ b/internal/shims/shims.go @@ -56,16 +56,16 @@ func (e NoExecutableForPluginError) Error() string { // FindExecutable takes a shim name and a current directory and returns the path // 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) if _, err := os.Stat(shimPath); err != nil { - return "", false, UnknownCommandError{shim: shimName} + return "", plugins.Plugin{}, "", false, UnknownCommandError{shim: shimName} } toolVersions, err := GetToolsAndVersionsFromShimFile(shimPath) if err != nil { - return "", false, err + return "", plugins.Plugin{}, "", false, err } 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) if err != nil { - return "", false, nil + return "", plugins.Plugin{}, "", false, nil } if found { @@ -93,21 +93,21 @@ func FindExecutable(conf config.Config, shimName, currentDirectory string) (stri } if len(existingPluginToolVersions) == 0 { - return "", false, NoVersionSetError{shim: shimName} + return "", plugins.Plugin{}, "", false, NoVersionSetError{shim: shimName} } for plugin, toolVersions := range existingPluginToolVersions { for _, version := range toolVersions.Versions { if version == "system" { if executablePath, found := FindSystemExecutable(conf, shimName); found { - return executablePath, true, nil + return executablePath, plugin, version, true, nil } break } executablePath, err := GetExecutablePath(conf, plugin, shimName, version) 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...) } - 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 @@ -127,7 +127,7 @@ func FindExecutable(conf config.Config, shimName, currentDirectory string) (stri func FindSystemExecutable(conf config.Config, executableName string) (string, bool) { currentPath := os.Getenv("PATH") 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) return executablePath, err == nil } @@ -288,7 +288,9 @@ func Path(conf config.Config, shimName string) string { 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) } @@ -298,14 +300,11 @@ func ensureShimDirExists(conf config.Config) error { // 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) { - dirs, err := ExecutableDirs(plugin) + paths, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: versionType, Value: version}) 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 { entries, err := os.ReadDir(path) 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 } +// 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 // contained in func ExecutableDirs(plugin plugins.Plugin) ([]string, error) { diff --git a/internal/shims/shims_test.go b/internal/shims/shims_test.go index be314362..ed5afbc9 100644 --- a/internal/shims/shims_test.go +++ b/internal/shims/shims_test.go @@ -30,16 +30,18 @@ func TestFindExecutable(t *testing.T) { currentDir := t.TempDir() 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.False(t, found) + assert.Empty(t, version) 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) { - executable, found, err := FindExecutable(conf, "dummy", currentDir) + executable, _, version, found, err := FindExecutable(conf, "dummy", currentDir) assert.Empty(t, executable) assert.False(t, found) + assert.Empty(t, version) 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") 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(executable), "dummy") + assert.Equal(t, plugin, gotPlugin) + assert.Equal(t, version, "1.1.0") assert.True(t, found) 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, 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.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) { conf, plugin := generateConfig(t) installVersion(t, conf, plugin, "1.2.3") diff --git a/main_test.go b/main_test.go index 424b4aba..d13b798d 100644 --- a/main_test.go +++ b/main_test.go @@ -75,9 +75,9 @@ func TestBatsTests(t *testing.T) { runBatsFile(t, dir, "reshim_command.bats") }) - //t.Run("shim_env_command", func(t *testing.T) { - // runBatsFile(t, dir, "shim_env_command.bats") - //}) + t.Run("shim_env_command", func(t *testing.T) { + runBatsFile(t, dir, "shim_env_command.bats") + }) //t.Run("shim_exec", func(t *testing.T) { // runBatsFile(t, dir, "shim_exec.bats") diff --git a/test/shim_env_command.bats b/test/shim_env_command.bats index c97da850..a5747856 100644 --- a/test/shim_env_command.bats +++ b/test/shim_env_command.bats @@ -38,7 +38,8 @@ teardown() { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" 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" run asdf env dummy @@ -46,13 +47,34 @@ teardown() { 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" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" 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" + # 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" run asdf env dummy @@ -63,8 +85,10 @@ teardown() { [ "$status" -eq 1 ] run asdf env dummy which dummy - [ "$output" = "$ASDF_DIR/shims/dummy" ] + [ "$output" = "$ASDF_BIN/dummy" ] [ "$status" -eq 0 ] + # Remove "system" dummy executable + rm "$ASDF_BIN/dummy" } @test "asdf env should set PATH correctly" {