1
0
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:
Andrey Nering 2017-04-22 15:46:29 -03:00
parent e327dab695
commit 6bc27baa96
5 changed files with 103 additions and 92 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
View File

@ -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
}

View File

@ -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)
},
}