mirror of
https://github.com/go-task/task.git
synced 2025-01-04 03:48:02 +02:00
Migrate from os/exec.Cmd to a native Go sh interpreter
github.com/mvdan/sh Closes #23
This commit is contained in:
parent
e327dab695
commit
6bc27baa96
@ -2,22 +2,51 @@ package execext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mvdan/sh/interp"
|
||||
"github.com/mvdan/sh/syntax"
|
||||
)
|
||||
|
||||
type RunCommandOptions struct {
|
||||
Context context.Context
|
||||
Command string
|
||||
Dir string
|
||||
Env []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
var (
|
||||
// ShPath is path to "sh" command
|
||||
ShPath string
|
||||
// ShExists is true if "sh" command is available on the system
|
||||
ShExists bool
|
||||
// ErrNilOptions is returned when a nil options is given
|
||||
ErrNilOptions = errors.New("execext: nil options given")
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
ShPath, err = exec.LookPath("sh")
|
||||
ShExists = err == nil
|
||||
}
|
||||
// RunCommand runs a shell command
|
||||
func RunCommand(opts *RunCommandOptions) error {
|
||||
if opts == nil {
|
||||
return ErrNilOptions
|
||||
}
|
||||
|
||||
func newShCommand(ctx context.Context, c string) *exec.Cmd {
|
||||
return exec.CommandContext(ctx, ShPath, "-c", c)
|
||||
p, err := syntax.Parse(strings.NewReader(opts.Command), "", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := interp.Runner{
|
||||
Context: opts.Context,
|
||||
File: p,
|
||||
Dir: opts.Dir,
|
||||
Env: opts.Env,
|
||||
Stdin: opts.Stdin,
|
||||
Stdout: opts.Stdout,
|
||||
Stderr: opts.Stderr,
|
||||
}
|
||||
if err = r.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package execext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// NewCommand returns a new command that runs on "sh" is available or on "cmd"
|
||||
// otherwise on Windows
|
||||
func NewCommand(ctx context.Context, c string) *exec.Cmd {
|
||||
return newShCommand(ctx, c)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package execext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// NewCommand returns a new command that runs on "sh" is available or on "cmd"
|
||||
// otherwise on Windows
|
||||
func NewCommand(ctx context.Context, c string) *exec.Cmd {
|
||||
if ShExists {
|
||||
return newShCommand(ctx, c)
|
||||
}
|
||||
return newCmdCommand(ctx, c)
|
||||
}
|
||||
|
||||
func newCmdCommand(ctx context.Context, c string) *exec.Cmd {
|
||||
return exec.CommandContext(ctx, "cmd", "/C", c)
|
||||
}
|
73
task.go
73
task.go
@ -1,6 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
@ -177,38 +178,54 @@ func (t *Task) runCommand(ctx context.Context, i int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := execext.NewCommand(ctx, c)
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
|
||||
envs, err := t.getEnviron(vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.Env != nil {
|
||||
cmd.Env = os.Environ()
|
||||
for key, value := range t.Env {
|
||||
replacedValue, err := ReplaceVariables(value, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
replacedKey, err := ReplaceVariables(key, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", replacedKey, replacedValue))
|
||||
}
|
||||
opts := &execext.RunCommandOptions{
|
||||
Context: ctx,
|
||||
Command: c,
|
||||
Dir: dir,
|
||||
Env: envs,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
if t.Set != "" {
|
||||
bytes, err := cmd.Output()
|
||||
if err != nil {
|
||||
|
||||
if t.Set == "" {
|
||||
log.Println(c)
|
||||
opts.Stdout = os.Stdout
|
||||
if err = execext.RunCommand(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Setenv(t.Set, strings.TrimSpace(string(bytes)))
|
||||
return nil
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
log.Println(c)
|
||||
if err = cmd.Run(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
buff := bytes.NewBuffer(nil)
|
||||
opts.Stdout = buff
|
||||
if err = execext.RunCommand(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Setenv(t.Set, strings.TrimSpace(buff.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) getEnviron(vars map[string]string) ([]string, error) {
|
||||
if t.Env == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
envs := os.Environ()
|
||||
|
||||
for k, v := range t.Env {
|
||||
replacedValue, err := ReplaceVariables(v, vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
replacedKey, err := ReplaceVariables(k, vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envs = append(envs, fmt.Sprintf("%s=%s", replacedKey, replacedValue))
|
||||
}
|
||||
return envs, nil
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package task
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
@ -35,19 +34,25 @@ func handleDynamicVariableContent(value string) (string, error) {
|
||||
if result, ok := varCmds[value]; ok {
|
||||
return result, nil
|
||||
}
|
||||
cmd := execext.NewCommand(context.Background(), value[1:])
|
||||
cmd.Stderr = os.Stderr
|
||||
b, err := cmd.Output()
|
||||
if err != nil {
|
||||
|
||||
buff := bytes.NewBuffer(nil)
|
||||
|
||||
opts := &execext.RunCommandOptions{
|
||||
Command: strings.TrimPrefix(value, "$"),
|
||||
Stdout: buff,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
if err := execext.RunCommand(opts); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if b[len(b)-1] == '\n' {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
if bytes.ContainsRune(b, '\n') {
|
||||
|
||||
result := buff.String()
|
||||
result = strings.TrimSuffix(result, "\n")
|
||||
if strings.ContainsRune(result, '\n') {
|
||||
return "", ErrMultilineResultCmd
|
||||
}
|
||||
result := strings.TrimSpace(string(b))
|
||||
|
||||
result = strings.TrimSpace(result)
|
||||
varCmds[value] = result
|
||||
return result, nil
|
||||
}
|
||||
@ -84,17 +89,12 @@ func init() {
|
||||
taskFuncs := template.FuncMap{
|
||||
"OS": func() string { return runtime.GOOS },
|
||||
"ARCH": func() string { return runtime.GOARCH },
|
||||
"IsSH": func() bool { return execext.ShExists },
|
||||
// historical reasons
|
||||
"IsSH": func() bool { return true },
|
||||
"FromSlash": func(path string) string {
|
||||
if execext.ShExists {
|
||||
return path
|
||||
}
|
||||
return filepath.FromSlash(path)
|
||||
},
|
||||
"ToSlash": func(path string) string {
|
||||
if execext.ShExists {
|
||||
return path
|
||||
}
|
||||
return filepath.ToSlash(path)
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user