Skip to content

Commit

Permalink
Use shims instead of direct operations on PATH.
Browse files Browse the repository at this point in the history
  • Loading branch information
aooohan committed May 9, 2024
1 parent 975a44c commit e8886b2
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 99 deletions.
25 changes: 11 additions & 14 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/logger"
"os"
"strings"
"text/template"
Expand Down Expand Up @@ -74,22 +75,18 @@ func activateCmd(ctx *cli.Context) error {
exportEnvs[k] = v
}

// generate shims for current shell
if envKeys.Paths.Len() > 0 {
logger.Debugf("Generate shims for current shell, path: %s\n", manager.PathMeta.ShellShimsPath)
envKeys.Paths.ToBinPaths().GenerateShims(manager.PathMeta.ShellShimsPath)
}

_ = os.Setenv(env.HookFlag, name)
exportEnvs[env.HookFlag] = &name
allPaths := envKeys.Paths.Merge(env.NewPaths(env.OsPaths))
pathStr := allPaths.String()
exportEnvs["PATH"] = &pathStr

// filter vfox sdk path
homePath := manager.PathMeta.HomePath
previousPaths := env.NewPaths(env.EmptyPaths)
for _, p := range allPaths.Slice() {
if strings.HasPrefix(p, homePath) {
previousPaths.Add(p)
}
}
prePathsStr := previousPaths.String()
exportEnvs[env.PreviousPathsFlag] = &prePathsStr
osPaths := env.NewPaths(env.OsPaths)
osPaths.Add(manager.PathMeta.ShellShimsPath)
osPathsStr := osPaths.String()
exportEnvs["PATH"] = &osPathsStr

