feat(golang-rewrite): introduce Version struct, get some shim_exec.bats tests passing

* Get more shim_exec.bats tests passing by adding shebang lines to test scripts
* Disable shim_exec test case for scenario that is no longer possible
* Add documentation on another breaking change
* Create toolversions.Version struct and update code to use new struct
This commit is contained in:
Trevor Brown 2024-10-31 09:30:48 -04:00
parent 9d9fc698f4
commit 924eecfa6a
12 changed files with 239 additions and 143 deletions

View File

@ -299,8 +299,8 @@ func getVersionInfo(conf config.Config, plugin plugins.Plugin, currentDir string
installed := false
if found {
firstVersion := toolversion.Versions[0]
versionType, version := toolversions.Parse(firstVersion)
installed = installs.IsInstalled(conf, plugin, versionType, version)
version := toolversions.Parse(firstVersion)
installed = installs.IsInstalled(conf, plugin, version)
}
return toolversion, found, installed
}
@ -354,13 +354,41 @@ func execCommand(logger *log.Logger, command string, args []string) error {
}
executable, found, err := shims.FindExecutable(conf, command, currentDir)
if err != nil {
logger.Printf("executable not found due to reason: %s", err.Error())
shimPath := shims.Path(conf, command)
toolVersions, _ := shims.GetToolsAndVersionsFromShimFile(shimPath)
if len(toolVersions) > 0 {
if anyInstalled(conf, toolVersions) {
logger.Printf("No version is set for command %s", command)
logger.Printf("Consider adding one of the following versions in your config file at %s/.tool-versions\n", currentDir)
} else {
logger.Printf("No preset version installed for command %s", command)
for _, toolVersion := range toolVersions {
for _, version := range toolVersion.Versions {
fmt.Printf("asdf install %s %s\n", toolVersion.Name, version)
}
}
fmt.Printf("or add one of the following versions in your config file at %s/.tool-versions\n", currentDir)
}
for _, toolVersion := range toolVersions {
for _, version := range toolVersion.Versions {
fmt.Printf("%s %s", toolVersion.Name, version)
}
}
}
os.Exit(126)
return err
}
if !found {
logger.Print("executable not found")
os.Exit(126)
return fmt.Errorf("executable not found")
}
if len(args) > 1 {
@ -372,6 +400,19 @@ func execCommand(logger *log.Logger, command string, args []string) error {
return exec.Exec(executable, args, os.Environ())
}
func anyInstalled(conf config.Config, toolVersions []toolversions.ToolVersions) bool {
for _, toolVersion := range toolVersions {
for _, version := range toolVersion.Versions {
version := toolversions.Parse(version)
plugin := plugins.New(conf, toolVersion.Name)
if installs.IsInstalled(conf, plugin, version) {
return true
}
}
}
return false
}
func pluginAddCommand(_ *cli.Context, conf config.Config, logger *log.Logger, pluginName, pluginRepo string) error {
if pluginName == "" {
// Invalid arguments
@ -577,10 +618,10 @@ func installCommand(logger *log.Logger, toolName, version string) error {
return err
}
} else {
parsedVersion, query := toolversions.ParseFromCliArg(version)
parsedVersion := toolversions.ParseFromCliArg(version)
if parsedVersion == "latest" {
err = versions.InstallVersion(conf, plugin, version, query, os.Stdout, os.Stderr)
if parsedVersion.Type == "latest" {
err = versions.InstallVersion(conf, plugin, version, parsedVersion.Value, os.Stdout, os.Stderr)
} else {
err = versions.InstallOneVersion(conf, plugin, version, os.Stdout, os.Stderr)
}
@ -890,7 +931,7 @@ func uninstallCommand(logger *log.Logger, tool, version string) error {
return shims.GenerateAll(conf, os.Stdout, os.Stderr)
}
func whereCommand(logger *log.Logger, tool, version string) error {
func whereCommand(logger *log.Logger, tool, versionStr string) error {
conf, err := config.LoadConfig()
if err != nil {
logger.Printf("error loading config: %s", err)
@ -912,21 +953,29 @@ func whereCommand(logger *log.Logger, tool, version string) error {
return err
}
versionType, parsedVersion := toolversions.Parse(version)
version := toolversions.Parse(versionStr)
if version == "" {
if version.Type == "system" {
logger.Printf("System version is selected")
return errors.New("System version is selected")
}
if version.Value == "" {
// resolve version
toolversions, found, err := resolve.Version(conf, plugin, currentDir)
versions, found, err := resolve.Version(conf, plugin, currentDir)
if err != nil {
fmt.Printf("err %#+v\n", err)
return err
}
if found && len(toolversions.Versions) > 0 && installs.IsInstalled(conf, plugin, "version", toolversions.Versions[0]) {
installPath := installs.InstallPath(conf, plugin, "version", toolversions.Versions[0])
if found && len(versions.Versions) > 0 {
versionStruct := toolversions.Version{Type: "version", Value: versions.Versions[0]}
if installs.IsInstalled(conf, plugin, versionStruct) {
installPath := installs.InstallPath(conf, plugin, versionStruct)
logger.Printf("%s", installPath)
return nil
}
}
// not found
msg := fmt.Sprintf("No version is set for %s; please run `asdf <global | shell | local> %s <version>`", tool, tool)
@ -934,25 +983,20 @@ func whereCommand(logger *log.Logger, tool, version string) error {
return errors.New(msg)
}
if version == "system" {
logger.Printf("System version is selected")
return errors.New("System version is selected")
}
if !installs.IsInstalled(conf, plugin, versionType, parsedVersion) {
if !installs.IsInstalled(conf, plugin, version) {
logger.Printf("Version not installed")
return errors.New("Version not installed")
}
installPath := installs.InstallPath(conf, plugin, versionType, parsedVersion)
installPath := installs.InstallPath(conf, plugin, version)
logger.Printf("%s", installPath)
return nil
}
func reshimToolVersion(conf config.Config, tool, version string, out io.Writer, errOut io.Writer) error {
versionType, version := toolversions.Parse(version)
return shims.GenerateForVersion(conf, plugins.New(conf, tool), versionType, version, out, errOut)
func reshimToolVersion(conf config.Config, tool, versionStr string, out io.Writer, errOut io.Writer) error {
version := toolversions.Parse(versionStr)
return shims.GenerateForVersion(conf, plugins.New(conf, tool), version.Type, version.Value, out, errOut)
}
func latestForPlugin(conf config.Config, toolName, pattern string, showStatus bool) error {
@ -971,7 +1015,7 @@ func latestForPlugin(conf config.Config, toolName, pattern string, showStatus bo
}
if showStatus {
installed := installs.IsInstalled(conf, plugin, "version", latest)
installed := installs.IsInstalled(conf, plugin, toolversions.Version{Type: "version", Value: latest})
fmt.Printf("%s\t%s\t%s\n", plugin.Name, latest, installedStatus(installed))
} else {
fmt.Printf("%s\n", latest)

View File

@ -43,6 +43,19 @@ not an executable. The new rewrite removes all shell code from asdf, and it is
now a binary rather than a shell function, so setting environment variables
directly in the shell is no longer possible.
### Executables Shims Resolve to Must Runnable by `syscall.Exec`
The most obvious example of this breaking change are scripts that lack a proper
shebang line. asdf 0.14.1 and older were implemented in Bash, so as long it was
an executable that could be executed with Bash it would run. This mean that
scripts lacking a shebang could still be run by `asdf exec`. With asdf 0.15.x
implemented in Go we now invoke executables via Go's `syscall.Exec` function,
which cannot handle scripts lacking a shebang.
In practice this isn't much of a problem. Most shell scripts DO contain a
shebang line. If a tool managed by asdf provides scripts that don't have a
shebang line one will need to be added to them.
## Installation
Installation of version 0.15.0 is much simpler than previous versions of asdf. It's just three steps:

View File

@ -80,9 +80,9 @@ func writePluginHelp(conf config.Config, toolName, toolVersion string, writer io
}
if toolVersion != "" {
versionType, version := toolversions.Parse(toolVersion)
env["ASDF_INSTALL_VERSION"] = version
env["ASDF_INSTALL_TYPE"] = versionType
version := toolversions.Parse(toolVersion)
env["ASDF_INSTALL_VERSION"] = version.Value
env["ASDF_INSTALL_TYPE"] = version.Type
}
if err := plugin.Exists(); err != nil {

View File

@ -38,26 +38,26 @@ func Installed(conf config.Config, plugin plugins.Plugin) (versions []string, er
}
// InstallPath returns the path to a tool installation
func InstallPath(conf config.Config, plugin plugins.Plugin, versionType, version string) string {
if versionType == "path" {
return version
func InstallPath(conf config.Config, plugin plugins.Plugin, version toolversions.Version) string {
if version.Type == "path" {
return version.Value
}
return filepath.Join(data.InstallDirectory(conf.DataDir, plugin.Name), toolversions.FormatForFS(versionType, version))
return filepath.Join(data.InstallDirectory(conf.DataDir, plugin.Name), toolversions.FormatForFS(version))
}
// DownloadPath returns the download path for a particular plugin and version
func DownloadPath(conf config.Config, plugin plugins.Plugin, versionType, version string) string {
if versionType == "path" {
func DownloadPath(conf config.Config, plugin plugins.Plugin, version toolversions.Version) string {
if version.Type == "path" {
return ""
}
return filepath.Join(data.DownloadDirectory(conf.DataDir, plugin.Name), toolversions.FormatForFS(versionType, version))
return filepath.Join(data.DownloadDirectory(conf.DataDir, plugin.Name), toolversions.FormatForFS(version))
}
// IsInstalled checks if a specific version of a tool is installed
func IsInstalled(conf config.Config, plugin plugins.Plugin, versionType, version string) bool {
installDir := InstallPath(conf, plugin, versionType, version)
func IsInstalled(conf config.Config, plugin plugins.Plugin, version toolversions.Version) bool {
installDir := InstallPath(conf, plugin, version)
// Check if version already installed
_, err := os.Stat(installDir)

View File

@ -8,6 +8,7 @@ import (
"asdf/internal/config"
"asdf/internal/installtest"
"asdf/internal/plugins"
"asdf/internal/toolversions"
"asdf/repotest"
"github.com/stretchr/testify/assert"
@ -19,12 +20,14 @@ func TestDownloadPath(t *testing.T) {
conf, plugin := generateConfig(t)
t.Run("returns empty string when given path version", func(t *testing.T) {
path := DownloadPath(conf, plugin, "path", "foo/bar")
version := toolversions.Version{Type: "path", Value: "foo/bar"}
path := DownloadPath(conf, plugin, version)
assert.Empty(t, path)
})
t.Run("returns empty string when given path version", func(t *testing.T) {
path := DownloadPath(conf, plugin, "version", "1.2.3")
version := toolversions.Version{Type: "version", Value: "1.2.3"}
path := DownloadPath(conf, plugin, version)
assert.Equal(t, path, filepath.Join(conf.DataDir, "downloads", "lua", "1.2.3"))
})
}
@ -33,12 +36,14 @@ func TestInstallPath(t *testing.T) {
conf, plugin := generateConfig(t)
t.Run("returns empty string when given path version", func(t *testing.T) {
path := InstallPath(conf, plugin, "path", "foo/bar")
version := toolversions.Version{Type: "path", Value: "foo/bar"}
path := InstallPath(conf, plugin, version)
assert.Equal(t, path, "foo/bar")
})
t.Run("returns install path when given regular version as version", func(t *testing.T) {
path := InstallPath(conf, plugin, "version", "1.2.3")
version := toolversions.Version{Type: "version", Value: "1.2.3"}
path := InstallPath(conf, plugin, version)
assert.Equal(t, path, filepath.Join(conf.DataDir, "installs", "lua", "1.2.3"))
})
}
@ -66,10 +71,12 @@ func TestIsInstalled(t *testing.T) {
installVersion(t, conf, plugin, "1.0.0")
t.Run("returns false when not installed", func(t *testing.T) {
assert.False(t, IsInstalled(conf, plugin, "version", "4.0.0"))
version := toolversions.Version{Type: "version", Value: "4.0.0"}
assert.False(t, IsInstalled(conf, plugin, version))
})
t.Run("returns true when installed", func(t *testing.T) {
assert.True(t, IsInstalled(conf, plugin, "version", "1.0.0"))
version := toolversions.Version{Type: "version", Value: "1.0.0"}
assert.True(t, IsInstalled(conf, plugin, version))
})
}
@ -87,9 +94,10 @@ func generateConfig(t *testing.T) (config.Config, plugins.Plugin) {
return conf, plugins.New(conf, testPluginName)
}
func mockInstall(t *testing.T, conf config.Config, plugin plugins.Plugin, version string) {
func mockInstall(t *testing.T, conf config.Config, plugin plugins.Plugin, versionStr string) {
t.Helper()
path := InstallPath(conf, plugin, "version", version)
version := toolversions.Version{Type: "version", Value: versionStr}
path := InstallPath(conf, plugin, version)
err := os.MkdirAll(path, os.ModePerm)
assert.Nil(t, err)
}

View File

@ -173,7 +173,7 @@ func getCustomExecutablePath(conf config.Config, plugin plugins.Plugin, shimName
var stdOut strings.Builder
var stdErr strings.Builder
installPath := installs.InstallPath(conf, plugin, "version", version)
installPath := installs.InstallPath(conf, plugin, toolversions.Version{Type: "version", Value: version})
env := map[string]string{"ASDF_INSTALL_TYPE": "version"}
err := plugin.RunCallback("exec-path", []string{installPath, shimName}, env, &stdOut, &stdErr)
@ -303,7 +303,7 @@ func ToolExecutables(conf config.Config, plugin plugins.Plugin, versionType, ver
return executables, err
}
installPath := installs.InstallPath(conf, plugin, versionType, version)
installPath := installs.InstallPath(conf, plugin, toolversions.Version{Type: versionType, Value: version})
paths := dirsToPaths(dirs, installPath)
for _, path := range paths {

View File

@ -12,6 +12,7 @@ import (
"asdf/internal/installs"
"asdf/internal/installtest"
"asdf/internal/plugins"
"asdf/internal/toolversions"
"asdf/repotest"
"github.com/stretchr/testify/assert"
@ -56,7 +57,8 @@ func TestFindExecutable(t *testing.T) {
t.Run("returns string containing path to system executable when system version set", func(t *testing.T) {
// Create dummy `ls` executable
path := filepath.Join(installs.InstallPath(conf, plugin, "version", version), "bin", "ls")
versionStruct := toolversions.Version{Type: "version", Value: version}
path := filepath.Join(installs.InstallPath(conf, plugin, versionStruct), "bin", "ls")
assert.Nil(t, os.WriteFile(path, []byte("echo 'I'm ls'"), 0o777))
// write system version to version file
@ -75,19 +77,19 @@ func TestFindExecutable(t *testing.T) {
}
func TestGetExecutablePath(t *testing.T) {
version := "1.1.0"
version := toolversions.Version{Type: "version", Value: "1.1.0"}
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version)
installVersion(t, conf, plugin, version.Value)
t.Run("returns path to executable", func(t *testing.T) {
path, err := GetExecutablePath(conf, plugin, "dummy", version)
path, err := GetExecutablePath(conf, plugin, "dummy", version.Value)
assert.Nil(t, err)
assert.Equal(t, filepath.Base(path), "dummy")
assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(path))), version)
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) {
path, err := GetExecutablePath(conf, plugin, "foo", version)
path, err := GetExecutablePath(conf, plugin, "foo", version.Value)
assert.ErrorContains(t, err, "executable not found")
assert.Equal(t, path, "")
})
@ -96,7 +98,7 @@ func TestGetExecutablePath(t *testing.T) {
// Create exec-path callback
installDummyExecPathScript(t, conf, plugin, version, "dummy")
path, err := GetExecutablePath(conf, plugin, "dummy", version)
path, err := GetExecutablePath(conf, plugin, "dummy", version.Value)
assert.Nil(t, err)
assert.Equal(t, filepath.Base(filepath.Dir(path)), "custom")
})
@ -285,12 +287,12 @@ func TestWrite(t *testing.T) {
}
func TestToolExecutables(t *testing.T) {
version := "1.1.0"
version := toolversions.Version{Type: "version", Value: "1.1.0"}
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version)
installVersion(t, conf, plugin, version.Value)
t.Run("returns list of executables for plugin", func(t *testing.T) {
executables, err := ToolExecutables(conf, plugin, "version", version)
executables, err := ToolExecutables(conf, plugin, "version", version.Value)
assert.Nil(t, err)
var filenames []string
@ -304,7 +306,7 @@ func TestToolExecutables(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
path := installs.InstallPath(conf, plugin, "version", version)
path := installs.InstallPath(conf, plugin, version)
executables, err := ToolExecutables(conf, plugin, "path", path)
assert.Nil(t, err)
@ -357,14 +359,14 @@ func generateConfig(t *testing.T) (config.Config, plugins.Plugin) {
return conf, installPlugin(t, conf, "dummy_plugin", testPluginName)
}
func installDummyExecPathScript(t *testing.T, conf config.Config, plugin plugins.Plugin, version, name string) {
func installDummyExecPathScript(t *testing.T, conf config.Config, plugin plugins.Plugin, version toolversions.Version, name string) {
t.Helper()
execPath := filepath.Join(plugin.Dir, "bin", "exec-path")
contents := fmt.Sprintf("#!/usr/bin/env bash\necho 'bin/custom/%s'", name)
err := os.WriteFile(execPath, []byte(contents), 0o777)
assert.Nil(t, err)
installPath := installs.InstallPath(conf, plugin, "version", version)
installPath := installs.InstallPath(conf, plugin, version)
err = os.MkdirAll(filepath.Join(installPath, "bin", "custom"), 0o777)
assert.Nil(t, err)

View File

@ -10,6 +10,12 @@ import (
"strings"
)
// Version struct represents a single version in asdf.
type Version struct {
Type string // Must be one of: version, ref, path, system, latest
Value string // Any string
}
// ToolVersions represents a tool along with versions specified for it
type ToolVersions struct {
Name string
@ -86,49 +92,52 @@ func Unique(versions []ToolVersions) (uniques []ToolVersions) {
// ParseFromCliArg parses a string that is passed in as an argument to one of
// the asdf subcommands. Some subcommands allow the special version `latest` to
// be used, with an optional filter string.
func ParseFromCliArg(version string) (string, string) {
func ParseFromCliArg(version string) Version {
segments := strings.Split(version, ":")
if len(segments) > 0 && segments[0] == "latest" {
if len(segments) > 1 {
// Must be latest with filter
return "latest", segments[1]
return Version{Type: "latest", Value: segments[1]}
}
return "latest", ""
return Version{Type: "latest", Value: ""}
}
return Parse(version)
}
// Parse parses a version string into versionType and version components
func Parse(version string) (string, string) {
func Parse(version string) Version {
segments := strings.Split(version, ":")
if len(segments) >= 1 {
remainder := strings.Join(segments[1:], ":")
switch segments[0] {
case "ref":
return "ref", remainder
return Version{Type: "ref", Value: remainder}
case "path":
// This is for people who have the local source already compiled
// Like those who work on the language, etc
// We'll allow specifying path:/foo/bar/project in .tool-versions
// And then use the binaries there
return "path", remainder
return Version{Type: "path", Value: remainder}
default:
return "version", version
}
}
return "version", version
if version == "system" {
return Version{Type: "system"}
}
return Version{Type: "version", Value: version}
}
// FormatForFS takes a versionType and version strings and generate a version
// string suitable for the file system
func FormatForFS(versionType, version string) string {
switch versionType {
func FormatForFS(version Version) string {
switch version.Type {
case "ref":
return fmt.Sprintf("ref-%s", version)
return fmt.Sprintf("ref-%s", version.Value)
default:
return version
return version.Value
}
}

View File

@ -159,63 +159,75 @@ func TestgetAllToolsAndVersionsInContent(t *testing.T) {
}
func TestParse(t *testing.T) {
t.Run("returns 'version', and unmodified version when passed semantic version", func(t *testing.T) {
versionType, version := Parse("1.2.3")
assert.Equal(t, versionType, "version")
assert.Equal(t, version, "1.2.3")
t.Run("when passed version string returns struct with type of 'version' and version as value", func(t *testing.T) {
version := Parse("1.2.3")
assert.Equal(t, version.Type, "version")
assert.Equal(t, version.Value, "1.2.3")
})
t.Run("returns 'ref' and reference version when passed a ref version", func(t *testing.T) {
versionType, version := Parse("ref:abc123")
assert.Equal(t, versionType, "ref")
assert.Equal(t, version, "abc123")
t.Run("when passed ref and version returns struct with type of 'ref' and version as value", func(t *testing.T) {
version := Parse("ref:abc123")
assert.Equal(t, version.Type, "ref")
assert.Equal(t, version.Value, "abc123")
})
t.Run("returns 'ref' and empty string when passed 'ref:'", func(t *testing.T) {
versionType, version := Parse("ref:")
assert.Equal(t, versionType, "ref")
assert.Equal(t, version, "")
t.Run("when passed 'ref:' returns struct with type of 'ref' and empty value", func(t *testing.T) {
version := Parse("ref:")
assert.Equal(t, version.Type, "ref")
assert.Equal(t, version.Value, "")
})
t.Run("when passed 'system' returns struct with type of 'system'", func(t *testing.T) {
version := Parse("system")
assert.Equal(t, version.Type, "system")
assert.Equal(t, version.Value, "")
})
}
func TestParseFromCliArg(t *testing.T) {
t.Run("returns 'latest' as version type when passed string 'latest'", func(t *testing.T) {
versionType, version := ParseFromCliArg("latest")
assert.Equal(t, versionType, "latest")
assert.Equal(t, version, "")
t.Run("when passed 'latest' returns struct with type of 'latest'", func(t *testing.T) {
version := ParseFromCliArg("latest")
assert.Equal(t, version.Type, "latest")
assert.Equal(t, version.Value, "")
})
t.Run("returns 'latest' and unmodified filter string when passed a latest version", func(t *testing.T) {
versionType, version := ParseFromCliArg("latest:1.2")
assert.Equal(t, versionType, "latest")
assert.Equal(t, version, "1.2")
t.Run("when passed latest with filter returns struct with type of 'latest' and unmodified filter string as value", func(t *testing.T) {
version := ParseFromCliArg("latest:1.2")
assert.Equal(t, version.Type, "latest")
assert.Equal(t, version.Value, "1.2")
})
t.Run("returns 'version', and unmodified version when passed semantic version", func(t *testing.T) {
versionType, version := ParseFromCliArg("1.2.3")
assert.Equal(t, versionType, "version")
assert.Equal(t, version, "1.2.3")
t.Run("when passed version string returns struct with type of 'version' and version as value", func(t *testing.T) {
version := ParseFromCliArg("1.2.3")
assert.Equal(t, version.Type, "version")
assert.Equal(t, version.Value, "1.2.3")
})
t.Run("returns 'ref' and reference version when passed a ref version", func(t *testing.T) {
versionType, version := ParseFromCliArg("ref:abc123")
assert.Equal(t, versionType, "ref")
assert.Equal(t, version, "abc123")
t.Run("when passed ref and version returns struct with type of 'ref' and version as value", func(t *testing.T) {
version := ParseFromCliArg("ref:abc123")
assert.Equal(t, version.Type, "ref")
assert.Equal(t, version.Value, "abc123")
})
t.Run("returns 'ref' and empty string when passed 'ref:'", func(t *testing.T) {
versionType, version := ParseFromCliArg("ref:")
assert.Equal(t, versionType, "ref")
assert.Equal(t, version, "")
t.Run("when passed 'ref:' returns struct with type of 'ref' and empty value", func(t *testing.T) {
version := ParseFromCliArg("ref:")
assert.Equal(t, version.Type, "ref")
assert.Equal(t, version.Value, "")
})
t.Run("when passed 'system' returns struct with type of 'system'", func(t *testing.T) {
version := ParseFromCliArg("system")
assert.Equal(t, version.Type, "system")
assert.Equal(t, version.Value, "")
})
}
func TestFormatForFS(t *testing.T) {
t.Run("returns version when version type is not ref", func(t *testing.T) {
assert.Equal(t, FormatForFS("version", "foobar"), "foobar")
assert.Equal(t, FormatForFS(Version{Type: "version", Value: "foobar"}), "foobar")
})
t.Run("returns version prefixed with 'ref-' when version type is ref", func(t *testing.T) {
assert.Equal(t, FormatForFS("ref", "foobar"), "ref-foobar")
assert.Equal(t, FormatForFS(Version{Type: "ref", Value: "foobar"}), "ref-foobar")
})
}

View File

@ -122,31 +122,31 @@ func InstallVersion(conf config.Config, plugin plugins.Plugin, version string, p
}
// InstallOneVersion installs a specific version of a specific tool
func InstallOneVersion(conf config.Config, plugin plugins.Plugin, version string, stdOut io.Writer, stdErr io.Writer) error {
func InstallOneVersion(conf config.Config, plugin plugins.Plugin, versionStr string, stdOut io.Writer, stdErr io.Writer) error {
err := plugin.Exists()
if err != nil {
return err
}
if version == systemVersion {
return UninstallableVersionError{versionType: "system"}
if versionStr == systemVersion {
return UninstallableVersionError{versionType: systemVersion}
}
versionType, version := toolversions.Parse(version)
version := toolversions.Parse(versionStr)
if versionType == "path" {
if version.Type == "path" {
return UninstallableVersionError{versionType: "path"}
}
downloadDir := installs.DownloadPath(conf, plugin, versionType, version)
installDir := installs.InstallPath(conf, plugin, versionType, version)
downloadDir := installs.DownloadPath(conf, plugin, version)
installDir := installs.InstallPath(conf, plugin, version)
if installs.IsInstalled(conf, plugin, versionType, version) {
if installs.IsInstalled(conf, plugin, version) {
return fmt.Errorf("version %s of %s is already installed", version, plugin.Name)
}
env := map[string]string{
"ASDF_INSTALL_TYPE": versionType,
"ASDF_INSTALL_VERSION": version,
"ASDF_INSTALL_TYPE": version.Type,
"ASDF_INSTALL_VERSION": version.Value,
"ASDF_INSTALL_PATH": installDir,
"ASDF_DOWNLOAD_PATH": downloadDir,
"ASDF_CONCURRENCY": asdfConcurrency(conf),
@ -157,7 +157,7 @@ func InstallOneVersion(conf config.Config, plugin plugins.Plugin, version string
return fmt.Errorf("unable to create download dir: %w", err)
}
err = hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_download_%s", plugin.Name), []string{version}, stdOut, stdErr)
err = hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_download_%s", plugin.Name), []string{version.Value}, stdOut, stdErr)
if err != nil {
return fmt.Errorf("failed to run pre-download hook: %w", err)
}
@ -167,7 +167,7 @@ func InstallOneVersion(conf config.Config, plugin plugins.Plugin, version string
return fmt.Errorf("failed to run download callback: %w", err)
}
err = hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_install_%s", plugin.Name), []string{version}, stdOut, stdErr)
err = hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_install_%s", plugin.Name), []string{version.Value}, stdOut, stdErr)
if err != nil {
return fmt.Errorf("failed to run pre-install hook: %w", err)
}
@ -188,7 +188,7 @@ func InstallOneVersion(conf config.Config, plugin plugins.Plugin, version string
return fmt.Errorf("unable to generate shims post-install: %w", err)
}
err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_install_%s", plugin.Name), []string{version}, stdOut, stdErr)
err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_install_%s", plugin.Name), []string{version.Value}, stdOut, stdErr)
if err != nil {
return fmt.Errorf("failed to run post-install hook: %w", err)
}
@ -278,26 +278,26 @@ func AllVersionsFiltered(plugin plugins.Plugin, query string) (versions []string
// post-uninstall hooks if set, and runs the plugin's uninstall callback if
// defined.
func Uninstall(conf config.Config, plugin plugins.Plugin, rawVersion string, stdout, stderr io.Writer) error {
versionType, version := toolversions.ParseFromCliArg(rawVersion)
version := toolversions.ParseFromCliArg(rawVersion)
if versionType == "latest" {
if version.Type == "latest" {
return errors.New("'latest' is a special version value that cannot be used for uninstall command")
}
if !installs.IsInstalled(conf, plugin, versionType, version) {
if !installs.IsInstalled(conf, plugin, version) {
return errors.New("No such version")
}
err := hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_uninstall_%s", plugin.Name), []string{version}, stdout, stderr)
err := hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_uninstall_%s", plugin.Name), []string{version.Value}, stdout, stderr)
if err != nil {
return err
}
// invoke uninstall callback if available
installDir := installs.InstallPath(conf, plugin, versionType, version)
installDir := installs.InstallPath(conf, plugin, version)
env := map[string]string{
"ASDF_INSTALL_TYPE": versionType,
"ASDF_INSTALL_VERSION": version,
"ASDF_INSTALL_TYPE": version.Type,
"ASDF_INSTALL_VERSION": version.Value,
"ASDF_INSTALL_PATH": installDir,
}
err = plugin.RunCallback("uninstall", []string{}, env, stdout, stderr)
@ -310,7 +310,7 @@ func Uninstall(conf config.Config, plugin plugins.Plugin, rawVersion string, std
return err
}
err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_uninstall_%s", plugin.Name), []string{version}, stdout, stderr)
err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_uninstall_%s", plugin.Name), []string{version.Value}, stdout, stderr)
if err != nil {
return err
}

View File

@ -17,11 +17,13 @@ echo "$ASDF_INSTALL_VERSION" >"$ASDF_INSTALL_PATH/version"
# create the dummy executable
mkdir -p "$ASDF_INSTALL_PATH/bin"
cat <<EOF >"$ASDF_INSTALL_PATH/bin/dummy"
#!/usr/bin/env bash
echo This is Dummy ${ASDF_INSTALL_VERSION}! \$2 \$1
EOF
chmod +x "$ASDF_INSTALL_PATH/bin/dummy"
mkdir -p "$ASDF_INSTALL_PATH/bin/subdir"
cat <<EOF >"$ASDF_INSTALL_PATH/bin/subdir/other_bin"
#!/usr/bin/env bash
echo This is Other Bin ${ASDF_INSTALL_VERSION}! \$2 \$1
EOF
chmod +x "$ASDF_INSTALL_PATH/bin/subdir/other_bin"

View File

@ -61,7 +61,8 @@ teardown() {
echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions"
run asdf install
echo "tr [:lower:] [:upper:]" >"$ASDF_DIR/installs/dummy/1.0/bin/upper"
echo "#!/usr/bin/env bash
tr [:lower:] [:upper:]" >"$ASDF_DIR/installs/dummy/1.0/bin/upper"
chmod +x "$ASDF_DIR/installs/dummy/1.0/bin/upper"
run asdf reshim dummy 1.0
@ -114,20 +115,22 @@ teardown() {
echo "$output" | grep -q "mummy 3.0" 2>/dev/null
}
@test "shim exec should suggest to install missing version" {
run asdf install dummy 1.0
# No longer possible for shim to specify version that isn't installed because
# shims are re-generated after every install and uninstall.
#@test "shim exec should suggest to install missing version" {
# run asdf install dummy 1.0
echo "dummy 2.0.0 1.3" >"$PROJECT_DIR/.tool-versions"
# echo "dummy 2.0.0 1.3" >"$PROJECT_DIR/.tool-versions"
run "$ASDF_DIR/shims/dummy" world hello
[ "$status" -eq 126 ]
echo "$output" | grep -q "No preset version installed for command dummy" 2>/dev/null
echo "$output" | grep -q "Please install a version by running one of the following:" 2>/dev/null
echo "$output" | grep -q "asdf install dummy 2.0.0" 2>/dev/null
echo "$output" | grep -q "asdf install dummy 1.3" 2>/dev/null
echo "$output" | grep -q "or add one of the following versions in your config file at $PROJECT_DIR/.tool-versions" 2>/dev/null
echo "$output" | grep -q "dummy 1.0" 2>/dev/null
}
# run "$ASDF_DIR/shims/dummy" world hello
# [ "$status" -eq 126 ]
# echo "$output" | grep -q "No preset version installed for command dummy" 2>/dev/null
# echo "$output" | grep -q "Please install a version by running one of the following:" 2>/dev/null
# echo "$output" | grep -q "asdf install dummy 2.0.0" 2>/dev/null
# echo "$output" | grep -q "asdf install dummy 1.3" 2>/dev/null
# echo "$output" | grep -q "or add one of the following versions in your config file at $PROJECT_DIR/.tool-versions" 2>/dev/null
# echo "$output" | grep -q "dummy 1.0" 2>/dev/null
#}
@test "shim exec should execute first plugin that is installed and set" {
run asdf install dummy 2.0.0
@ -199,7 +202,8 @@ teardown() {
echo "dummy system" >"$PROJECT_DIR/.tool-versions"
mkdir "$PROJECT_DIR/foo/"
echo "echo System" >"$PROJECT_DIR/foo/dummy"
echo "#!/usr/bin/env bash
echo System" >"$PROJECT_DIR/foo/dummy"
chmod +x "$PROJECT_DIR/foo/dummy"
run env "PATH=$PATH:$PROJECT_DIR/foo" "$ASDF_DIR/shims/dummy" hello
@ -214,7 +218,8 @@ teardown() {
CUSTOM_DUMMY_PATH="$PROJECT_DIR/foo"
CUSTOM_DUMMY_BIN_PATH="$CUSTOM_DUMMY_PATH/bin"
mkdir -p "$CUSTOM_DUMMY_BIN_PATH"
echo "echo System" >"$CUSTOM_DUMMY_BIN_PATH/dummy"
echo "#!/usr/bin/env bash
echo System" >"$CUSTOM_DUMMY_BIN_PATH/dummy"
chmod +x "$CUSTOM_DUMMY_BIN_PATH/dummy"
echo "dummy path:$CUSTOM_DUMMY_PATH" >"$PROJECT_DIR/.tool-versions"
@ -230,7 +235,8 @@ teardown() {
echo "dummy 2.0.0" >>"$PROJECT_DIR/.tool-versions"
mkdir "$PROJECT_DIR/foo/"
echo "echo System" >"$PROJECT_DIR/foo/dummy"
echo "#!/usr/bin/env bash
echo System" >"$PROJECT_DIR/foo/dummy"
chmod +x "$PROJECT_DIR/foo/dummy"
run env "PATH=$PATH:$PROJECT_DIR/foo" "$ASDF_DIR/shims/dummy" hello