feat: initial working version
This commit is contained in:
parent
09b9d3af60
commit
c2eb3a6a72
9 changed files with 410 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,3 +21,4 @@
|
||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
|
*.wasm
|
82
build/go_build.go
Normal file
82
build/go_build.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "code.icb4dc0.de/buildr/wasi-module-sdk-go"
|
||||||
|
"code.icb4dc0.de/buildr/wasi-module-sdk-go/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultArgsLength = 6
|
||||||
|
goArchAndGoOS = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ sdk.Module = (*GoBuild)(nil)
|
||||||
|
// _ sdk.Helper = (*GoBuild)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type GoBuild struct {
|
||||||
|
Env map[string]string `hcl:"environment,optional"`
|
||||||
|
Binary string `hcl:"binary"`
|
||||||
|
Main string `hcl:"main"`
|
||||||
|
GoOS string `hcl:"goos"`
|
||||||
|
GoArch string `hcl:"goarch"`
|
||||||
|
Flags []string `hcl:"flags,optional"`
|
||||||
|
LdFlags []string `hcl:"ldflags,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GoBuild) Type() string {
|
||||||
|
return "go_build"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GoBuild) Category() sdk.Category {
|
||||||
|
return sdk.CategoryBuild
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GoBuild) Execute(ctx sdk.ExecutionContext) (err error) {
|
||||||
|
logger := ctx.Logger()
|
||||||
|
|
||||||
|
buildArgs := make([]string, 1, defaultArgsLength+len(g.Flags))
|
||||||
|
buildArgs[0] = "build"
|
||||||
|
|
||||||
|
if ldFlags := strings.Join(g.LdFlags, " "); ldFlags != "" {
|
||||||
|
buildArgs = append(buildArgs, "-ldflags", ldFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildArgs = append(buildArgs, g.Flags...)
|
||||||
|
|
||||||
|
buildArgs = append(
|
||||||
|
buildArgs,
|
||||||
|
"-o",
|
||||||
|
filepath.Join(ctx.OutDir(), g.Binary),
|
||||||
|
g.Main,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.Info(
|
||||||
|
"Executing go build",
|
||||||
|
slog.String("args", strings.Join(buildArgs, " ")),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd := exec.NewCommand(
|
||||||
|
"go",
|
||||||
|
buildArgs...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.Env == nil {
|
||||||
|
g.Env = make(map[string]string, goArchAndGoOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Env["GOARCH"] = g.GoArch
|
||||||
|
g.Env["GOOS"] = g.GoOS
|
||||||
|
|
||||||
|
cmd.AddEnv(g.Env)
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
15
go.mod
Normal file
15
go.mod
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
module code.icb4dc0.de/buildr/golang-plugin
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
code.icb4dc0.de/buildr/wasi-module-sdk-go v0.0.0-20230629182727-3bf4797b6abd
|
||||||
|
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/tetratelabs/tinymem v0.1.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace code.icb4dc0.de/buildr/wasi-module-sdk-go => ../wasi-module-sdk-go
|
20
go.sum
Normal file
20
go.sum
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/tetratelabs/tinymem v0.1.0 h1:Qza1JAg9lquPPJ/CIei5qQYx7t18KLie83O2WR6CM58=
|
||||||
|
github.com/tetratelabs/tinymem v0.1.0/go.mod h1:WFFTZFhLod6lTL+UetFAopVbGaB+KFsVcIY+RUv7NeY=
|
||||||
|
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
|
||||||
|
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
|
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||||
|
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||||
|
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||||
|
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
22
main.go
Normal file
22
main.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
//go:generate tinygo build -o golang.wasm -gc=leaking --no-debug -target=wasi main.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.icb4dc0.de/buildr/golang-plugin/build"
|
||||||
|
"code.icb4dc0.de/buildr/golang-plugin/tool"
|
||||||
|
sdk "code.icb4dc0.de/buildr/wasi-module-sdk-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sdk.Register(sdk.CategoryTool, "go_tool", sdk.ModuleFactoryFunc(func() sdk.Module {
|
||||||
|
return new(tool.GoTool)
|
||||||
|
}))
|
||||||
|
|
||||||
|
sdk.Register(sdk.CategoryBuild, "go_build", sdk.ModuleFactoryFunc(func() sdk.Module {
|
||||||
|
return new(build.GoBuild)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
159
tool/go_tool.go
Normal file
159
tool/go_tool.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package tool
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "code.icb4dc0.de/buildr/wasi-module-sdk-go"
|
||||||
|
"code.icb4dc0.de/buildr/wasi-module-sdk-go/exec"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
stateEncoder = sdk.NewJSONStateEncoder[GoToolState](ctx)
|
||||||
|
state = g.state()
|
||||||
|
)
|
||||||
|
|
||||||
|
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()))
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
31
tool/go_tool_state.go
Normal file
31
tool/go_tool_state.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package tool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GoToolState struct {
|
||||||
|
Env map[string]string `json:"env,omitempty"`
|
||||||
|
InstalledVersion string `json:"installed_version"`
|
||||||
|
BuildArgs []string `json:"build_args,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s GoToolState) Equals(other GoToolState) bool {
|
||||||
|
if s.InstalledVersion != other.InstalledVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.BuildArgs) != len(other.BuildArgs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(s.BuildArgs)
|
||||||
|
slices.Sort(other.BuildArgs)
|
||||||
|
|
||||||
|
if !slices.Equal(s.BuildArgs, other.BuildArgs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return maps.Equal(s.Env, other.Env)
|
||||||
|
}
|
71
tool/go_tool_test.go
Normal file
71
tool/go_tool_test.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package tool_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.icb4dc0.de/buildr/golang-plugin/tool"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoTool_BinaryName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
type fields struct {
|
||||||
|
BinaryNameOverride string
|
||||||
|
Repository string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Override",
|
||||||
|
fields: fields{
|
||||||
|
BinaryNameOverride: "go-buildr",
|
||||||
|
Repository: "code.icb4dc0.de/buildr/buildr",
|
||||||
|
},
|
||||||
|
want: "go-buildr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No override, no inline-version, no version suffix",
|
||||||
|
fields: fields{
|
||||||
|
Repository: "code.icb4dc0.de/buildr/buildr",
|
||||||
|
},
|
||||||
|
want: "buildr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No override, no inline-version, version suffix",
|
||||||
|
fields: fields{
|
||||||
|
Repository: "code.icb4dc0.de/buildr/buildr/v2",
|
||||||
|
},
|
||||||
|
want: "buildr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No override, inline-version, no version suffix",
|
||||||
|
fields: fields{
|
||||||
|
Repository: "code.icb4dc0.de/buildr/buildr@latest",
|
||||||
|
},
|
||||||
|
want: "buildr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No override, inline-version, version suffix",
|
||||||
|
fields: fields{
|
||||||
|
Repository: "code.icb4dc0.de/buildr/buildr/v2@latest",
|
||||||
|
},
|
||||||
|
want: "buildr",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
g := tool.GoTool{
|
||||||
|
BinaryNameOverride: tt.fields.BinaryNameOverride,
|
||||||
|
Repository: tt.fields.Repository,
|
||||||
|
Version: tt.fields.Version,
|
||||||
|
}
|
||||||
|
if got := g.BinaryName(); got != tt.want {
|
||||||
|
t.Errorf("BinaryName() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
9
tool/state.go
Normal file
9
tool/state.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package tool
|
||||||
|
|
||||||
|
type State string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StateDefault State = ""
|
||||||
|
StateLocal State = "local"
|
||||||
|
StateGlobal State = "global"
|
||||||
|
)
|
Loading…
Reference in a new issue