refactor: extract core logic to a plain Go package

This commit is contained in:
2022-02-26 18:38:37 +00:00
parent b67da4accb
commit 5fcb2b52ab
21 changed files with 1126 additions and 763 deletions

286
manager/manager.go Normal file
View 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
}