Merge pull request #53 from asdf-vm/tb/full-version-resolution

feat(golang-rewrite): full version resolution
This commit is contained in:
Trevor Brown 2024-08-08 08:45:12 -04:00 committed by Trevor Brown
commit ef91474538
4 changed files with 215 additions and 57 deletions

View File

@ -4,6 +4,7 @@ package config
import (
"context"
"io/fs"
"strconv"
"strings"
@ -29,6 +30,19 @@ type PluginRepoCheckDuration struct {
var pluginRepoCheckDurationDefault = PluginRepoCheckDuration{Every: 60}
// Config is the primary value this package builds and returns
type Config struct {
Home string
ConfigFile string `env:"ASDF_CONFIG_FILE, overwrite"`
DefaultToolVersionsFilename string `env:"ASDF_DEFAULT_TOOL_VERSIONS_FILENAME, overwrite"`
// Unclear if this value will be needed with the golang implementation.
// AsdfDir string
DataDir string `env:"ASDF_DATA_DIR, overwrite"`
ForcePrepend bool `env:"ASDF_FORCE_PREPEND, overwrite"`
// Field that stores the settings struct if it is loaded
Settings Settings
}
// Settings is a struct that stores config values from the asdfrc file
type Settings struct {
Loaded bool
@ -41,17 +55,13 @@ type Settings struct {
DisablePluginShortNameRepository bool
}
// Config is the primary value this package builds and returns
type Config struct {
Home string
ConfigFile string `env:"ASDF_CONFIG_FILE, overwrite"`
DefaultToolVersionsFilename string `env:"ASDF_DEFAULT_TOOL_VERSIONS_FILENAME, overwrite"`
// Unclear if this value will be needed with the golang implementation.
// AsdfDir string
DataDir string `env:"ASDF_DATA_DIR, overwrite"`
ForcePrepend bool `env:"ASDF_FORCE_PREPEND, overwrite"`
// Field that stores the settings struct if it is loaded
Settings Settings
func defaultConfig(dataDir, configFile string) *Config {
return &Config{
ForcePrepend: forcePrependDefault,
DataDir: dataDir,
ConfigFile: configFile,
DefaultToolVersionsFilename: defaultToolVersionsFilenameDefault,
}
}
func defaultSettings() *Settings {
@ -151,7 +161,11 @@ func (c *Config) GetHook(hook string) (string, error) {
return "", err
}
return c.Settings.Raw.Key(hook).String(), nil
if c.Settings.Raw != nil {
return c.Settings.Raw.Key(hook).String(), nil
}
return "", nil
}
func (c *Config) loadSettings() error {
@ -160,12 +174,18 @@ func (c *Config) loadSettings() error {
}
settings, err := loadSettings(c.ConfigFile)
if err != nil {
return err
}
c.Settings = settings
if err != nil {
_, ok := err.(*fs.PathError)
if ok {
return nil
}
return err
}
return nil
}
@ -180,29 +200,25 @@ func loadConfigEnv() (Config, error) {
return Config{}, err
}
config := Config{
ForcePrepend: forcePrependDefault,
DataDir: dataDir,
ConfigFile: configFile,
DefaultToolVersionsFilename: defaultToolVersionsFilenameDefault,
}
config := defaultConfig(dataDir, configFile)
context := context.Background()
err = envconfig.Process(context, &config)
err = envconfig.Process(context, config)
return config, err
return *config, err
}
func loadSettings(asdfrcPath string) (Settings, error) {
settings := defaultSettings()
// asdfrc is effectively formatted as ini
config, err := ini.Load(asdfrcPath)
if err != nil {
return Settings{}, err
return *settings, err
}
mainConf := config.Section("")
settings := defaultSettings()
settings.Raw = mainConf
settings.Loaded = true

View File

@ -92,6 +92,30 @@ func TestConfigMethods(t *testing.T) {
assert.Nil(t, err, "Returned error when loading settings")
assert.True(t, DisablePluginShortNameRepository, "Expected DisablePluginShortNameRepository to be set")
})
t.Run("When file does not exist returns settings struct with defaults", func(t *testing.T) {
config := Config{ConfigFile: "non-existant"}
legacy, err := config.LegacyVersionFile()
assert.Nil(t, err)
assert.False(t, legacy)
keepDownload, err := config.AlwaysKeepDownload()
assert.Nil(t, err)
assert.False(t, keepDownload)
lastCheck, err := config.PluginRepositoryLastCheckDuration()
assert.Nil(t, err)
assert.False(t, lastCheck.Never)
checkDuration, err := config.PluginRepositoryLastCheckDuration()
assert.Nil(t, err)
assert.Equal(t, checkDuration.Every, 60)
shortName, err := config.DisablePluginShortNameRepository()
assert.Nil(t, err)
assert.False(t, shortName)
})
}
func TestConfigGetHook(t *testing.T) {
@ -124,4 +148,12 @@ func TestConfigGetHook(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, hookCmd, "echo 'Executing' \"with args: $@\"")
})
t.Run("works if no config file", func(t *testing.T) {
config := Config{}
hookCmd, err := config.GetHook("some_hook")
assert.Nil(t, err)
assert.Empty(t, hookCmd)
})
}

