Added parallel computing V2

master
Paulo Simão 2021-10-25 18:44:30 -03:00
parent 2195674c08
commit dfdb8f0c50
3 changed files with 94 additions and 43 deletions

View File

@ -20,17 +20,20 @@ import "os"
import "os/exec" import "os/exec"
func main() { func main() {
err := shelly.Exec(` _, err := shelly.Exec(`
ls -larth & ls -larth &
pwd & pwd &
whoami whoami
date`, &shelly.Opts{ date`,
Await: false, &shelly.Opts{
SetupProc: func(cmd *exec.Cmd) { Await: false,
cmd.Stdout = os.Stdout Wd: ".",
cmd.Stderr = os.Stderr Debug: true,
}, SetupProc: func(cmd *exec.Cmd) {
}) cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
},
})
if err != nil { if err != nil {
panic(err) panic(err)
} }

116
lib.go
View File

@ -4,10 +4,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"os"
"os/exec" "os/exec"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync"
) )
type Mode int type Mode int
@ -40,13 +41,16 @@ const (
LINETYPE_SERIAL LineType = iota LINETYPE_SERIAL LineType = iota
LINETYPE_PARALLEL LINETYPE_PARALLEL
LINETYPE_COMMENT LINETYPE_COMMENT
LINETYPE_CHANGEWD
) )
// Opts tells Exec how to behave // Opts tells Exec how to behave
// Opts.Await will inform whether its call should wait for all parallel started jobs to finish // Opts.Await will inform whether its call should wait for all parallel started jobs to finish
// Opts.SetupProc will customize the processes before execution - Eg.: setting out and err // Opts.SetupProc will customize the processes before execution - Eg.: setting out and err
type Opts struct { type Opts struct {
Debug bool
Await bool Await bool
Wd string
SetupProc func(cmd *exec.Cmd) SetupProc func(cmd *exec.Cmd)
} }
@ -158,28 +162,32 @@ func Lines(str string) ([]Line, error) {
Tokens: make([]string, 0), Tokens: make([]string, 0),
LType: LINETYPE_SERIAL, LType: LINETYPE_SERIAL,
} }
addLine := func(line Line) {
if strings.HasPrefix(line.Tokens[0], "#") {
line.LType = LINETYPE_COMMENT
}
if line.Tokens[0] == "cd" {
line.LType = LINETYPE_CHANGEWD
}
ret = append(ret, line)
}
for _, tk := range tokens { for _, tk := range tokens {
switch tk { switch tk {
case "\n", ";": case "\n", ";", "&&":
if len(line.Tokens) > 0 { if len(line.Tokens) > 0 {
if strings.HasPrefix(line.Tokens[0], "#") { addLine(line)
line.LType = LINETYPE_COMMENT
} else {
line.LType = LINETYPE_SERIAL
}
ret = append(ret, line)
} }
line = Line{ line = Line{
Tokens: make([]string, 0), Tokens: make([]string, 0),
} }
case "&": case "&":
if len(line.Tokens) > 0 { if len(line.Tokens) > 0 {
if strings.HasPrefix(line.Tokens[0], "#") { line.LType = LINETYPE_PARALLEL
line.LType = LINETYPE_COMMENT addLine(line)
} else {
line.LType = LINETYPE_PARALLEL
}
ret = append(ret, line)
} }
line = Line{ line = Line{
Tokens: make([]string, 0), Tokens: make([]string, 0),
@ -189,15 +197,26 @@ func Lines(str string) ([]Line, error) {
} }
} }
if len(line.Tokens) > 0 { if len(line.Tokens) > 0 {
ret = append(ret, line) addLine(line)
} }
return ret, nil return ret, nil
} }
// Exec will call Lines and execute one by one // Exec will call Lines and execute one by one
func Exec(str string, opts ...*Opts) error { func Exec(str string, opts ...*Opts) ([]*exec.Cmd, error) {
wd, err := os.Getwd()
var opt *Opts if err != nil {
return nil, err
}
ret := make([]*exec.Cmd, 0)
opt := &Opts{
Await: false,
Wd: wd,
SetupProc: func(cmd *exec.Cmd) {
},
}
if opts != nil && len(opts) > 0 { if opts != nil && len(opts) > 0 {
opt = opts[0] opt = opts[0]
} }
@ -208,47 +227,74 @@ func Exec(str string, opts ...*Opts) error {
} }
} }
cmdwd := opt.Wd
prepCmd := func(l Line) *exec.Cmd { prepCmd := func(l Line) *exec.Cmd {
cmd := exec.Command(l.Tokens[0], l.Tokens[1:]...) cmd := exec.Command(l.Tokens[0], l.Tokens[1:]...)
cmd.Stdout = log.Writer() cmd.Stdout = log.Writer()
cmd.Stderr = log.Writer() cmd.Stderr = log.Writer()
cmd.Dir = cmdwd
opt.SetupProc(cmd) opt.SetupProc(cmd)
ret = append(ret, cmd)
return cmd return cmd
} }
wg := sync.WaitGroup{}
lines, err := Lines(str) lines, err := Lines(str)
if err != nil { if err != nil {
return err return ret, err
} }
for _, l := range lines { for _, l := range lines {
switch l.LType { switch l.LType {
case LINETYPE_COMMENT: case LINETYPE_COMMENT:
continue continue
case LINETYPE_CHANGEWD:
if filepath.IsAbs(l.Tokens[1]) {
cmdwd = l.Tokens[1]
} else {
cmdwd, err = filepath.Abs(filepath.Join(wd, l.Tokens[1]))
if err != nil {
return nil, err
}
}
log.Printf("CMDWD NOW IS: %s", cmdwd)
continue
case LINETYPE_SERIAL: case LINETYPE_SERIAL:
cmd := prepCmd(l) cmd := prepCmd(l)
if opt.Debug {
log.Printf("Running %s from dir %s with params %v", cmd.Path, cmd.Dir, cmd.Args)
}
err = cmd.Run() err = cmd.Run()
if opt.Debug {
if err != nil {
log.Printf("Error running: %s: %s", cmd.Path, err.Error())
}
}
if err != nil { if err != nil {
return err return ret, err
} }
case LINETYPE_PARALLEL: case LINETYPE_PARALLEL:
go func(l Line) { cmd := prepCmd(l)
cmd := prepCmd(l)
wg.Add(1) if opt.Debug {
err = cmd.Run() log.Printf("Running %s from dir %s with params %v", cmd.Path, cmd.Dir, cmd.Args)
wg.Done() }
}(l) err = cmd.Start()
if opt.Debug {
if err != nil {
log.Printf("Error running: %s: %s", cmd.Path, err.Error())
}
}
if err != nil {
return nil, err
}
} }
} }
if opt.Await { return ret, err
wg.Wait()
}
return nil
} }
@ -256,9 +302,11 @@ func Exec(str string, opts ...*Opts) error {
func Kill(p int) error { func Kill(p int) error {
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
return Exec(fmt.Sprintf("taskkill /T /F /PID %d", p)) _, err := Exec(fmt.Sprintf("taskkill /T /F /PID %d", p))
return err
default: default:
return Exec(fmt.Sprintf("kill -9 %d", p)) _, err := Exec(fmt.Sprintf("kill -9 %d", p))
return err
} }
} }

View File

@ -27,7 +27,7 @@ func TestLines(t *testing.T) {
} }
func TestExec(t *testing.T) { func TestExec(t *testing.T) {
err := Exec(` _, err := Exec(`
ls -larth & ls -larth &
pwd & pwd &
whoami & whoami &