169 lines
3.7 KiB
Go
169 lines
3.7 KiB
Go
package tool
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/exp/slog"
|
|
|
|
sdk "code.icb4dc0.de/buildr/wasi-module-sdk-go"
|
|
"code.icb4dc0.de/buildr/wasi-module-sdk-go/exec"
|
|
)
|
|
|
|
const (
|
|
goInstall = 2
|
|
stateAccessTimeout = 100 * time.Millisecond
|
|
)
|
|
|
|
var (
|
|
_ sdk.Module = (*GoTool)(nil)
|
|
moduleVersionRegexp = regexp.MustCompile(`^v\d$`)
|
|
)
|
|
|
|
type GoTool struct {
|
|
Env map[string]string `hcl:"environment,optional"`
|
|
BinaryNameOverride string `hcl:"binary_name"`
|
|
Repository string `hcl:"repository"`
|
|
Version string `hcl:"version"`
|
|
State State `hcl:"state,optional"`
|
|
BuildArgs []string `hcl:"build_args,optional"`
|
|
}
|
|
|
|
func (g GoTool) BinaryName() string {
|
|
if g.BinaryNameOverride != "" {
|
|
return g.BinaryNameOverride
|
|
}
|
|
|
|
repo := g.Repository
|
|
|
|
if atIdx := strings.LastIndex(repo, "@"); atIdx != -1 {
|
|
repo = repo[:atIdx]
|
|
}
|
|
|
|
u, err := url.Parse(repo)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
remain, binPackageName := path.Split(u.Path)
|
|
if moduleVersionRegexp.MatchString(binPackageName) {
|
|
_, binPackageName = path.Split(remain[:len(remain)-1])
|
|
}
|
|
|
|
return binPackageName
|
|
}
|
|
|
|
func (g GoTool) Execute(ctx sdk.ExecutionContext) error {
|
|
var (
|
|
binName = g.BinaryName()
|
|
stateKey = fmt.Sprintf("%s.state", binName)
|
|
logger = ctx.Logger().With(
|
|
slog.String("tool_name", binName),
|
|
slog.String("repository", g.Repository),
|
|
slog.String("version", g.Version),
|
|
)
|
|
stateEncoder = sdk.NewJSONStateEncoder[GoToolState](ctx)
|
|
state = g.state()
|
|
)
|
|
|
|
logger.Info("Ensuring got tool is installed")
|
|
|
|
if p, err := exec.LookPath(binName); state == StateGlobal && err == nil {
|
|
logger.Info(
|
|
"Found tool installation",
|
|
slog.String("tool_path", p),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
stateCtx, cancel := context.WithTimeout(ctx, stateAccessTimeout)
|
|
currentState, _, _, err := stateEncoder.Get(stateCtx, stateKey)
|
|
cancel()
|
|
|
|
if err != nil {
|
|
logger.Warn("Failed to get state", slog.String("err", err.Error()))
|
|
}
|
|
|
|
desiredState := GoToolState{
|
|
InstalledVersion: g.Version,
|
|
BuildArgs: g.BuildArgs,
|
|
Env: g.Env,
|
|
}
|
|
|
|
existingToolPath := filepath.Join(ctx.BinariesDir(), binName)
|
|
if _, err = os.Stat(existingToolPath); err == nil && desiredState.Equals(currentState) {
|
|
logger.Info("Tool is already installed according to state", slog.String("tool_path", existingToolPath))
|
|
return nil
|
|
}
|
|
|
|
logger.Debug("Installing Go tool", slog.String("out_dir", ctx.OutDir()))
|
|
|
|
if _, err = exec.LookPath("go"); err != nil {
|
|
return fmt.Errorf("failed to lookup path for 'go' binary: %w", err)
|
|
}
|
|
|
|
args := make([]string, 0, goInstall+len(g.BuildArgs))
|
|
|
|
args = append(args, "install")
|
|
args = append(args, g.BuildArgs...)
|
|
args = append(args, fmt.Sprintf("%s@%s", g.Repository, g.version()))
|
|
|
|
logger.Debug("Installing Go tool", slog.String("args", strings.Join(args, ", ")))
|
|
|
|
cmd := exec.NewCommand(
|
|
"go",
|
|
args...,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if g.Env == nil {
|
|
g.Env = make(map[string]string)
|
|
}
|
|
|
|
g.Env["GOBIN"] = ctx.OutDir()
|
|
|
|
cmd.AddEnv(g.Env)
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
stateCtx, cancel = context.WithTimeout(ctx, stateAccessTimeout)
|
|
defer cancel()
|
|
return stateEncoder.Set(stateCtx, stateKey, desiredState)
|
|
}
|
|
|
|
func (g GoTool) Category() sdk.Category {
|
|
return sdk.CategoryTool
|
|
}
|
|
|
|
func (g GoTool) Type() string {
|
|
return "go_tool"
|
|
}
|
|
|
|
func (g GoTool) version() string {
|
|
if g.Version == "" {
|
|
return "latest"
|
|
}
|
|
|
|
return g.Version
|
|
}
|
|
|
|
func (g GoTool) state() State {
|
|
//nolint:exhaustive // handled by default
|
|
switch g.State {
|
|
case StateGlobal:
|
|
return StateGlobal
|
|
default:
|
|
return StateLocal
|
|
}
|
|
}
|