Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: find .tool-versions and legacy files until reach root directory and keep .tool-versions order. #289

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions cmd/commands/activate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package commands

import (
"fmt"
"github.com/version-fox/vfox/internal/util"
"os"
"strings"
"text/template"
Expand Down Expand Up @@ -46,25 +47,18 @@ func activateCmd(ctx *cli.Context) error {
manager := internal.NewSdkManager()
defer manager.Close()

workToolVersion, err := toolset.NewToolVersion(manager.PathMeta.WorkingDirectory)
toolVersionMap, err := manager.FindToolVersion()
if err != nil {
return err
}

if err = manager.ParseLegacyFile(func(sdkname, version string) {
if _, ok := workToolVersion.Record[sdkname]; !ok {
workToolVersion.Record[sdkname] = version
}
}); err != nil {
return err
}
homeToolVersion, err := toolset.NewToolVersion(manager.PathMeta.HomePath)
if err != nil {
return err
}
sdkEnvs, err := manager.EnvKeys(toolset.MultiToolVersions{
workToolVersion,
homeToolVersion,
sdkEnvs, err := manager.EnvKeys([]*util.SortedMap[string, string]{
toolVersionMap,
homeToolVersion.SortedMap,
}, internal.ShellLocation)
if err != nil {
return err
Expand Down
84 changes: 50 additions & 34 deletions cmd/commands/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/version-fox/vfox/internal/logger"
"github.com/version-fox/vfox/internal/shell"
"github.com/version-fox/vfox/internal/toolset"
"github.com/version-fox/vfox/internal/util"
"os"
"path/filepath"
)

Expand Down Expand Up @@ -147,61 +149,75 @@ func envFlag(ctx *cli.Context) error {
}

func aggregateEnvKeys(manager *internal.Manager) (internal.SdkEnvs, error) {
workToolVersion, err := toolset.NewToolVersion(manager.PathMeta.WorkingDirectory)
if err != nil {
return nil, err
}

if err = manager.ParseLegacyFile(func(sdkname, version string) {
if _, ok := workToolVersion.Record[sdkname]; !ok {
workToolVersion.Record[sdkname] = version
}
}); err != nil {
toolVersionMap, err := manager.FindToolVersion()
if err != nil {
return nil, err
}

curToolVersion, err := toolset.NewToolVersion(manager.PathMeta.CurTmpPath)
if err != nil {
return nil, err
}
defer curToolVersion.Save()

// Add the working directory to the first
tvs := toolset.MultiToolVersions{workToolVersion, curToolVersion}
multiToolVersions := []*util.SortedMap[string, string]{
toolVersionMap,
curToolVersion.SortedMap,
}

flushCache, err := cache.NewFileCache(filepath.Join(manager.PathMeta.CurTmpPath, "flush_env.cache"))
if err != nil {
return nil, err
}
defer flushCache.Close()

var sdkEnvs []*internal.SdkEnv
var (
sdkEnvs []*internal.SdkEnv
finalSdks = make(map[string]struct{})
cacheLen = flushCache.Len()
)

tvs.FilterTools(func(name, version string) bool {
if lookupSdk, err := manager.LookupSdk(name); err == nil {
vv, ok := flushCache.Get(name)
if ok && string(vv) == version {
logger.Debugf("Hit cache, skip flush envrionment, %s@%s\n", name, version)
return true
} else {
logger.Debugf("No hit cache, name: %s cache: %s, expected: %s \n", name, string(vv), version)
for _, tv := range multiToolVersions {
if err = tv.ForEach(func(name string, version string) error {
if _, ok := finalSdks[name]; ok {
return nil
}
v := internal.Version(version)
if keys, err := lookupSdk.EnvKeys(v, internal.ShellLocation); err == nil {
flushCache.Set(name, cache.Value(version), cache.NeverExpired)

sdkEnvs = append(sdkEnvs, &internal.SdkEnv{
Sdk: lookupSdk, Env: keys,
})

// If we encounter a .tool-versions file, it is valid for the entire shell session,
// unless we encounter the next .tool-versions file or manually switch to the use command.
curToolVersion.Record[name] = version
return true
if lookupSdk, err := manager.LookupSdk(name); err == nil {
vv, ok := flushCache.Get(name)
if ok && string(vv) == version {
logger.Debugf("Hit cache, skip flush envrionment, %s@%s\n", name, version)
finalSdks[name] = struct{}{}
return nil
} else {
logger.Debugf("No hit cache, name: %s cache: %s, expected: %s \n", name, string(vv), version)
}
v := internal.Version(version)
if keys, err := lookupSdk.EnvKeys(v, internal.ShellLocation); err == nil {
flushCache.Set(name, cache.Value(version), cache.NeverExpired)

sdkEnvs = append(sdkEnvs, &internal.SdkEnv{
Sdk: lookupSdk, Env: keys,
})
finalSdks[name] = struct{}{}
}
}
return nil
}); err != nil {
return nil, err
}
return false
})
}

// Remove the old cache
if cacheLen != len(finalSdks) {
for _, sdkname := range flushCache.Keys() {
if _, ok := finalSdks[sdkname]; !ok {
linkPath := filepath.Join(manager.PathMeta.CurTmpPath, sdkname)
logger.Debugf("Remove unused sdk link: %s\n", linkPath)
_ = os.Remove(linkPath)
flushCache.Remove(sdkname)
}
}
}
return sdkEnvs, nil
}
22 changes: 22 additions & 0 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ func (c *FileCache) Get(key string) (Value, bool) {
return item.Val, true
}

func (c *FileCache) Keys() []string {
c.mu.RLock()
defer c.mu.RUnlock()
keys := make([]string, 0, len(c.items))
for k := range c.items {
keys = append(keys, k)
}
return keys
}

func (c *FileCache) Len() int {
return len(c.items)
}

func (c *FileCache) Clear() int {
c.mu.Lock()
defer c.mu.Unlock()
len := len(c.items)
c.items = make(map[string]Item)
return len
}

// Remove a key from the cache
func (c *FileCache) Remove(key string) {
c.mu.Lock()
Expand Down
95 changes: 84 additions & 11 deletions internal/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"

"github.com/mitchellh/go-ps"
"github.com/pterm/pterm"
Expand Down Expand Up @@ -67,13 +68,13 @@ type Manager struct {
Config *config.Config
}

func (m *Manager) EnvKeys(tvs toolset.MultiToolVersions, location Location) (SdkEnvs, error) {
func (m *Manager) EnvKeys(tvs []*util.SortedMap[string, string], location Location) (SdkEnvs, error) {
var sdkEnvs SdkEnvs
tools := make(map[string]struct{})
for _, t := range tvs {
for name, version := range t.Record {
_ = t.ForEach(func(name string, version string) error {
if _, ok := tools[name]; ok {
continue
return nil
}
if lookupSdk, err := m.LookupSdk(name); err == nil {
v := Version(version)
Expand All @@ -86,7 +87,8 @@ func (m *Manager) EnvKeys(tvs toolset.MultiToolVersions, location Location) (Sdk
})
}
}
}
return nil
})
}
return sdkEnvs, nil
}
Expand Down Expand Up @@ -216,7 +218,7 @@ func (m *Manager) Remove(pluginName string) error {
return err
}
for _, filename := range source.Plugin.LegacyFilenames {
delete(lfr.Record, filename)
lfr.Remove(filename)
}
if err = lfr.Save(); err != nil {
return fmt.Errorf("remove legacy filenames failed: %w", err)
Expand Down Expand Up @@ -301,10 +303,10 @@ func (m *Manager) Update(pluginName string) error {
return err
}
for _, filename := range sdk.Plugin.LegacyFilenames {
delete(lfr.Record, filename)
lfr.Remove(filename)
}
for _, filename := range tempPlugin.LegacyFilenames {
lfr.Record[filename] = pluginName
lfr.Set(filename, pluginName)
}
if err = lfr.Save(); err != nil {
return fmt.Errorf("update legacy filenames failed: %w", err)
Expand Down Expand Up @@ -458,7 +460,7 @@ func (m *Manager) Add(pluginName, url, alias string) error {
return err
}
for _, filename := range tempPlugin.LegacyFilenames {
lfr.Record[filename] = pname
lfr.Set(filename, pname)
}
if err = lfr.Save(); err != nil {
return fmt.Errorf("add legacy filenames failed: %w", err)
Expand Down Expand Up @@ -654,8 +656,8 @@ func (m *Manager) ParseLegacyFile(output func(sdkname, version string)) error {
}

// There are some legacy files to be parsed.
if len(legacyFileRecord.Record) > 0 {
for filename, sdkname := range legacyFileRecord.Record {
if legacyFileRecord.Len() > 0 {
_ = legacyFileRecord.ForEach(func(filename string, sdkname string) error {
path := filepath.Join(m.PathMeta.WorkingDirectory, filename)
if util.FileExists(path) {
logger.Debugf("Parsing legacy file %s \n", path)
Expand All @@ -669,12 +671,83 @@ func (m *Manager) ParseLegacyFile(output func(sdkname, version string)) error {
}
}
}
}
return nil
})

}
return nil
}

func (m *Manager) FindToolVersion() (*util.SortedMap[string, string], error) {
path := m.PathMeta.WorkingDirectory
legacyFileRecord, err := m.loadLegacyFileRecord()
if err != nil {
return nil, err
}
versionMap := util.NewSortedMap[string, string]()
for {
logger.Debugf("Find tool version in %s \n", pterm.LightBlue(path))
file := filepath.Join(path, toolset.ToolVersionFilename)
if util.FileExists(file) {
logger.Debugf("Parsing tool version file: %s \n", file)
wtv, err := toolset.NewFileRecord(file)
if err != nil {
return nil, err
}
versionMap = wtv.SortedMap
}
if m.Config.LegacyVersionFile.Enable {
var wg sync.WaitGroup
for _, filename := range legacyFileRecord.Keys() {
wg.Add(1)
go func(fn string) {
defer wg.Done()
sdkname, _ := legacyFileRecord.Get(fn)
legacyFile := filepath.Join(m.PathMeta.WorkingDirectory, fn)
if util.FileExists(legacyFile) {
logger.Debugf("Parsing legacy file %s \n", legacyFile)
if sdk, err := m.LookupSdk(sdkname); err == nil {
// The .tool-version in the current directory has the highest priority,
// checking to see if the version information in the legacy file exists in the former,
// and updating to the former record (Don’t fall into the file!) if it doesn't.
if version, err := sdk.ParseLegacyFile(legacyFile); err == nil && version != "" {
logger.Debugf("Found %s@%s in %s \n", sdkname, version, legacyFile)
versionMap.Set(sdkname, string(version))
}
}
}
}(filename)
}
wg.Wait()
}
if versionMap.Len() != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may not understand the code correctly. As soon as the first .toolsversion is found, the recursion is stopped. However, it could be that not all SDKs are specified in this file. In other words, I would expect a combination of all .toolsversions from all paths. The global file has the lowest priority and then ascending according to proximity to the current working directory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You reminded me that the global .tool-versions file should be combined.

In other words, I would expect a combination of all .toolsversions from all paths

I don't agree to this point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree to this point.

Expressed incorrectly. I mean all files that can be found up to the root. Say the following example

image

If I enter folder childdir1, I will receive all SDK in .tool-versions file 1 and 3, but not 2.

Copy link
Member Author

@aooohan aooohan Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reply. I have got it, and I will continue to improve this PR soon later.

break
}

parent := filepath.Dir(path)
if parent == path {
logger.Debugf("Reach root directory, stop searching: %s \n", parent)

if versionMap.Len() == 0 {
logger.Debugf("%s \n", pterm.LightRed("Toolchain version not found, use globally version."))
file := filepath.Join(m.PathMeta.HomePath, toolset.ToolVersionFilename)
if util.FileExists(file) {
logger.Debugf("Parsing tool version file: %s \n", file)
wtv, err := toolset.NewFileRecord(file)
if err != nil {
return nil, err
}
versionMap = wtv.SortedMap
}
}
break
}
path = parent
}

return versionMap, nil
}

func NewSdkManager() *Manager {
meta, err := newPathMeta()
if err != nil {
Expand Down
Loading
Loading