mirror of
https://github.com/jimeh/evm.git
synced 2026-02-19 07:26:40 +00:00
refactor: extract core logic to a plain Go package
This commit is contained in:
286
manager/manager.go
Normal file
286
manager/manager.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
Err = errors.New("")
|
||||
ErrVersion = fmt.Errorf("%w", Err)
|
||||
ErrNoCurrentVersion = fmt.Errorf("%w", ErrVersion)
|
||||
ErrVersionNotFound = fmt.Errorf("%w", ErrVersion)
|
||||
ErrBinNotFound = fmt.Errorf("%w", ErrVersion)
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
Config *Config
|
||||
}
|
||||
|
||||
func New(config *Config) (*Manager, error) {
|
||||
if config == nil {
|
||||
var err error
|
||||
config, err = NewConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Manager{Config: config}, nil
|
||||
}
|
||||
|
||||
func (m *Manager) CurrentVersion() string {
|
||||
return m.Config.Current.Version
|
||||
}
|
||||
|
||||
func (m *Manager) CurrentSetBy() string {
|
||||
return m.Config.Current.SetBy
|
||||
}
|
||||
|
||||
func (m *Manager) List(ctx context.Context) ([]*Version, error) {
|
||||
return newVersions(ctx, m.Config)
|
||||
}
|
||||
|
||||
func (m *Manager) Get(ctx context.Context, version string) (*Version, error) {
|
||||
return newVersion(ctx, m.Config, version)
|
||||
}
|
||||
|
||||
func (m *Manager) Use(ctx context.Context, version string) error {
|
||||
ver, err := m.Get(ctx, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentFile := filepath.Join(m.Config.Paths.Root, currentFileName)
|
||||
err = os.WriteFile(currentFile, []byte(ver.Version), 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.rehashVersions(ctx, false, []*Version{ver})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) RehashAll(ctx context.Context) error {
|
||||
versions, err := m.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.rehashVersions(ctx, true, versions)
|
||||
}
|
||||
|
||||
func (m *Manager) RehashVersions(
|
||||
ctx context.Context,
|
||||
versions []string,
|
||||
) error {
|
||||
var vers []*Version
|
||||
for _, s := range versions {
|
||||
v, err := m.Get(ctx, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vers = append(vers, v)
|
||||
}
|
||||
|
||||
return m.rehashVersions(ctx, false, vers)
|
||||
}
|
||||
|
||||
func (m *Manager) rehashVersions(
|
||||
ctx context.Context,
|
||||
tidy bool,
|
||||
versions []*Version,
|
||||
) error {
|
||||
|
||||
programs := map[string]bool{}
|
||||
for _, ver := range versions {
|
||||
for _, bin := range ver.Binaries {
|
||||
base := filepath.Base(bin)
|
||||
programs[base] = true
|
||||
}
|
||||
}
|
||||
|
||||
shims, err := m.ListShims(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shimMap := map[string]bool{}
|
||||
for _, s := range shims {
|
||||
base := filepath.Base(s)
|
||||
shimMap[base] = true
|
||||
}
|
||||
|
||||
shim, err := m.shim()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(m.Config.Paths.Shims, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name := range programs {
|
||||
shimFile := filepath.Join(m.Config.Paths.Shims, name)
|
||||
err = os.WriteFile(shimFile, shim, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var f fs.FileInfo
|
||||
f, err = os.Stat(shimFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.Mode().Perm() != 0o755 {
|
||||
err = os.Chmod(shimFile, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
delete(shimMap, name)
|
||||
}
|
||||
|
||||
if tidy {
|
||||
for name := range shimMap {
|
||||
shimFile := filepath.Join(m.Config.Paths.Shims, name)
|
||||
err := os.Remove(shimFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var shimTemplate = template.Must(template.New("other").Parse(
|
||||
`#!/usr/bin/env bash
|
||||
set -e
|
||||
[ -n "$EVM_DEBUG" ] && set -x
|
||||
|
||||
program="${0##*/}"
|
||||
export EVM_ROOT="{{.Paths.Root}}"
|
||||
exec "{{.Paths.Binary}}" exec "$program" "$@"
|
||||
`))
|
||||
|
||||
func (m *Manager) shim() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := shimTemplate.Execute(&buf, m.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (m *Manager) ListShims(ctx context.Context) ([]string, error) {
|
||||
entries, err := os.ReadDir(m.Config.Paths.Shims)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := []string{}
|
||||
|
||||
for _, entry := range entries {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
shimPath := filepath.Join(m.Config.Paths.Shims, entry.Name())
|
||||
f, err := os.Stat(shimPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Mode().IsRegular() {
|
||||
r = append(r, shimPath)
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Exec(
|
||||
ctx context.Context,
|
||||
program string,
|
||||
args []string,
|
||||
) error {
|
||||
current := m.CurrentVersion()
|
||||
if current == "" {
|
||||
return ErrNoCurrentVersion
|
||||
}
|
||||
|
||||
return m.ExecVersion(ctx, current, program, args)
|
||||
}
|
||||
|
||||
func (m *Manager) ExecVersion(
|
||||
ctx context.Context,
|
||||
version string,
|
||||
program string,
|
||||
args []string,
|
||||
) error {
|
||||
ver, err := m.Get(ctx, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bin, err := ver.FindBin(program)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
execArgs := append([]string{bin}, args...)
|
||||
execEnv := os.Environ()
|
||||
|
||||
// Prepend selected version's bin directory to PATH.
|
||||
for i := 0; i < len(execEnv); i++ {
|
||||
if strings.HasPrefix(execEnv[i], "PATH=") {
|
||||
execEnv[i] = "PATH=" + ver.BinDir + ":" + execEnv[i][5:]
|
||||
}
|
||||
}
|
||||
|
||||
return syscall.Exec(bin, execArgs, execEnv)
|
||||
}
|
||||
|
||||
func (m *Manager) FindBin(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
) ([]*Version, error) {
|
||||
versions, err := m.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var availableIn []*Version
|
||||
for _, ver := range versions {
|
||||
if _, err := ver.FindBin(name); err == nil {
|
||||
availableIn = append(availableIn, ver)
|
||||
}
|
||||
}
|
||||
|
||||
return availableIn, nil
|
||||
}
|
||||
Reference in New Issue
Block a user