path := manager.PathMeta.ExecutablePath
path = strings.Replace(path, "\\", "/", -1)
Expand Down
24 changes: 6 additions & 18 deletions cmd/commands/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/urfave/cli/v2"
"github.com/version-fox/vfox/internal"
"github.com/version-fox/vfox/internal/env"
"github.com/version-fox/vfox/internal/logger"
"github.com/version-fox/vfox/internal/shell"
"github.com/version-fox/vfox/internal/toolset"
)
Expand Down Expand Up @@ -127,27 +128,14 @@ func envFlag(ctx *cli.Context) error {
for k, v := range envKeys.Variables {
exportEnvs[k] = v
}
sdkPaths := envKeys.Paths

// Takes the complement of previousPaths and sdkPaths, and removes the complement from osPaths.
previousPaths := env.NewPaths(env.PreviousPaths)
for _, pp := range previousPaths.Slice() {
if sdkPaths.Contains(pp) {
previousPaths.Remove(pp)
}
}
osPaths := env.NewPaths(env.OsPaths)
if previousPaths.Len() != 0 {
for _, pp := range previousPaths.Slice() {
osPaths.Remove(pp)
}
// FIXME Optimize to avoid repeated generation
// generate shims for current shell
if envKeys.Paths.Len() > 0 {
logger.Debugf("Generate shims for current shell, path: %s\n", manager.PathMeta.ShellShimsPath)
envKeys.Paths.ToBinPaths().GenerateShims(manager.PathMeta.ShellShimsPath)
}
// Set the sdk paths to the new previous paths.
newPreviousPathStr := sdkPaths.String()
exportEnvs[env.PreviousPathsFlag] = &newPreviousPathStr

pathStr := sdkPaths.Merge(osPaths).String()
exportEnvs["PATH"] = &pathStr
exportStr := s.Export(exportEnvs)
fmt.Println(exportStr)
return nil
Expand Down
51 changes: 1 addition & 50 deletions internal/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ package env

import (
"io"
"os"
"strings"

"github.com/version-fox/vfox/internal/util"
)

type Manager interface {
Expand All @@ -38,51 +34,6 @@ type Vars map[string]*string
// Envs is a struct that contains environment variables and PATH.
type Envs struct {
Variables Vars
BinPaths *BinPaths
Paths *Paths
}

type PathFrom int

const (
EmptyPaths PathFrom = iota
OsPaths
PreviousPaths
)

// Paths is a slice of PATH.
type Paths struct {
util.Set[string]
}

func (p *Paths) Merge(other *Paths) *Paths {
for _, path := range other.Slice() {
p.Add(path)
}
return p
}

// NewPaths returns a new Paths.
// from is the source of the paths.
// If from is OsPaths, it returns the paths from the environment variable PATH.
// If from is PreviousPaths, it returns the paths from the environment variable __VFOX_PREVIOUS_PATHS
// If from is neither OsPaths nor PreviousPaths, it returns an empty Paths.
func NewPaths(from PathFrom) *Paths {
var paths []string
switch from {
case OsPaths:
paths = strings.Split(os.Getenv("PATH"), string(os.PathListSeparator))
case PreviousPaths:
if preStr := os.Getenv(PreviousPathsFlag); preStr != "" {
paths = strings.Split(preStr, string(os.PathListSeparator))
}
default:

}
p := &Paths{
util.NewSortedSet[string](),
}
for _, v := range paths {
p.Add(v)
}
return p
}
112 changes: 112 additions & 0 deletions internal/env/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2024 Han Li and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package env

import (
"github.com/version-fox/vfox/internal/logger"
"github.com/version-fox/vfox/internal/util"
"os"
"path/filepath"
"strings"
)

type PathFrom int

const (
EmptyPaths PathFrom = iota
OsPaths
PreviousPaths
)

// BinPaths is a slice of bin paths.
type BinPaths Paths

// GenerateShims generates shims to target dir for the bin paths.
func (b *BinPaths) GenerateShims(dir string) {
logger.Debugf("Generate shims to %s", dir)
for _, p := range b.Slice() {
name := filepath.Base(p)
targetShim := filepath.Join(dir, name)
logger.Debugf("Create shim from %s to %s", p, targetShim)
if util.FileExists(targetShim) {
_ = os.Remove(targetShim)
}
if err := util.MkSymlink(p, targetShim); err != nil {
logger.Debugf("Create symlink failed: %s", err)
continue
}
}
}

// Paths is a slice of PATH.
type Paths struct {
util.Set[string]
}

func (p *Paths) Merge(other *Paths) *Paths {
for _, path := range other.Slice() {
p.Add(path)
}
return p
}

// ToBinPaths returns a BinPaths from Paths which contains only executable files.
func (p *Paths) ToBinPaths() *BinPaths {
bins := NewPaths(EmptyPaths)
for _, path := range p.Slice() {
dir, err := os.ReadDir(path)
if err != nil {
return nil
}
for _, d := range dir {
if d.IsDir() {
continue
}
file := filepath.Join(path, d.Name())
if util.IsExecutable(file) {
bins.Add(file)
}
}
}
return (*BinPaths)(bins)
}

// NewPaths returns a new Paths.
// from is the source of the paths.
// If from is OsPaths, it returns the paths from the environment variable PATH.
// If from is PreviousPaths, it returns the paths from the environment variable __VFOX_PREVIOUS_PATHS
// If from is neither OsPaths nor PreviousPaths, it returns an empty Paths.
func NewPaths(from PathFrom) *Paths {
var paths []string
switch from {
case OsPaths:
paths = strings.Split(os.Getenv("PATH"), string(os.PathListSeparator))
case PreviousPaths:
if preStr := os.Getenv(PreviousPathsFlag); preStr != "" {
paths = strings.Split(preStr, string(os.PathListSeparator))
}
default:

}
p := &Paths{
util.NewSortedSet[string](),
}
for _, v := range paths {
p.Add(v)
}
return p
}
9 changes: 9 additions & 0 deletions internal/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type PathMeta struct {
PluginPath string
ExecutablePath string
WorkingDirectory string
GlobalShimsPath string
ShellShimsPath string
}

func newPathMeta() (*PathMeta, error) {
Expand Down Expand Up @@ -63,6 +65,11 @@ func newPathMeta() (*PathMeta, error) {
}
}

globalShimsPath := filepath.Join(homePath, "shims")
_ = os.MkdirAll(globalShimsPath, 0777)
shellShimsPath := filepath.Join(curTmpPath, "shims")
_ = os.MkdirAll(shellShimsPath, 0777)

workingDirectory, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("get current working directory failed: %w", err)
Expand All @@ -76,5 +83,7 @@ func newPathMeta() (*PathMeta, error) {
PluginPath: pluginPath,
ExecutablePath: exePath,
WorkingDirectory: workingDirectory,
GlobalShimsPath: globalShimsPath,
ShellShimsPath: shellShimsPath,
}, nil
}
41 changes: 24 additions & 17 deletions internal/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,26 +289,27 @@ 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")
sdkPackage, err := b.GetLocalSdkPackage(version)

keys, err := b.EnvKeys(version)
if err != nil {
pterm.Printf("Failed to get local sdk info, err:%s\n", err.Error())
return err
}

toolVersion, err := toolset.NewToolVersion(b.sdkManager.PathMeta.HomePath)
if err != nil {
return fmt.Errorf("failed to read tool versions, err:%w", err)
}
keys, err := b.Plugin.EnvKeys(sdkPackage)
if err != nil {
return fmt.Errorf("plugin [EnvKeys] method error: %w", err)
}

keys.Paths.ToBinPaths().GenerateShims(b.sdkManager.PathMeta.GlobalShimsPath)

// 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.Add(b.sdkManager.PathMeta.GlobalShimsPath)
if err = b.sdkManager.EnvManager.Load(keys); err != nil {
return err
}
Expand All @@ -328,28 +329,32 @@ 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 := envKeys.Paths.ToBinPaths()
if scope == Global {
sdkPackage, err := b.GetLocalSdkPackage(version)
if err != nil {
pterm.Printf("Failed to get local sdk info, err:%s\n", err.Error())
return err
}
toolVersion, err := toolset.NewToolVersion(b.sdkManager.PathMeta.HomePath)
if err != nil {
return fmt.Errorf("failed to read tool versions, err:%w", err)
}
keys, err := b.Plugin.EnvKeys(sdkPackage)
if err != nil {
return fmt.Errorf("plugin [EnvKeys] method error: %w", err)
}

binPaths.GenerateShims(b.sdkManager.PathMeta.GlobalShimsPath)

// clear global env
logger.Debugf("Clear global env: %s\n", b.Plugin.SdkName)
if oldVersion, ok := toolVersion.Record[b.Plugin.SdkName]; ok {
b.clearGlobalEnv(Version(oldVersion))
}

if err = b.sdkManager.EnvManager.Load(keys); err != nil {
// FIXME Need optimization
for _, p := range envKeys.Paths.Slice() {
envKeys.Paths.Remove(p)
}
envKeys.Paths.Add(b.sdkManager.PathMeta.GlobalShimsPath)

if err = b.sdkManager.EnvManager.Load(envKeys); err != nil {
return err
}
err = b.sdkManager.EnvManager.Flush()
Expand Down Expand Up @@ -379,6 +384,8 @@ func (b *Sdk) useInHook(version Version, scope UseScope) error {
return fmt.Errorf("failed to save tool versions, err:%w", err)
}

binPaths.GenerateShims(b.sdkManager.PathMeta.ShellShimsPath)

pterm.Printf("Now using %s.\n", pterm.LightGreen(b.label(version)))
if !env.IsHookEnv() {
return shell.GetProcess().Open(os.Getppid())
Expand Down
Loading

0 comments on commit e8886b2

Please sign in to comment.