View File

@ -15,19 +15,55 @@ import (
// ToolVersions represents a tool along with versions specified for it
type ToolVersions struct {
Name string
Versions []string
Source string
Versions []string
Directory 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)
// Version takes a plugin and a directory and resolves the tool to one or more
// versions.
func Version(conf config.Config, plugin plugins.Plugin, directory string) (versions ToolVersions, found bool, err error) {
version, envVariableName, found := findVersionsInEnv(plugin.Name)
if found {
return ToolVersions{Versions: version, Source: envVariableName}, true, nil
}
for !found {
versions, found, err = findVersionsInDir(conf, plugin, directory)
if err != nil {
return versions, false, err
}
nextDir := path.Dir(directory)
if nextDir == directory {
break
}
directory = nextDir
}
return versions, found, err
}
func findVersionsInDir(conf config.Config, plugin plugins.Plugin, directory string) (versions ToolVersions, found bool, err error) {
legacyFiles, err := conf.LegacyVersionFile()
if err != nil {
return versions, found, err
}
if legacyFiles {
versions, found, err := findVersionsInLegacyFile(plugin, directory)
if found || err != nil {
return versions, found, err
}
}
filepath := path.Join(directory, conf.DefaultToolVersionsFilename)
if _, err = os.Stat(filepath); err == nil {
versions, found, err := toolversions.FindToolVersions(filepath, plugin.Name)
if found || err != nil {
return versions, found, err
return ToolVersions{Versions: versions, Source: conf.DefaultToolVersionsFilename, Directory: directory}, found, err
}
}
@ -35,36 +71,36 @@ func findVersionsInDir(conf config.Config, plugin plugins.Plugin, directory stri
}
// findVersionsInEnv returns the version from the environment if present
func findVersionsInEnv(pluginName string) ([]string, bool) {
func findVersionsInEnv(pluginName string) ([]string, string, bool) {
envVariableName := "ASDF_" + strings.ToUpper(pluginName) + "_VERSION"
versionString := os.Getenv(envVariableName)
if versionString == "" {
return []string{}, false
return []string{}, envVariableName, false
}
return parseVersion(versionString), true
return parseVersion(versionString), envVariableName, 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) {
func findVersionsInLegacyFile(plugin plugins.Plugin, directory string) (versions ToolVersions, found bool, err error) {
var legacyFileNames []string
legacyFileNames, err = plugin.LegacyFilenames()
if err != nil {
return []string{}, false, err
return versions, false, err
}
for _, filename := range legacyFileNames {
filepath := path.Join(directory, filename)
if _, err := os.Stat(filepath); err == nil {
versions, err := plugin.ParseLegacyVersionFile(filepath)
versionsSlice, err := plugin.ParseLegacyVersionFile(filepath)
if len(versions) == 0 || (len(versions) == 1 && versions[0] == "") {
return nil, false, nil
if len(versionsSlice) == 0 || (len(versionsSlice) == 1 && versionsSlice[0] == "") {
return versions, false, nil
}
return versions, err == nil, err
return ToolVersions{Versions: versionsSlice, Source: filename, Directory: directory}, err == nil, err
}
}

View File

@ -12,9 +12,66 @@ import (
"github.com/stretchr/testify/assert"
)
func TestVersion(t *testing.T) {
testDataDir := t.TempDir()
currentDir := t.TempDir()
conf := config.Config{DataDir: testDataDir, DefaultToolVersionsFilename: ".tool-versions", ConfigFile: "testdata/asdfrc"}
_, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, "lua")
assert.Nil(t, err)
plugin := plugins.New(conf, "lua")
t.Run("returns empty slice when non-existent version passed", func(t *testing.T) {
toolVersion, found, err := Version(conf, plugin, t.TempDir())
assert.Nil(t, err)
assert.False(t, found)
assert.Empty(t, toolVersion.Versions)
})
t.Run("returns single version from .tool-versions file", func(t *testing.T) {
// write a version file
data := []byte("lua 1.2.3")
err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)
toolVersion, found, err := Version(conf, plugin, currentDir)
assert.Nil(t, err)
assert.True(t, found)
assert.Equal(t, toolVersion.Versions, []string{"1.2.3"})
})
t.Run("returns version from env when env variable set", func(t *testing.T) {
// Set env
t.Setenv("ASDF_LUA_VERSION", "2.3.4")
// write a version file
data := []byte("lua 1.2.3")
err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)
// assert env variable takes precedence
toolVersion, found, err := Version(conf, plugin, currentDir)
assert.Nil(t, err)
assert.True(t, found)
assert.Equal(t, toolVersion.Versions, []string{"2.3.4"})
})
t.Run("returns single version from .tool-versions file in parent directory", func(t *testing.T) {
// write a version file
data := []byte("lua 1.2.3")
err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)
subDir := filepath.Join(currentDir, "subdir")
err = os.MkdirAll(subDir, 0o777)
assert.Nil(t, err)
toolVersion, found, err := Version(conf, plugin, subDir)
assert.Nil(t, err)
assert.True(t, found)
assert.Equal(t, toolVersion.Versions, []string{"1.2.3"})
})
}
func TestFindVersionsInDir(t *testing.T) {
testDataDir := t.TempDir()
conf := config.Config{DataDir: testDataDir, DefaultToolVersionsFilename: ".tool-versions"}
conf := config.Config{DataDir: testDataDir, DefaultToolVersionsFilename: ".tool-versions", ConfigFile: "testdata/asdfrc"}
_, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, "lua")
assert.Nil(t, err)
plugin := plugins.New(conf, "lua")
@ -35,9 +92,9 @@ func TestFindVersionsInDir(t *testing.T) {
data := []byte("lua 1.2.3")
err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)
versions, found, err := findVersionsInDir(conf, plugin, currentDir)
toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir)
assert.Equal(t, versions, []string{"1.2.3"})
assert.Equal(t, toolVersion.Versions, []string{"1.2.3"})
assert.True(t, found)
assert.Nil(t, err)
})
@ -48,9 +105,9 @@ func TestFindVersionsInDir(t *testing.T) {
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)
toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir)
assert.Equal(t, versions, []string{"1.2.3", "2.3.4"})
assert.Equal(t, toolVersion.Versions, []string{"1.2.3", "2.3.4"})
assert.True(t, found)
assert.Nil(t, err)
})
@ -62,9 +119,22 @@ func TestFindVersionsInDir(t *testing.T) {
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)
toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir)
assert.Equal(t, versions, []string{"1.2.3", "2.3.4"})
assert.Equal(t, toolVersion.Versions, []string{"1.2.3", "2.3.4"})
assert.True(t, found)
assert.Nil(t, err)
})
t.Run("when legacy file support is on looks up version in legacy file", func(t *testing.T) {
currentDir := t.TempDir()
data := []byte("1.2.3 2.3.4")
err = os.WriteFile(filepath.Join(currentDir, ".dummy-version"), data, 0o666)
toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir)
assert.Equal(t, toolVersion.Versions, []string{"1.2.3", "2.3.4"})
assert.True(t, found)
assert.Nil(t, err)
})
@ -82,15 +152,15 @@ func TestFindVersionsLegacyFiles(t *testing.T) {
_, 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)
toolVersion, found, err := findVersionsInLegacyFile(plugin, t.TempDir())
assert.Empty(t, toolVersion.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)
toolVersion, found, err := findVersionsInLegacyFile(plugin, t.TempDir())
assert.Empty(t, toolVersion.Versions)
assert.False(t, found)
assert.Nil(t, err)
})
@ -102,8 +172,8 @@ func TestFindVersionsLegacyFiles(t *testing.T) {
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"})
toolVersion, found, err := findVersionsInLegacyFile(plugin, currentDir)
assert.Equal(t, toolVersion.Versions, []string{"1.2.3"})
assert.True(t, found)
assert.Nil(t, err)
})
@ -111,24 +181,28 @@ func TestFindVersionsLegacyFiles(t *testing.T) {
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")
versions, envVariableName, found := findVersionsInEnv("non-existent")
assert.False(t, found)
assert.Empty(t, versions)
assert.Equal(t, envVariableName, "ASDF_NON-EXISTENT_VERSION")
})
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")
versions, envVariableName, found := findVersionsInEnv("lua")
assert.True(t, found)
assert.Equal(t, versions, []string{"5.4.5"})
assert.Equal(t, envVariableName, "ASDF_LUA_VERSION")
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")
versions, envVariableName, found := findVersionsInEnv("lua")
assert.True(t, found)
assert.Equal(t, versions, []string{"5.4.5", "5.4.6"})
assert.Equal(t, envVariableName, "ASDF_LUA_VERSION")
os.Unsetenv("ASDF_LUA_VERSION")
})
}