mirror of
https://github.com/asdf-vm/asdf.git
synced 2024-12-24 12:25:28 -07:00
Merge pull request #52 from asdf-vm/tb/version-resolution
feat(golang-rewrite): create internal/resolve package
This commit is contained in:
commit
01ec98eef4
84
internal/resolve/resolve.go
Normal file
84
internal/resolve/resolve.go
Normal file
@ -0,0 +1,84 @@
|
||||
// Package resolve contains functions for resolving a tool version in a given
|
||||
// directory. This is a core feature of asdf as asdf must be able to resolve a
|
||||
// tool version in any directory if set.
|
||||
package resolve
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"asdf/config"
|
||||
"asdf/internal/toolversions"
|
||||
"asdf/plugins"
|
||||
)
|
||||
|
||||
// ToolVersions represents a tool along with versions specified for it
|
||||
type ToolVersions struct {
|
||||
Name string
|
||||
Versions []string
|
||||
Source string
|
||||
}
|
||||
|
||||
func findVersionsInDir(conf config.Config, plugin plugins.Plugin, directory string) (versions []string, found bool, err error) {
|
||||
filename := conf.DefaultToolVersionsFilename
|
||||
filepath := path.Join(directory, filename)
|
||||
|
||||
if _, err = os.Stat(filepath); err == nil {
|
||||
versions, found, err := toolversions.FindToolVersions(filepath, plugin.Name)
|
||||
if found || err != nil {
|
||||
return versions, found, err
|
||||
}
|
||||
}
|
||||
|
||||
return versions, found, nil
|
||||
}
|
||||
|
||||
// findVersionsInEnv returns the version from the environment if present
|
||||
func findVersionsInEnv(pluginName string) ([]string, bool) {
|
||||
envVariableName := "ASDF_" + strings.ToUpper(pluginName) + "_VERSION"
|
||||
versionString := os.Getenv(envVariableName)
|
||||
if versionString == "" {
|
||||
return []string{}, false
|
||||
}
|
||||
return parseVersion(versionString), true
|
||||
}
|
||||
|
||||
// findVersionsInLegacyFile looks up a legacy version in the given directory if
|
||||
// the specified plugin has a list-legacy-filenames callback script. If the
|
||||
// callback script exists asdf will look for files with the given name in the
|
||||
// current and extract the version from them.
|
||||
func findVersionsInLegacyFile(plugin plugins.Plugin, directory string) (versions []string, found bool, err error) {
|
||||
var legacyFileNames []string
|
||||
|
||||
legacyFileNames, err = plugin.LegacyFilenames()
|
||||
if err != nil {
|
||||
return []string{}, false, err
|
||||
}
|
||||
|
||||
for _, filename := range legacyFileNames {
|
||||
filepath := path.Join(directory, filename)
|
||||
if _, err := os.Stat(filepath); err == nil {
|
||||
versions, err := plugin.ParseLegacyVersionFile(filepath)
|
||||
|
||||
if len(versions) == 0 || (len(versions) == 1 && versions[0] == "") {
|
||||
return nil, false, nil
|
||||
}
|
||||
return versions, err == nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return versions, found, err
|
||||
}
|
||||
|
||||
// parseVersion parses the raw version
|
||||
func parseVersion(rawVersions string) []string {
|
||||
var versions []string
|
||||
for _, version := range strings.Split(rawVersions, " ") {
|
||||
version = strings.TrimSpace(version)
|
||||
if len(version) > 0 {
|
||||
versions = append(versions, version)
|
||||
}
|
||||
}
|
||||
return versions
|
||||
}
|
134
internal/resolve/resolve_test.go
Normal file
134
internal/resolve/resolve_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
package resolve
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"asdf/config"
|
||||
"asdf/plugins"
|
||||
"asdf/repotest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFindVersionsInDir(t *testing.T) {
|
||||
testDataDir := t.TempDir()
|
||||
conf := config.Config{DataDir: testDataDir, DefaultToolVersionsFilename: ".tool-versions"}
|
||||
_, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, "lua")
|
||||
assert.Nil(t, err)
|
||||
plugin := plugins.New(conf, "lua")
|
||||
|
||||
t.Run("when no versions set returns found false", func(t *testing.T) {
|
||||
currentDir := t.TempDir()
|
||||
|
||||
versions, found, err := findVersionsInDir(conf, plugin, currentDir)
|
||||
|
||||
assert.Empty(t, versions)
|
||||
assert.False(t, found)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("when version is set returns found true and version", func(t *testing.T) {
|
||||
currentDir := t.TempDir()
|
||||
|
||||
data := []byte("lua 1.2.3")
|
||||
err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)
|
||||
|
||||
versions, found, err := findVersionsInDir(conf, plugin, currentDir)
|
||||
|
||||
assert.Equal(t, versions, []string{"1.2.3"})
|
||||
assert.True(t, found)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("when multiple versions present in .tool-versions returns found true and versions", func(t *testing.T) {
|
||||
currentDir := t.TempDir()
|
||||
|
||||
data := []byte("lua 1.2.3 2.3.4")
|
||||
err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)
|
||||
|
||||
versions, found, err := findVersionsInDir(conf, plugin, currentDir)
|
||||
|
||||
assert.Equal(t, versions, []string{"1.2.3", "2.3.4"})
|
||||
assert.True(t, found)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("when DefaultToolVersionsFilename is set reads from file with that name if exists", func(t *testing.T) {
|
||||
conf := config.Config{DataDir: testDataDir, DefaultToolVersionsFilename: "custom-file"}
|
||||
currentDir := t.TempDir()
|
||||
|
||||
data := []byte("lua 1.2.3 2.3.4")
|
||||
err = os.WriteFile(filepath.Join(currentDir, "custom-file"), data, 0o666)
|
||||
|
||||
versions, found, err := findVersionsInDir(conf, plugin, currentDir)
|
||||
|
||||
assert.Equal(t, versions, []string{"1.2.3", "2.3.4"})
|
||||
assert.True(t, found)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindVersionsLegacyFiles(t *testing.T) {
|
||||
testDataDir := t.TempDir()
|
||||
conf := config.Config{DataDir: testDataDir}
|
||||
_, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, "lua")
|
||||
assert.Nil(t, err)
|
||||
plugin := plugins.New(conf, "lua")
|
||||
|
||||
t.Run("when given tool that lacks list-legacy-filenames callback returns empty versions list", func(t *testing.T) {
|
||||
pluginName := "foobar"
|
||||
_, err := repotest.InstallPlugin("dummy_plugin_no_download", conf.DataDir, pluginName)
|
||||
assert.Nil(t, err)
|
||||
plugin := plugins.New(conf, pluginName)
|
||||
versions, found, err := findVersionsInLegacyFile(plugin, t.TempDir())
|
||||
assert.Empty(t, versions)
|
||||
assert.False(t, found)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("when given tool that has a list-legacy-filenames callback but file not found returns empty versions list", func(t *testing.T) {
|
||||
versions, found, err := findVersionsInLegacyFile(plugin, t.TempDir())
|
||||
assert.Empty(t, versions)
|
||||
assert.False(t, found)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("when given tool that has a list-legacy-filenames callback and file found returns populated versions list", func(t *testing.T) {
|
||||
// write legacy version file
|
||||
currentDir := t.TempDir()
|
||||
data := []byte("1.2.3")
|
||||
err = os.WriteFile(filepath.Join(currentDir, ".dummy-version"), data, 0o666)
|
||||
assert.Nil(t, err)
|
||||
|
||||
versions, found, err := findVersionsInLegacyFile(plugin, currentDir)
|
||||
assert.Equal(t, versions, []string{"1.2.3"})
|
||||
assert.True(t, found)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindVersionsInEnv(t *testing.T) {
|
||||
t.Run("when env variable isn't set returns empty list of versions", func(t *testing.T) {
|
||||
versions, found := findVersionsInEnv("non-existent")
|
||||
assert.False(t, found)
|
||||
assert.Empty(t, versions)
|
||||
})
|
||||
|
||||
t.Run("when env variable is set returns version", func(t *testing.T) {
|
||||
os.Setenv("ASDF_LUA_VERSION", "5.4.5")
|
||||
versions, found := findVersionsInEnv("lua")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, versions, []string{"5.4.5"})
|
||||
os.Unsetenv("ASDF_LUA_VERSION")
|
||||
})
|
||||
|
||||
t.Run("when env variable is set to multiple versions", func(t *testing.T) {
|
||||
os.Setenv("ASDF_LUA_VERSION", "5.4.5 5.4.6")
|
||||
versions, found := findVersionsInEnv("lua")
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, versions, []string{"5.4.5", "5.4.6"})
|
||||
os.Unsetenv("ASDF_LUA_VERSION")
|
||||
})
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"asdf/config"
|
||||
"asdf/execute"
|
||||
@ -68,6 +69,63 @@ func New(config config.Config, name string) Plugin {
|
||||
return Plugin{Dir: dir, Name: name}
|
||||
}
|
||||
|
||||
// LegacyFilenames returns a slice of filenames if the plugin contains the
|
||||
// list-legacy-filenames callback.
|
||||
func (p Plugin) LegacyFilenames() (filenames []string, err error) {
|
||||
var stdOut strings.Builder
|
||||
var stdErr strings.Builder
|
||||
err = p.RunCallback("list-legacy-filenames", []string{}, map[string]string{}, &stdOut, &stdErr)
|
||||
if err != nil {
|
||||
_, ok := err.(NoCallbackError)
|
||||
if ok {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
for _, filename := range strings.Split(stdOut.String(), " ") {
|
||||
filenames = append(filenames, strings.TrimSpace(filename))
|
||||
}
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
// ParseLegacyVersionFile takes a file and uses the parse-legacy-file callback
|
||||
// script to parse it if the script is present. Otherwise just reads the file
|
||||
// directly. In either case the returned string is split on spaces and a slice
|
||||
// of versions is returned.
|
||||
func (p Plugin) ParseLegacyVersionFile(path string) (versions []string, err error) {
|
||||
parseLegacyFileName := "parse-legacy-file"
|
||||
parseCallbackPath := filepath.Join(p.Dir, "bin", parseLegacyFileName)
|
||||
|
||||
var rawVersions string
|
||||
|
||||
if _, err := os.Stat(parseCallbackPath); err == nil {
|
||||
var stdOut strings.Builder
|
||||
var stdErr strings.Builder
|
||||
|
||||
err = p.RunCallback(parseLegacyFileName, []string{path}, map[string]string{}, &stdOut, &stdErr)
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
|
||||
rawVersions = stdOut.String()
|
||||
} else {
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
|
||||
rawVersions = string(bytes)
|
||||
}
|
||||
|
||||
for _, version := range strings.Split(rawVersions, " ") {
|
||||
versions = append(versions, strings.TrimSpace(version))
|
||||
}
|
||||
|
||||
return versions, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -414,6 +414,68 @@ func TestRunCallback(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestLegacyFilenames(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 list of filenames when list-legacy-filenames callback is present", func(t *testing.T) {
|
||||
filenames, err := plugin.LegacyFilenames()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filenames, []string{".dummy-version", ".dummyrc"})
|
||||
})
|
||||
|
||||
t.Run("returns empty list when list-legacy-filenames callback not present", func(t *testing.T) {
|
||||
testPluginName := "foobar"
|
||||
_, err := repotest.InstallPlugin("dummy_plugin_no_download", testDataDir, testPluginName)
|
||||
assert.Nil(t, err)
|
||||
plugin := New(conf, testPluginName)
|
||||
|
||||
filenames, err := plugin.LegacyFilenames()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, filenames, []string{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseLegacyVersionFile(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)
|
||||
|
||||
data := []byte("dummy-1.2.3")
|
||||
currentDir := t.TempDir()
|
||||
path := filepath.Join(currentDir, ".dummy-version")
|
||||
err = os.WriteFile(path, data, 0o666)
|
||||
assert.Nil(t, err)
|
||||
|
||||
t.Run("returns file contents unchanged when parse-legacy-file callback not present", func(t *testing.T) {
|
||||
testPluginName := "foobar"
|
||||
_, err := repotest.InstallPlugin("dummy_plugin_no_download", testDataDir, testPluginName)
|
||||
assert.Nil(t, err)
|
||||
plugin := New(conf, testPluginName)
|
||||
|
||||
versions, err := plugin.ParseLegacyVersionFile(path)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, versions, []string{"dummy-1.2.3"})
|
||||
})
|
||||
|
||||
t.Run("returns file contents parsed by parse-legacy-file callback when it is present", func(t *testing.T) {
|
||||
versions, err := plugin.ParseLegacyVersionFile(path)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, versions, []string{"1.2.3"})
|
||||
})
|
||||
|
||||
t.Run("returns error when passed file that doesn't exist", func(t *testing.T) {
|
||||
versions, err := plugin.ParseLegacyVersionFile("non-existant-file")
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, versions)
|
||||
})
|
||||
}
|
||||
|
||||
func touchFile(name string) error {
|
||||
file, err := os.OpenFile(name, os.O_RDONLY|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
|
@ -31,15 +31,28 @@ func Setup(asdfDataDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GeneratePlugin copies in the specified plugin fixture into a test directory
|
||||
// and initializes a Git repo for it so it can be installed by asdf.
|
||||
func GeneratePlugin(fixtureName, asdfDataDir, pluginName string) (string, error) {
|
||||
// InstallPlugin copies in the specified plugin fixture into the asdfDataDir's
|
||||
// plugin directory and initializes a Git repo for it so asdf treats it as
|
||||
// installed.
|
||||
func InstallPlugin(fixtureName, asdfDataDir, pluginName string) (string, error) {
|
||||
root, err := getModuleRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fixturesDir := filepath.Join(asdfDataDir, fixturesDir)
|
||||
destDir := filepath.Join(asdfDataDir, "plugins")
|
||||
return generatePluginInDir(root, fixtureName, destDir, pluginName)
|
||||
}
|
||||
|
||||
// GeneratePlugin copies in the specified plugin fixture into a test directory
|
||||
// and initializes a Git repo for it so it can be installed by asdf.
|
||||
func GeneratePlugin(fixtureName, dir, pluginName string) (string, error) {
|
||||
root, err := getModuleRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fixturesDir := filepath.Join(dir, fixturesDir)
|
||||
return generatePluginInDir(root, fixtureName, fixturesDir, pluginName)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user