github.com/sandwich-go/boost@v1.3.29/xproc/process.go (about)

     1  package xproc
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"runtime"
     8  	"strings"
     9  
    10  	"github.com/sandwich-go/boost/internal/log"
    11  	"github.com/sandwich-go/boost/xos"
    12  	"github.com/sandwich-go/boost/xslice"
    13  )
    14  
    15  const envKeyPPid = "GO_PROC_PPID"
    16  
    17  type Process struct {
    18  	cc *ProcessOptions
    19  	exec.Cmd
    20  	Manager *Manager
    21  	PPid    int
    22  }
    23  
    24  // NewProcess creates and returns a new Process.
    25  func NewProcessWithOptions(path string, cc *ProcessOptions) *Process {
    26  	process := &Process{
    27  		cc:      cc,
    28  		Manager: nil,
    29  		PPid:    os.Getpid(),
    30  		Cmd: exec.Cmd{
    31  			Args:       []string{path},
    32  			Path:       path,
    33  			Stdin:      cc.Stdin,
    34  			Stdout:     cc.Stdout,
    35  			Stderr:     cc.Stderr,
    36  			Env:        append(os.Environ(), cc.Env...),
    37  			Dir:        cc.WorkingDir,
    38  			ExtraFiles: make([]*os.File, 0), // not support on windows
    39  		},
    40  	}
    41  	if len(cc.Args) > 0 {
    42  		start := 0
    43  		if strings.EqualFold(path, cc.Args[0]) {
    44  			start = 1
    45  		}
    46  		process.Args = append(process.Args, cc.Args[start:]...)
    47  	}
    48  	return process
    49  }
    50  
    51  // NewProcess creates and returns a new Process.
    52  func NewProcess(path string, opt ...ProcessOption) *Process {
    53  	return NewProcessWithOptions(path, NewProcessOptions(opt...))
    54  }
    55  
    56  // NewProcessShellCmdWithOptions creates and returns a process with given command and optional environment variable array.
    57  func NewProcessShellCmdWithOptions(cmd string, cc *ProcessOptions) *Process {
    58  	argsLen := len(cc.Args)
    59  	cc.Args = xslice.StringsSetAdd(parseCommand(cmd), cc.Args...)
    60  	if argsLen == 0 {
    61  		cc.Args = xslice.StringsSetAdd([]string{xos.GetShellOption()}, cc.Args...)
    62  	}
    63  	return NewProcessWithOptions(xos.GetShell(), cc)
    64  }
    65  
    66  // Start starts executing the process in non-blocking way.
    67  // It returns the pid if success, or else it returns an error.
    68  func (p *Process) Start() (int, error) {
    69  	if p.Process != nil {
    70  		return p.Pid(), nil
    71  	}
    72  	p.Env = append(p.Env, fmt.Sprintf("%s=%d", envKeyPPid, p.PPid))
    73  	if err := p.Cmd.Start(); err == nil {
    74  		if p.Manager != nil {
    75  			p.Manager.processes.Store(p.Process.Pid, p)
    76  		}
    77  		return p.Process.Pid, nil
    78  	} else {
    79  		return 0, err
    80  	}
    81  }
    82  
    83  // Run executes the process in blocking way.
    84  func (p *Process) Run() error {
    85  	if _, err := p.Start(); err == nil {
    86  		return p.Wait()
    87  	} else {
    88  		return err
    89  	}
    90  }
    91  
    92  func (p *Process) Pid() int {
    93  	if p.Process != nil {
    94  		return p.Process.Pid
    95  	}
    96  	return 0
    97  }
    98  
    99  func (p *Process) Release() error {
   100  	return p.Process.Release()
   101  }
   102  
   103  // Kill causes the Process to exit immediately.
   104  func (p *Process) Kill() (err error) {
   105  	if err = p.Process.Kill(); err != nil {
   106  		return fmt.Errorf("Kill got err:%w", err)
   107  	}
   108  	if p.Manager != nil {
   109  		p.Manager.processes.Delete(p.Pid())
   110  	}
   111  	if runtime.GOOS != "windows" {
   112  		if err = p.Process.Release(); err != nil {
   113  			return fmt.Errorf("Release got err:%w", err)
   114  		}
   115  	}
   116  	// ignores this error, just log it.
   117  	_, err = p.Process.Wait()
   118  	if err != nil {
   119  		log.Error(fmt.Sprintf("Wait got err:%s", err.Error()))
   120  	}
   121  	return nil
   122  }
   123  
   124  // Signal sends a signal to the Process.
   125  // Sending Interrupt on Windows is not implemented.
   126  func (p *Process) Signal(sig os.Signal) error {
   127  	return p.Process.Signal(sig)
   128  }