Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go toolchain "caster" #21

Merged
merged 1 commit into from
Nov 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.15

require (
github.com/Masterminds/semver/v3 v3.1.0
github.com/magefile/mage v1.10.0
github.com/stretchr/testify v1.6.1
github.com/svengreb/golib v0.1.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
Expand Down
31 changes: 31 additions & 0 deletions internal/support/os/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package os provides utilities for operating system related operations and interactions.
package os

import (
"fmt"
"strings"
)

// EnvMapToSlice transforms a map of environment variable key/value pairs into a slice separated by an equal sign.
func EnvMapToSlice(src map[string]string) []string {
dst := make([]string, len(src))
for k, v := range src {
dst = append(dst, fmt.Sprintf("%s=%s", k, v))
}
return dst
}

// EnvSliceToMap transforms a slice of environment variables separated by an equal sign into a map.
func EnvSliceToMap(src []string, dst map[string]string) {
for _, envVar := range src {
kv := strings.Split(envVar, "=")
if len(kv) == 1 {
dst[kv[0]] = ""
} else {
dst[kv[0]] = kv[1]
}
}
}
1 change: 1 addition & 0 deletions pkg/cast/cast.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package cast provides caster for spell incantations.
package cast

import (
Expand Down
47 changes: 47 additions & 0 deletions pkg/cast/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package cast

import (
"errors"
"fmt"

wErr "github.com/svengreb/wand/pkg/error"
)

const (
// ErrCasterValidation indicates that a caster validation failed.
ErrCasterValidation = wErr.ErrString("caster validation failed")

// ErrCasterSpellIncantationKindUnsupported indicates that a spell incantation kind is not supported by a caster.
ErrCasterSpellIncantationKindUnsupported = wErr.ErrString("unsupported spell incantation kind")
)

// ErrCast represents a cast error.
type ErrCast struct {
// Err is a wrapped error.
Err error
// Kind is the error kind.
Kind error
}

func (e *ErrCast) Error() string {
msg := "cast error"
if e.Kind != nil {
msg = fmt.Sprintf("%s: %v", msg, e.Kind)
}
if e.Err != nil {
msg = fmt.Sprintf("%s: %v", msg, e.Err)
}

return msg
}

// Is enables usage of errors.Is() to determine the kind of error that occurred.
func (e *ErrCast) Is(err error) bool {
return errors.Is(err, e.Kind)
}

// Unwrap returns the underlying error for usage with errors.Unwrap().
func (e *ErrCast) Unwrap() error { return e.Err }
64 changes: 64 additions & 0 deletions pkg/cast/golang/toolchain/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package toolchain

import (
"github.com/magefile/mage/mg"
)

const (
// CasterName is the name of the Go toolchain command caster.
CasterName = "golang"

// DefaultEnvVarGO111MODULE is the default environment variable name to toggle the Go 1.11 module mode.
DefaultEnvVarGO111MODULE = "GO111MODULE"

// DefaultEnvVarGOBIN is the default environment variable name for the Go binary executable path.
DefaultEnvVarGOBIN = "GOBIN"

// DefaultEnvVarGOFLAGS is the default environment variable name for Go tool flags.
DefaultEnvVarGOFLAGS = "GOFLAGS"

// DefaultEnvVarGOPATH is the default environment variable name for the Go path.
DefaultEnvVarGOPATH = "GOPATH"
)

// DefaultExec is the default path to the Go executable.
var DefaultExec = mg.GoCmd()

// Options stores Go toolchain command caster options.
type Options struct {
Env map[string]string
Exec string
}

// Option is a Go toolchain command caster option.
type Option func(*Options)

// WithEnv sets the caster environment.
func WithEnv(env map[string]string) Option {
return func(o *Options) {
o.Env = env
}
}

// WithExec sets the path to the Go executable.
func WithExec(nameOrPath string) Option {
return func(o *Options) {
o.Exec = nameOrPath
}
}

// newOptions creates new Go toolchain command caster options.
func newOptions(opts ...Option) *Options {
opt := &Options{
Env: make(map[string]string),
Exec: DefaultExec,
}
for _, o := range opts {
o(opt)
}

return opt
}
89 changes: 89 additions & 0 deletions pkg/cast/golang/toolchain/toolchain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package toolchain

import (
"fmt"
"os/exec"

"github.com/magefile/mage/sh"
glFS "github.com/svengreb/golib/pkg/io/fs"

"github.com/svengreb/wand/pkg/cast"
"github.com/svengreb/wand/pkg/spell"
)

// Caster is a Go toolchain command caster.
type Caster struct {
opts *Options
}

// GetExec returns the path to the binary executable.
func (c *Caster) GetExec() string {
return c.opts.Exec
}

// Cast casts a spell incantation.
// It returns an error of type *cast.ErrCast when the spell is not a spell.KindBinary and any other error that occurs
// during the command execution.
func (c *Caster) Cast(si spell.Incantation) error {
if si.Kind() != spell.KindBinary {
return &cast.ErrCast{
Err: fmt.Errorf("%q", si.Kind()),
Kind: cast.ErrCasterSpellIncantationKindUnsupported,
}
}

s, ok := si.(spell.Binary)
if !ok {
return &cast.ErrCast{
Err: fmt.Errorf("expected %q but got %q", s.Kind(), si.Kind()),
Kind: cast.ErrCasterSpellIncantationKindUnsupported,
}
}

args := si.Formula()
for k, v := range s.Env() {
c.opts.Env[k] = v
}

return sh.RunWithV(c.opts.Env, c.opts.Exec, args...)
}

// Handles returns the supported spell.Kind.
func (c *Caster) Handles() spell.Kind {
return spell.KindBinary
}

// Validate validates the Go toolchain command caster.
// It returns an error of type *cast.ErrCast when the binary executable does not exists at the configured path and when
// it is also not available in the executable search paths of the current environment.
func (c *Caster) Validate() error {
// Check if the Go executable exists,...
execExits, fsErr := glFS.FileExists(c.opts.Exec)
if fsErr != nil {
return &cast.ErrCast{
Err: fmt.Errorf("caster %q: %w", CasterName, fsErr),
Kind: cast.ErrCasterValidation,
}
}
// ...otherwise try to look up the system-wide executable paths.
if !execExits {
path, pathErr := exec.LookPath(c.opts.Exec)
if pathErr != nil {
return &cast.ErrCast{
Err: fmt.Errorf("caster %q: %q not found or does not exist: %w", CasterName, c.opts.Exec, pathErr),
Kind: cast.ErrCasterValidation,
}
}
c.opts.Exec = path
}

return nil
}

// NewCaster creates a new Go toolchain command caster.
func NewCaster(opts ...Option) *Caster {
return &Caster{opts: newOptions(opts...)}
}
12 changes: 12 additions & 0 deletions pkg/error/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package error provides types and utilities to handle errors.
package error

// ErrString is a string type for implementing constant errors.
type ErrString string

func (e ErrString) Error() string {
return string(e)
}
19 changes: 7 additions & 12 deletions pkg/project/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,24 @@ package project
import (
"errors"
"fmt"

wErr "github.com/svengreb/wand/pkg/error"
)

const (
// ErrDeriveVCSInformation indicates that the derivation of VCS version information failed.
ErrDeriveVCSInformation = ErrString("failed to derive VCS version information")
ErrDeriveVCSInformation = wErr.ErrString("failed to derive VCS version information")

// ErrDetectProjectRootDirPath indicates that the detection of a project root directory path failed.
ErrDetectProjectRootDirPath = ErrString("failed to detect project root directory path")
ErrDetectProjectRootDirPath = wErr.ErrString("failed to detect project root directory path")

// ErrDetermineGoModuleInformation indicates that a determination of Go module information failed.
ErrDetermineGoModuleInformation = ErrString("failed to determine Go module information")
ErrDetermineGoModuleInformation = wErr.ErrString("failed to determine Go module information")

// ErrPathNotRelative indicates that a path is not releative.
ErrPathNotRelative = ErrString("path is not relative")
// ErrPathNotRelative indicates that a path is not relative.
ErrPathNotRelative = wErr.ErrString("path is not relative")
)

// ErrString is a string type for implementing constant errors.
type ErrString string

func (e ErrString) Error() string {
return string(e)
}

// ErrProject represents a project error.
type ErrProject struct {
// Err is a wrapped error.
Expand Down
6 changes: 6 additions & 0 deletions pkg/project/vcs/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package vcs provides packages utilities to interact with version control systems.
// See https://en.wikipedia.org/wiki/Version_control for more details about VCS.
package vcs
2 changes: 2 additions & 0 deletions pkg/project/vcs/git/git.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package git provides VCS utility functions to interact with Git repositories.
// See https://git-scm.com for more details about Git.
package git

import (
Expand Down
1 change: 1 addition & 0 deletions pkg/spell/spell.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package spell provides incantations for different kinds.
package spell

import "github.com/svengreb/wand/pkg/project"
Expand Down