From d3f42def966185671e136e07872d95f81d0eb1a4 Mon Sep 17 00:00:00 2001 From: lihan Date: Fri, 17 May 2024 18:14:24 +0800 Subject: [PATCH] refactor: link to the sdk installation directory instead of the bin directory fix #278 fix version-fox/vfox-java#16 --- cmd/commands/activate.go | 7 +- cmd/commands/env.go | 18 ++-- internal/manager.go | 4 +- internal/package.go | 119 ++++++++++++++++++++++ internal/scope.go | 21 ++++ internal/sdk.go | 197 ++++++++++++++++--------------------- internal/shim/shim_unix.go | 3 +- 7 files changed, 237 insertions(+), 132 deletions(-) diff --git a/cmd/commands/activate.go b/cmd/commands/activate.go index 10bbefce..75159c23 100644 --- a/cmd/commands/activate.go +++ b/cmd/commands/activate.go @@ -65,13 +65,11 @@ func activateCmd(ctx *cli.Context) error { sdkEnvs, err := manager.EnvKeys(toolset.MultiToolVersions{ workToolVersion, homeToolVersion, - }) + }, internal.ShellLocation) if err != nil { return err } - sdkCurrentPaths := sdkEnvs.LinkCurrent(manager.PathMeta.CurTmpPath) - envKeys := sdkEnvs.ToEnvs() exportEnvs := make(env.Vars) @@ -82,8 +80,7 @@ func activateCmd(ctx *cli.Context) error { _ = os.Setenv(env.HookFlag, name) exportEnvs[env.HookFlag] = &name osPaths := env.NewPaths(env.OsPaths) - sdkCurrentPaths.Merge(osPaths) - pathsStr := sdkCurrentPaths.String() + pathsStr := envKeys.Paths.Merge(osPaths).String() exportEnvs["PATH"] = &pathsStr path := manager.PathMeta.ExecutablePath diff --git a/cmd/commands/env.go b/cmd/commands/env.go index 57c976dc..83470d8d 100644 --- a/cmd/commands/env.go +++ b/cmd/commands/env.go @@ -85,7 +85,7 @@ func outputJSON() error { } tvs.FilterTools(func(name, version string) bool { if lookupSdk, err := manager.LookupSdk(name); err == nil { - if keys, err := lookupSdk.EnvKeys(internal.Version(version)); err == nil { + if keys, err := lookupSdk.EnvKeys(internal.Version(version), internal.ShellLocation); err == nil { data.SDKs[lookupSdk.Plugin.Name] = keys.Variables data.Paths = append(data.Paths, keys.Paths.Slice()...) return true @@ -126,23 +126,19 @@ func envFlag(ctx *cli.Context) error { return err } - envKeys := sdkEnvs.ToEnvs() + if len(sdkEnvs) == 0 { + return nil + } - // link to current directory - sdkCurrentPaths := sdkEnvs.LinkCurrent(manager.PathMeta.CurTmpPath) + envKeys := sdkEnvs.ToEnvs() exportEnvs := make(env.Vars) for k, v := range envKeys.Variables { exportEnvs[k] = v } - // No changes, no need to refresh environment. - if sdkCurrentPaths.Len() == 0 { - return nil - } osPaths := env.NewPaths(env.OsPaths) - sdkCurrentPaths.Merge(osPaths) - pathsStr := sdkCurrentPaths.String() + pathsStr := envKeys.Paths.Merge(osPaths).String() exportEnvs["PATH"] = &pathsStr exportStr := s.Export(exportEnvs) @@ -191,7 +187,7 @@ func aggregateEnvKeys(manager *internal.Manager) (internal.SdkEnvs, error) { 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); err == nil { + if keys, err := lookupSdk.EnvKeys(v, internal.ShellLocation); err == nil { flushCache.Set(name, cache.Value(version), cache.NeverExpired) sdkEnvs = append(sdkEnvs, &internal.SdkEnv{ diff --git a/internal/manager.go b/internal/manager.go index 6db1dc24..4cfce4e1 100644 --- a/internal/manager.go +++ b/internal/manager.go @@ -66,7 +66,7 @@ type Manager struct { Config *config.Config } -func (m *Manager) EnvKeys(tvs toolset.MultiToolVersions) (SdkEnvs, error) { +func (m *Manager) EnvKeys(tvs toolset.MultiToolVersions, location Location) (SdkEnvs, error) { var sdkEnvs SdkEnvs tools := make(map[string]struct{}) for _, t := range tvs { @@ -76,7 +76,7 @@ func (m *Manager) EnvKeys(tvs toolset.MultiToolVersions) (SdkEnvs, error) { } if lookupSdk, err := m.LookupSdk(name); err == nil { v := Version(version) - if ek, err := lookupSdk.EnvKeys(v); err == nil { + if ek, err := lookupSdk.EnvKeys(v, location); err == nil { tools[name] = struct{}{} sdkEnvs = append(sdkEnvs, &SdkEnv{ diff --git a/internal/package.go b/internal/package.go index 42767e3d..1041d45c 100644 --- a/internal/package.go +++ b/internal/package.go @@ -17,14 +17,118 @@ package internal import ( + "fmt" + "github.com/version-fox/vfox/internal/logger" + "github.com/version-fox/vfox/internal/util" + "os" "path/filepath" ) +// LocationPackage represents a package that needs to be linked +type LocationPackage struct { + from *Package + sdk *Sdk + toPath string + location Location +} + +func NewLocationPackage(version Version, sdk *Sdk, location Location) (*LocationPackage, error) { + var mockPath string + switch location { + case OriginalLocation: + mockPath = "" + case GlobalLocation: + mockPath = filepath.Join(sdk.InstallPath, "current") + case ShellLocation: + mockPath = filepath.Join(sdk.sdkManager.PathMeta.CurTmpPath, sdk.Plugin.SdkName) + default: + return nil, fmt.Errorf("unknown location: %s", location) + } + sdkPackage, err := sdk.GetLocalSdkPackage(version) + if err != nil { + return nil, fmt.Errorf("failed to get local sdk info, err:%w", err) + } + return &LocationPackage{ + from: sdkPackage, + sdk: sdk, + toPath: mockPath, + location: location, + }, nil +} + +func (l *LocationPackage) ConvertLocation() *Package { + if l.location == OriginalLocation { + return l.from + } + clone := l.from.Clone() + mockPath := l.toPath + sdkPackage := clone + hasAddition := len(sdkPackage.Additions) != 0 + if !hasAddition { + sdkPackage.Main.Path = mockPath + } else { + sdkPackage.Main.Path = filepath.Join(mockPath, sdkPackage.Main.Name) + for _, a := range sdkPackage.Additions { + a.Path = filepath.Join(mockPath, a.Name) + } + } + return clone +} + +func (l *LocationPackage) Link() (*Package, error) { + if l.location == OriginalLocation { + return l.from, nil + } + mockPath := l.toPath + sourcePackage := l.from + targetPackage := l.ConvertLocation() + // If the mock path already exists, delete it first. + if util.FileExists(mockPath) { + logger.Debugf("Removing old package path: %s\n", mockPath) + if err := os.RemoveAll(mockPath); err != nil { + return nil, err + } + } + hasAddition := len(targetPackage.Additions) != 0 + if !hasAddition { + logger.Debugf("Create symlink %s -> %s\n", sourcePackage.Main.Path, targetPackage.Main.Path) + if err := util.MkSymlink(sourcePackage.Main.Path, targetPackage.Main.Path); err != nil { + return nil, fmt.Errorf("failed to create symlink, err:%w", err) + } + } else { + _ = os.MkdirAll(mockPath, 0755) + logger.Debugf("Create symlink %s -> %s\n", sourcePackage.Main.Path, targetPackage.Main.Path) + if err := util.MkSymlink(sourcePackage.Main.Path, targetPackage.Main.Path); err != nil { + return nil, fmt.Errorf("failed to create symlink, err:%w", err) + } + for i, a := range targetPackage.Additions { + sa := sourcePackage.Additions[i] + logger.Debugf("Create symlink %s -> %s\n", sa.Path, a.Path) + if err := util.MkSymlink(sa.Path, a.Path); err != nil { + return nil, fmt.Errorf("failed to create symlink, err:%w", err) + } + } + } + return targetPackage, nil +} + type Package struct { Main *Info Additions []*Info } +func (p *Package) Clone() *Package { + main := p.Main.Clone() + additions := make([]*Info, len(p.Additions)) + for i, a := range p.Additions { + additions[i] = a.Clone() + } + return &Package{ + Main: main, + Additions: additions, + } +} + type Info struct { Name string `luai:"name"` Version Version `luai:"version"` @@ -34,6 +138,21 @@ type Info struct { Checksum *Checksum } +func (i *Info) Clone() *Info { + headers := make(map[string]string, len(i.Headers)) + for k, v := range i.Headers { + headers[k] = v + } + return &Info{ + Name: i.Name, + Version: i.Version, + Path: i.Path, + Headers: headers, + Note: i.Note, + Checksum: i.Checksum, + } +} + func (i *Info) label() string { return i.Name + "@" + string(i.Version) } diff --git a/internal/scope.go b/internal/scope.go index f2a2cddb..701f435e 100644 --- a/internal/scope.go +++ b/internal/scope.go @@ -18,12 +18,20 @@ package internal type UseScope int +type Location int + const ( Global UseScope = iota Project Session ) +const ( + OriginalLocation Location = iota + GlobalLocation + ShellLocation +) + func (s UseScope) String() string { switch s { case Global: @@ -36,3 +44,16 @@ func (s UseScope) String() string { return "unknown" } } + +func (s Location) String() string { + switch s { + case GlobalLocation: + return "global" + case ShellLocation: + return "shell" + case OriginalLocation: + return "original" + default: + return "unknown" + } +} diff --git a/internal/sdk.go b/internal/sdk.go index c6229dbe..a549dc0e 100644 --- a/internal/sdk.go +++ b/internal/sdk.go @@ -40,6 +40,10 @@ import ( "github.com/version-fox/vfox/internal/util" ) +var ( + localSdkPackageCache = make(map[Version]*Package) +) + type Version string type SdkEnv struct { @@ -47,40 +51,6 @@ type SdkEnv struct { Env *env.Envs } -func (s *SdkEnv) LinkToTargetPath(targetDir string) (*env.Paths, error) { - return s.linkToCurrent(filepath.Join(targetDir, s.Sdk.Plugin.SdkName)) -} - -func (s *SdkEnv) LinkToInstallPath() (*env.Paths, error) { - return s.linkToCurrent(s.Sdk.InstallPath) -} - -// linkToCurrent link the specified version to the target directory -func (s *SdkEnv) linkToCurrent(targetDir string) (*env.Paths, error) { - paths := env.NewPaths(env.EmptyPaths) - for index, p := range s.Env.Paths.Slice() { - var tp string - if index == 0 { - tp = filepath.Join(targetDir, "current") - } else { - tp = filepath.Join(targetDir, filepath.Base(p)) - } - if util.FileExists(tp) { - if err := os.Remove(tp); err != nil { - logger.Errorf("Failed to remove symlink %s\n", tp) - return nil, err - } - } - _ = os.MkdirAll(targetDir, 0755) - if err := util.MkSymlink(p, tp); err != nil { - logger.Errorf("Failed to create symlink %s -> %s\n", p, tp) - return nil, err - } - paths.Add(tp) - } - return paths, nil -} - type SdkEnvs []*SdkEnv func (d *SdkEnvs) ToEnvs() *env.Envs { @@ -98,17 +68,6 @@ func (d *SdkEnvs) ToEnvs() *env.Envs { return envs } -// LinkCurrent link the current sdk to the `current` directory of the target directory -func (d *SdkEnvs) LinkCurrent(targetDir string) *env.Paths { - sdkCurrentPaths := env.NewPaths(env.EmptyPaths) - for _, sdkEnv := range *d { - if path, err := sdkEnv.LinkToTargetPath(targetDir); err == nil { - sdkCurrentPaths.Merge(path) - } - } - return sdkCurrentPaths -} - type Sdk struct { sdkManager *Manager Plugin *LuaPlugin @@ -301,14 +260,50 @@ func (b *Sdk) Available(args []string) ([]*Package, error) { return b.Plugin.Available(args) } -func (b *Sdk) EnvKeys(version Version) (*env.Envs, error) { +func (b *Sdk) ToLinkPackage(version Version, location Location) error { + linkPackage, err := b.GetLinkPackage(version, ShellLocation) + if err != nil { + return err + } + _, err = linkPackage.Link() + return err +} + +// GetLinkPackage will make symlink according Location and return the sdk package. +func (b *Sdk) GetLinkPackage(version Version, location Location) (*LocationPackage, error) { + return NewLocationPackage(version, b, location) +} + +func (b *Sdk) EnvKeysWithoutLink(version Version, location Location) (*env.Envs, error) { label := b.label(version) if !b.CheckExists(version) { return nil, fmt.Errorf("%s is not installed", label) } - sdkPackage, err := b.GetLocalSdkPackage(version) + linkPackage, err := b.GetLinkPackage(version, location) + if err != nil { + return nil, err + } + sdkPackage := linkPackage.ConvertLocation() + keys, err := b.Plugin.EnvKeys(sdkPackage) + if err != nil { + return nil, fmt.Errorf("plugin [EnvKeys] error: err:%w", err) + } + return keys, nil +} + +// EnvKeys will make symlink according Location. +func (b *Sdk) EnvKeys(version Version, location Location) (*env.Envs, error) { + label := b.label(version) + if !b.CheckExists(version) { + return nil, fmt.Errorf("%s is not installed", label) + } + linkPackage, err := b.GetLinkPackage(version, location) if err != nil { - return nil, fmt.Errorf("failed to get local sdk info, err:%w", err) + return nil, err + } + sdkPackage, err := linkPackage.Link() + if err != nil { + return nil, err } keys, err := b.Plugin.EnvKeys(sdkPackage) if err != nil { @@ -369,7 +364,7 @@ func (b *Sdk) Use(version Version, scope UseScope) error { if !env.IsHookEnv() { pterm.Printf("Warning: The current shell lacks hook support or configuration. It has switched to global scope automatically.\n") - keys, err := b.EnvKeys(version) + keys, err := b.EnvKeys(version, GlobalLocation) if err != nil { return err } @@ -390,24 +385,10 @@ func (b *Sdk) Use(version Version, scope UseScope) error { } } - sdkEnv := SdkEnv{ - Sdk: b, - Env: keys, - } - - paths, err := sdkEnv.LinkToInstallPath() - if err != nil { - return err - } - // clear global env if oldVersion, ok := toolVersion.Record[b.Plugin.SdkName]; ok { b.clearGlobalEnv(Version(oldVersion)) } - for _, p := range keys.Paths.Slice() { - keys.Paths.Remove(p) - } - keys.Paths.Merge(paths) if err = b.sdkManager.EnvManager.Load(keys); err != nil { return err } @@ -426,20 +407,9 @@ func (b *Sdk) Use(version Version, scope UseScope) error { } func (b *Sdk) useInHook(version Version, scope UseScope) error { - var multiToolVersion toolset.MultiToolVersions - envKeys, err := b.EnvKeys(version) - if err != nil { - return err - } - binPaths, err := envKeys.Paths.ToBinPaths() - if err != nil { - return err - } - - sdkEnv := SdkEnv{ - Sdk: b, - Env: envKeys, - } + var ( + multiToolVersion toolset.MultiToolVersions + ) if scope == Global { toolVersion, err := toolset.NewToolVersion(b.sdkManager.PathMeta.HomePath) @@ -447,11 +417,14 @@ func (b *Sdk) useInHook(version Version, scope UseScope) error { return fmt.Errorf("failed to read tool versions, err:%w", err) } - paths, err := sdkEnv.LinkToInstallPath() + envKeys, err := b.EnvKeys(version, GlobalLocation) + if err != nil { + return err + } + binPaths, err := envKeys.Paths.ToBinPaths() if err != nil { return err } - for _, bin := range binPaths.Slice() { binShim := shim.NewShim(bin, b.sdkManager.PathMeta.GlobalShimsPath) if err = binShim.Generate(); err != nil { @@ -465,12 +438,6 @@ func (b *Sdk) useInHook(version Version, scope UseScope) error { b.clearGlobalEnv(Version(oldVersion)) } - // FIXME Need optimization - for _, p := range envKeys.Paths.Slice() { - envKeys.Paths.Remove(p) - } - envKeys.Paths.Merge(paths) - if err = b.sdkManager.EnvManager.Load(envKeys); err != nil { return err } @@ -500,8 +467,7 @@ func (b *Sdk) useInHook(version Version, scope UseScope) error { if err = multiToolVersion.Save(); err != nil { return fmt.Errorf("failed to save tool versions, err:%w", err) } - - if _, err = sdkEnv.LinkToTargetPath(b.sdkManager.PathMeta.CurTmpPath); err != nil { + if err = b.ToLinkPackage(version, ShellLocation); err != nil { return err } @@ -579,11 +545,11 @@ func (b *Sdk) clearGlobalEnv(version Version) { if version == "" { return } - sdkPackage, err := b.GetLocalSdkPackage(version) + sdkPackage, err := b.GetLinkPackage(version, GlobalLocation) if err != nil { return } - envKV, err := b.Plugin.EnvKeys(sdkPackage) + envKV, err := b.Plugin.EnvKeys(sdkPackage.ConvertLocation()) if err != nil { return } @@ -592,12 +558,12 @@ func (b *Sdk) clearGlobalEnv(version Version) { } func (b *Sdk) GetLocalSdkPackage(version Version) (*Package, error) { - versionPath := b.VersionPath(version) - mainSdk := &Info{ - Name: b.Plugin.Name, - Version: version, + p, ok := localSdkPackageCache[version] + if ok { + return p, nil } - var additions []*Info + versionPath := b.VersionPath(version) + items := make(map[string]*Info) dir, err := os.ReadDir(versionPath) if err != nil { return nil, err @@ -606,33 +572,32 @@ func (b *Sdk) GetLocalSdkPackage(version Version) (*Package, error) { if d.IsDir() { split := strings.SplitN(d.Name(), "-", 2) name := split[0] - if name == b.Plugin.Name { - mainSdk.Path = filepath.Join(versionPath, d.Name()) - continue - } if len(split) != 2 { continue } v := split[1] - additions = append(additions, &Info{ + items[name] = &Info{ Name: name, Version: Version(v), Path: filepath.Join(versionPath, d.Name()), - }) + } } } - if err != nil { - return nil, err - } - - if mainSdk.Path == "" { + main := items[b.Plugin.Name] + delete(items, b.Plugin.Name) + if main.Path == "" { return nil, errors.New("main sdk not found") - } - return &Package{ - Main: mainSdk, + var additions []*Info + for _, v := range items { + additions = append(additions, v) + } + p2 := &Package{ + Main: main, Additions: additions, - }, nil + } + localSdkPackageCache[version] = p2 + return p2, nil } func (b *Sdk) CheckExists(version Version) bool { @@ -733,12 +698,12 @@ func (b *Sdk) ClearCurrentEnv() error { } if current != "" { - keys, err := b.EnvKeys(current) + envKeys, err := b.EnvKeysWithoutLink(current, GlobalLocation) if err != nil { return err } fmt.Println("Cleaning up the shims...") - if paths, err := keys.Paths.ToBinPaths(); err == nil { + if paths, err := envKeys.Paths.ToBinPaths(); err == nil { for _, p := range paths.Slice() { if err = shim.NewShim(p, b.sdkManager.PathMeta.GlobalShimsPath).Clear(); err != nil { return err @@ -748,8 +713,14 @@ func (b *Sdk) ClearCurrentEnv() error { envManager := b.sdkManager.EnvManager fmt.Println("Cleaning up env config...") - keys.Paths.Add(curPath) - _ = envManager.Remove(keys) + _ = envManager.Remove(envKeys) + _ = envManager.Flush() + + envKeys, err = b.EnvKeysWithoutLink(current, OriginalLocation) + if err != nil { + return err + } + _ = envManager.Remove(envKeys) _ = envManager.Flush() } diff --git a/internal/shim/shim_unix.go b/internal/shim/shim_unix.go index 41598d9e..ca8da5b6 100644 --- a/internal/shim/shim_unix.go +++ b/internal/shim/shim_unix.go @@ -29,7 +29,8 @@ import ( func (s *Shim) Clear() error { name := filepath.Base(s.BinaryPath) targetShim := filepath.Join(s.OutputPath, name) - if !util.FileExists(targetShim) { + _, err := os.Readlink(targetShim) + if os.IsNotExist(err) { return nil } return os.Remove(targetShim)