mirror of
https://github.com/jimeh/evm.git
synced 2026-02-18 23:16:39 +00:00
287 lines
5.0 KiB
Go
287 lines
5.0 KiB
Go
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
|
|
}
|