mirror of
https://github.com/asdf-vm/asdf.git
synced 2024-12-19 09:55:01 -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"
|
||||
"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")
|
||||
|
@ -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
|
||||
|
||||
|
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.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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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" {
|
||||
|
Loading…
Reference in New Issue
Block a user