Merge pull request #86 from asdf-vm/tb/more-shim-exec-fixes

feat(golang-rewrite): more shim exec fixes
This commit is contained in:
Trevor Brown 2024-11-30 08:26:44 -05:00 committed by Trevor Brown
commit e8cde35779
5 changed files with 149 additions and 42 deletions

View File

@ -1110,7 +1110,7 @@ func whereCommand(logger *log.Logger, tool, versionStr string) error {
func reshimToolVersion(conf config.Config, tool, versionStr string, out io.Writer, errOut io.Writer) error { func reshimToolVersion(conf config.Config, tool, versionStr string, out io.Writer, errOut io.Writer) error {
version := toolversions.Parse(versionStr) version := toolversions.Parse(versionStr)
return shims.GenerateForVersion(conf, plugins.New(conf, tool), version.Type, version.Value, out, errOut) return shims.GenerateForVersion(conf, plugins.New(conf, tool), version, out, errOut)
} }
func latestForPlugin(conf config.Config, toolName, pattern string, showStatus bool) error { func latestForPlugin(conf config.Config, toolName, pattern string, showStatus bool) error {

View File

@ -86,6 +86,13 @@ func FindExecutable(conf config.Config, shimName, currentDirectory string) (stri
tempVersions = append(tempVersions, "system") tempVersions = append(tempVersions, "system")
} }
parsedVersions := toolversions.ParseSlice(versions.Versions)
for _, parsedVersion := range parsedVersions {
if parsedVersion.Type == "path" {
tempVersions = append(tempVersions, toolversions.Format(parsedVersion))
}
}
versions.Versions = tempVersions versions.Versions = tempVersions
existingPluginToolVersions[plugin] = versions existingPluginToolVersions[plugin] = versions
} }
@ -98,14 +105,25 @@ func FindExecutable(conf config.Config, shimName, currentDirectory string) (stri
for plugin, toolVersions := range existingPluginToolVersions { for plugin, toolVersions := range existingPluginToolVersions {
for _, version := range toolVersions.Versions { for _, version := range toolVersions.Versions {
if version == "system" { parsedVersion := toolversions.Parse(version)
if parsedVersion.Type == "system" {
if executablePath, found := SystemExecutableOnPath(conf, shimName); found { if executablePath, found := SystemExecutableOnPath(conf, shimName); found {
return executablePath, plugin, version, true, nil return executablePath, plugin, version, true, nil
} }
break break
} }
executablePath, err := GetExecutablePath(conf, plugin, shimName, version)
if parsedVersion.Type == "path" {
executablePath, err := GetExecutablePath(conf, plugin, shimName, parsedVersion)
if err == nil {
return executablePath, plugin, version, true, nil
}
break
}
executablePath, err := GetExecutablePath(conf, plugin, shimName, parsedVersion)
if err == nil { if err == nil {
return executablePath, plugin, version, true, nil return executablePath, plugin, version, true, nil
} }
@ -141,13 +159,13 @@ func ExecutableOnPath(path, command string) (string, error) {
} }
// GetExecutablePath returns the path of the executable // GetExecutablePath returns the path of the executable
func GetExecutablePath(conf config.Config, plugin plugins.Plugin, shimName, version string) (string, error) { func GetExecutablePath(conf config.Config, plugin plugins.Plugin, shimName string, version toolversions.Version) (string, error) {
path, err := getCustomExecutablePath(conf, plugin, shimName, version) path, err := getCustomExecutablePath(conf, plugin, shimName, version)
if err == nil { if err == nil {
return path, err return path, err
} }
executables, err := ToolExecutables(conf, plugin, "version", version) executables, err := ToolExecutables(conf, plugin, version)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -177,11 +195,11 @@ func GetToolsAndVersionsFromShimFile(shimPath string) (versions []toolversions.T
return versions, err return versions, err
} }
func getCustomExecutablePath(conf config.Config, plugin plugins.Plugin, shimName, version string) (string, error) { func getCustomExecutablePath(conf config.Config, plugin plugins.Plugin, shimName string, version toolversions.Version) (string, error) {
var stdOut strings.Builder var stdOut strings.Builder
var stdErr strings.Builder var stdErr strings.Builder
installPath := installs.InstallPath(conf, plugin, toolversions.Version{Type: "version", Value: version}) installPath := installs.InstallPath(conf, plugin, version)
env := map[string]string{"ASDF_INSTALL_TYPE": "version"} env := map[string]string{"ASDF_INSTALL_TYPE": "version"}
err := plugin.RunCallback("exec-path", []string{installPath, shimName}, env, &stdOut, &stdErr) err := plugin.RunCallback("exec-path", []string{installPath, shimName}, env, &stdOut, &stdErr)
@ -234,27 +252,24 @@ func GenerateForPluginVersions(conf config.Config, plugin plugins.Plugin, stdOut
} }
for _, version := range installedVersions { for _, version := range installedVersions {
GenerateForVersion(conf, plugin, "version", version, stdOut, stdErr) parsedVersion := toolversions.Parse(version)
GenerateForVersion(conf, plugin, parsedVersion, stdOut, stdErr)
} }
return nil return nil
} }
// GenerateForVersion loops over all the executable files found for a tool and // GenerateForVersion loops over all the executable files found for a tool and
// generates a shim for each one // generates a shim for each one
func GenerateForVersion(conf config.Config, plugin plugins.Plugin, versionType, version string, stdOut io.Writer, stdErr io.Writer) error { func GenerateForVersion(conf config.Config, plugin plugins.Plugin, version toolversions.Version, stdOut io.Writer, stdErr io.Writer) error {
err := hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_reshim_%s", plugin.Name), []string{version}, stdOut, stdErr) err := hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_reshim_%s", plugin.Name), []string{toolversions.Format(version)}, stdOut, stdErr)
if err != nil { if err != nil {
return err return err
} }
executables, err := ToolExecutables(conf, plugin, versionType, version) executables, err := ToolExecutables(conf, plugin, version)
if err != nil { if err != nil {
return err return err
} }
if versionType == "path" {
version = fmt.Sprintf("path:%s", version)
}
for _, executablePath := range executables { for _, executablePath := range executables {
err := Write(conf, plugin, version, executablePath) err := Write(conf, plugin, version, executablePath)
if err != nil { if err != nil {
@ -262,7 +277,7 @@ func GenerateForVersion(conf config.Config, plugin plugins.Plugin, versionType,
} }
} }
err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_reshim_%s", plugin.Name), []string{version}, stdOut, stdErr) err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_reshim_%s", plugin.Name), []string{toolversions.Format(version)}, stdOut, stdErr)
if err != nil { if err != nil {
return err return err
} }
@ -270,7 +285,7 @@ func GenerateForVersion(conf config.Config, plugin plugins.Plugin, versionType,
} }
// Write generates a shim script and writes it to disk // Write generates a shim script and writes it to disk
func Write(conf config.Config, plugin plugins.Plugin, version, executablePath string) error { func Write(conf config.Config, plugin plugins.Plugin, version toolversions.Version, executablePath string) error {
err := ensureShimDirExists(conf) err := ensureShimDirExists(conf)
if err != nil { if err != nil {
return err return err
@ -278,7 +293,7 @@ func Write(conf config.Config, plugin plugins.Plugin, version, executablePath st
shimName := filepath.Base(executablePath) shimName := filepath.Base(executablePath)
shimPath := Path(conf, shimName) shimPath := Path(conf, shimName)
versions := []toolversions.ToolVersions{{Name: plugin.Name, Versions: []string{version}}} versions := []toolversions.ToolVersions{{Name: plugin.Name, Versions: []string{toolversions.Format(version)}}}
if _, err := os.Stat(shimPath); err == nil { if _, err := os.Stat(shimPath); err == nil {
oldVersions, err := GetToolsAndVersionsFromShimFile(shimPath) oldVersions, err := GetToolsAndVersionsFromShimFile(shimPath)
@ -307,8 +322,8 @@ 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, version toolversions.Version) (executables []string, err error) {
paths, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: versionType, Value: version}) paths, err := ExecutablePaths(conf, plugin, version)
if err != nil { if err != nil {
return []string{}, err return []string{}, err
} }

View File

@ -80,6 +80,25 @@ func TestFindExecutable(t *testing.T) {
assert.Equal(t, filepath.Base(executable), "ls") assert.Equal(t, filepath.Base(executable), "ls")
assert.NotEqual(t, executable, path) assert.NotEqual(t, executable, path)
}) })
t.Run("returns path to executable on path when path version set", func(t *testing.T) {
// write system version to version file
toolpath := filepath.Join(currentDir, ".tool-versions")
dir := installs.InstallPath(conf, plugin, toolversions.Version{Type: "version", Value: "1.1.0"})
pathVersion := fmt.Sprintf("path:%s/./", dir)
assert.Nil(t, os.WriteFile(toolpath, []byte(fmt.Sprintf("lua %s\n", pathVersion)), 0o666))
assert.Nil(t, GenerateAll(conf, &stdout, &stderr))
executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir)
assert.Equal(t, plugin, gotPlugin)
assert.Equal(t, version, pathVersion)
assert.True(t, found)
assert.Nil(t, err)
// see that it actually returns path to system ls
assert.Equal(t, filepath.Base(executable), "dummy")
assert.True(t, strings.HasPrefix(executable, dir))
})
} }
func TestGetExecutablePath(t *testing.T) { func TestGetExecutablePath(t *testing.T) {
@ -88,14 +107,14 @@ func TestGetExecutablePath(t *testing.T) {
installVersion(t, conf, plugin, version.Value) installVersion(t, conf, plugin, version.Value)
t.Run("returns path to executable", func(t *testing.T) { t.Run("returns path to executable", func(t *testing.T) {
path, err := GetExecutablePath(conf, plugin, "dummy", version.Value) path, err := GetExecutablePath(conf, plugin, "dummy", version)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, filepath.Base(path), "dummy") assert.Equal(t, filepath.Base(path), "dummy")
assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(path))), version.Value) assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(path))), version.Value)
}) })
t.Run("returns error when executable with name not found", func(t *testing.T) { t.Run("returns error when executable with name not found", func(t *testing.T) {
path, err := GetExecutablePath(conf, plugin, "foo", version.Value) path, err := GetExecutablePath(conf, plugin, "foo", version)
assert.ErrorContains(t, err, "executable not found") assert.ErrorContains(t, err, "executable not found")
assert.Equal(t, path, "") assert.Equal(t, path, "")
}) })
@ -104,7 +123,7 @@ func TestGetExecutablePath(t *testing.T) {
// Create exec-path callback // Create exec-path callback
installDummyExecPathScript(t, conf, plugin, version, "dummy") installDummyExecPathScript(t, conf, plugin, version, "dummy")
path, err := GetExecutablePath(conf, plugin, "dummy", version.Value) path, err := GetExecutablePath(conf, plugin, "dummy", version)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, filepath.Base(filepath.Dir(path)), "custom") assert.Equal(t, filepath.Base(filepath.Dir(path)), "custom")
}) })
@ -114,7 +133,7 @@ func TestRemoveAll(t *testing.T) {
version := "1.1.0" version := "1.1.0"
conf, plugin := generateConfig(t) conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version) installVersion(t, conf, plugin, version)
executables, err := ToolExecutables(conf, plugin, "version", version) executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version})
assert.Nil(t, err) assert.Nil(t, err)
stdout, stderr := buildOutputs() stdout, stderr := buildOutputs()
@ -137,7 +156,7 @@ func TestGenerateAll(t *testing.T) {
installVersion(t, conf, plugin, version) installVersion(t, conf, plugin, version)
installPlugin(t, conf, "dummy_plugin", "ruby") installPlugin(t, conf, "dummy_plugin", "ruby")
installVersion(t, conf, plugin, version2) installVersion(t, conf, plugin, version2)
executables, err := ToolExecutables(conf, plugin, "version", version) executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version})
assert.Nil(t, err) assert.Nil(t, err)
stdout, stderr := buildOutputs() stdout, stderr := buildOutputs()
@ -166,7 +185,7 @@ func TestGenerateForPluginVersions(t *testing.T) {
conf, plugin := generateConfig(t) conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version) installVersion(t, conf, plugin, version)
installVersion(t, conf, plugin, version2) installVersion(t, conf, plugin, version2)
executables, err := ToolExecutables(conf, plugin, "version", version) executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version})
assert.Nil(t, err) assert.Nil(t, err)
stdout, stderr := buildOutputs() stdout, stderr := buildOutputs()
@ -198,17 +217,17 @@ func TestGenerateForPluginVersions(t *testing.T) {
} }
func TestGenerateForVersion(t *testing.T) { func TestGenerateForVersion(t *testing.T) {
version := "1.1.0" version := toolversions.Version{Type: "version", Value: "1.1.0"}
version2 := "2.0.0" version2 := toolversions.Version{Type: "version", Value: "2.0.0"}
conf, plugin := generateConfig(t) conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version) installVersion(t, conf, plugin, version.Value)
installVersion(t, conf, plugin, version2) installVersion(t, conf, plugin, version2.Value)
executables, err := ToolExecutables(conf, plugin, "version", version) executables, err := ToolExecutables(conf, plugin, version)
assert.Nil(t, err) assert.Nil(t, err)
t.Run("generates shim script for every executable in version", func(t *testing.T) { t.Run("generates shim script for every executable in version", func(t *testing.T) {
stdout, stderr := buildOutputs() stdout, stderr := buildOutputs()
assert.Nil(t, GenerateForVersion(conf, plugin, "version", version, &stdout, &stderr)) assert.Nil(t, GenerateForVersion(conf, plugin, version, &stdout, &stderr))
// check for generated shims // check for generated shims
for _, executable := range executables { for _, executable := range executables {
@ -220,8 +239,8 @@ func TestGenerateForVersion(t *testing.T) {
t.Run("updates existing shims for every executable in version", func(t *testing.T) { t.Run("updates existing shims for every executable in version", func(t *testing.T) {
stdout, stderr := buildOutputs() stdout, stderr := buildOutputs()
assert.Nil(t, GenerateForVersion(conf, plugin, "version", version, &stdout, &stderr)) assert.Nil(t, GenerateForVersion(conf, plugin, version, &stdout, &stderr))
assert.Nil(t, GenerateForVersion(conf, plugin, "version", version2, &stdout, &stderr)) assert.Nil(t, GenerateForVersion(conf, plugin, version2, &stdout, &stderr))
// check for generated shims // check for generated shims
for _, executable := range executables { for _, executable := range executables {
@ -233,12 +252,12 @@ func TestGenerateForVersion(t *testing.T) {
} }
func TestWrite(t *testing.T) { func TestWrite(t *testing.T) {
version := "1.1.0" version := toolversions.Version{Type: "version", Value: "1.1.0"}
version2 := "2.0.0" version2 := toolversions.Version{Type: "version", Value: "2.0.0"}
conf, plugin := generateConfig(t) conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version) installVersion(t, conf, plugin, version.Value)
installVersion(t, conf, plugin, version2) installVersion(t, conf, plugin, version2.Value)
executables, err := ToolExecutables(conf, plugin, "version", version) executables, err := ToolExecutables(conf, plugin, version)
executable := executables[0] executable := executables[0]
assert.Nil(t, err) assert.Nil(t, err)
@ -298,7 +317,7 @@ func TestToolExecutables(t *testing.T) {
installVersion(t, conf, plugin, version.Value) installVersion(t, conf, plugin, version.Value)
t.Run("returns list of executables for plugin", func(t *testing.T) { t.Run("returns list of executables for plugin", func(t *testing.T) {
executables, err := ToolExecutables(conf, plugin, "version", version.Value) executables, err := ToolExecutables(conf, plugin, version)
assert.Nil(t, err) assert.Nil(t, err)
var filenames []string var filenames []string
@ -313,7 +332,7 @@ func TestToolExecutables(t *testing.T) {
t.Run("returns list of executables for version installed in arbitrary directory", func(t *testing.T) { t.Run("returns list of executables for version installed in arbitrary directory", func(t *testing.T) {
// Reference regular install by path to validate this behavior // Reference regular install by path to validate this behavior
path := installs.InstallPath(conf, plugin, version) path := installs.InstallPath(conf, plugin, version)
executables, err := ToolExecutables(conf, plugin, "path", path) executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "path", Value: path})
assert.Nil(t, err) assert.Nil(t, err)
var filenames []string var filenames []string
@ -329,7 +348,7 @@ func TestToolExecutables(t *testing.T) {
// foo is first in list returned by list-bin-paths but doesn't exist, do // foo is first in list returned by list-bin-paths but doesn't exist, do
// we still get the executables in the bin/ dir? // we still get the executables in the bin/ dir?
repotest.WritePluginCallback(plugin.Dir, "list-bin-paths", "#!/usr/bin/env bash\necho 'foo bin'") repotest.WritePluginCallback(plugin.Dir, "list-bin-paths", "#!/usr/bin/env bash\necho 'foo bin'")
executables, err := ToolExecutables(conf, plugin, "version", version.Value) executables, err := ToolExecutables(conf, plugin, version)
assert.Nil(t, err) assert.Nil(t, err)
var filenames []string var filenames []string

View File

@ -130,6 +130,26 @@ func Parse(version string) Version {
return Version{Type: "version", Value: version} return Version{Type: "version", Value: version}
} }
// ParseSlice takes a slice of strings and returns a slice of parsed versions.
func ParseSlice(versions []string) (parsedVersions []Version) {
for _, version := range versions {
parsedVersions = append(parsedVersions, Parse(version))
}
return parsedVersions
}
// Format takes a Version struct and formats it as a string
func Format(version Version) string {
switch version.Type {
case "system":
return "system"
case "path":
return fmt.Sprintf("path:%s", version.Value)
default:
return version.Value
}
}
// FormatForFS takes a versionType and version strings and generate a version // FormatForFS takes a versionType and version strings and generate a version
// string suitable for the file system // string suitable for the file system
func FormatForFS(version Version) string { func FormatForFS(version Version) string {

View File

@ -222,6 +222,59 @@ func TestParseFromCliArg(t *testing.T) {
}) })
} }
func TestParseSlice(t *testing.T) {
t.Run("returns slice of parsed tool versions", func(t *testing.T) {
versions := ParseSlice([]string{"1.2.3"})
assert.Equal(t, []Version{{Type: "version", Value: "1.2.3"}}, versions)
})
t.Run("returns empty slice when empty slice provided", func(t *testing.T) {
versions := ParseSlice([]string{})
assert.Empty(t, versions)
})
t.Run("parses special versions", func(t *testing.T) {
versions := ParseSlice([]string{"ref:foo", "system", "path:/foo/bar"})
assert.Equal(t, []Version{{Type: "ref", Value: "foo"}, {Type: "system"}, {Type: "path", Value: "/foo/bar"}}, versions)
})
}
func TestFormat(t *testing.T) {
tests := []struct {
desc string
input Version
output string
}{
{
desc: "with regular version",
input: Version{Type: "version", Value: "foobar"},
output: "foobar",
},
{
desc: "with ref version",
input: Version{Type: "ref", Value: "foobar"},
output: "foobar",
},
{
desc: "with system version",
input: Version{Type: "system", Value: "system"},
output: "system",
},
{
desc: "with system version",
input: Version{Type: "path", Value: "/foo/bar"},
output: "path:/foo/bar",
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
got := Format(tt.input)
assert.Equal(t, got, tt.output)
})
}
}
func TestFormatForFS(t *testing.T) { func TestFormatForFS(t *testing.T) {
t.Run("returns version when version type is not ref", func(t *testing.T) { t.Run("returns version when version type is not ref", func(t *testing.T) {
assert.Equal(t, FormatForFS(Version{Type: "version", Value: "foobar"}), "foobar") assert.Equal(t, FormatForFS(Version{Type: "version", Value: "foobar"}), "foobar")