github.com/bir3/gocompiler@v0.9.2202/exec/exec.go (about) 1 package exec 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 "time" 12 ) 13 14 type process struct { 15 c *Cmd 16 } 17 type processState struct { 18 c *Cmd 19 } 20 21 type Cmd struct { 22 // Path is the path of the command to run. 23 // 24 // This is the only field that must be set to a non-zero 25 // value. If Path is relative, it is evaluated relative 26 // to Dir. 27 Path string 28 29 // Args holds command line arguments, including the command as Args[0]. 30 // If the Args field is empty or nil, Run uses {Path}. 31 // 32 // In typical use, both Path and Args are set by calling Command. 33 Dir string 34 Args []string 35 Env []string 36 Stdin io.Reader 37 Stdout io.Writer 38 Stderr io.Writer 39 40 Cancel func() error 41 WaitDelay time.Duration 42 Process process 43 ProcessState *processState 44 45 realCmd *exec.Cmd 46 47 isCommand bool 48 isCommandContext bool 49 ctx context.Context 50 name string 51 arg []string 52 53 isTool bool // temp debug 54 } 55 56 // type Cmd = exec.Cmd 57 type ExitError = exec.ExitError 58 type Signal = os.Signal 59 60 var ErrWaitDelay error = exec.ErrWaitDelay 61 62 func (p process) Signal(sig Signal) error { 63 return p.c.realCmd.Process.Signal(sig) 64 } 65 func (p process) Kill() error { 66 return p.c.realCmd.Process.Kill() 67 } 68 69 func (p processState) Success() bool { 70 return p.c.realCmd.ProcessState.Success() 71 } 72 func (p processState) UserTime() time.Duration { 73 return p.c.realCmd.ProcessState.UserTime() 74 } 75 func (p processState) SystemTime() time.Duration { 76 return p.c.realCmd.ProcessState.SystemTime() 77 } 78 79 func Command(name string, arg ...string) *Cmd { 80 cmd := &Cmd{name: name, arg: arg, isCommand: true, 81 Path: name, 82 Args: append([]string{name}, arg...), 83 } 84 if filepath.Base(name) == name { 85 lp, _ := LookPath(name) 86 if lp != "" { 87 // Update cmd.Path even if err is non-nil. 88 // If err is ErrDot (especially on Windows), lp may include a resolved 89 // extension (like .exe or .bat) that should be preserved. 90 cmd.Path = lp 91 } 92 //if err != nil { 93 // cmd.Err = err 94 //} 95 } 96 return cmd 97 } 98 99 func CommandContext(ctx context.Context, name string, arg ...string) *Cmd { 100 cmd := Command(name, arg...) 101 cmd.ctx = ctx 102 return cmd 103 104 } 105 106 func isTool(file string) string { 107 // input: $GOROOT/pkg/tool/darwin_arm64/compile 108 // - assume GOROOT is absolute path 109 if strings.HasPrefix(file, os.Getenv("GOROOT")) { 110 dir_os_arch := filepath.Dir(file) // pkg/tool/darwin_arm64 111 dir_tool := filepath.Dir(dir_os_arch) // pkg/tool 112 113 if filepath.Base(dir_tool) == "tool" { 114 return filepath.Base(file) 115 } 116 } 117 return "" 118 } 119 120 func LookPath(file string) (string, error) { 121 122 if isTool(file) != "" { 123 124 return file, nil 125 } 126 path, err := exec.LookPath(file) 127 128 return path, err 129 } 130 131 func (c *Cmd) Run() error { 132 c.mirror() 133 err := c.realCmd.Run() 134 return err 135 } 136 func (c *Cmd) Environ() []string { 137 c.mirror() 138 return c.realCmd.Environ() 139 } 140 141 func (c *Cmd) mirror() { 142 if c.isCommand { 143 c.realCmd = exec.Command(c.name, c.arg...) 144 } else if c.isCommandContext { 145 c.realCmd = exec.CommandContext(c.ctx, c.name, c.arg...) 146 } else { 147 // manually constructed by the user 148 c.realCmd = &exec.Cmd{} 149 c.realCmd.Path = c.Path 150 c.realCmd.Args = c.Args 151 } 152 153 c.realCmd.Dir = c.Dir 154 c.realCmd.Env = c.Env 155 c.realCmd.Stdin = c.Stdin 156 c.realCmd.Stdout = c.Stdout 157 c.realCmd.Stderr = c.Stderr 158 c.realCmd.Cancel = c.Cancel 159 c.realCmd.WaitDelay = c.WaitDelay 160 c.Process.c = c 161 if c.ProcessState == nil { 162 c.ProcessState = &processState{} 163 } 164 c.ProcessState.c = c 165 166 // fork/exec $GOROOT/pkg/tool/darwin_arm64/compile: no such file or directory 167 tool := isTool(c.Path) 168 169 if tool != "" { 170 // BUG: could add multiple times since shared var 171 if c.realCmd.Env == nil { 172 c.realCmd.Env = os.Environ() 173 } 174 c.realCmd.Env = append(c.realCmd.Env, fmt.Sprintf("BIR3_GOCOMPILER_TOOL=%s", tool)) 175 c.realCmd.Path, _ = os.Executable() 176 c.realCmd.Args[0], _ = os.Executable() 177 c.isTool = true 178 } 179 } 180 181 func (c *Cmd) Start() error { 182 c.mirror() 183 return c.realCmd.Start() 184 } 185 func (c *Cmd) Wait() error { 186 c.mirror() 187 return c.realCmd.Wait() 188 } 189 func (c *Cmd) CombinedOutput() ([]byte, error) { 190 c.mirror() 191 buf, err := c.realCmd.CombinedOutput() 192 193 return buf, err 194 195 } 196 func (c *Cmd) Output() ([]byte, error) { 197 c.mirror() 198 buf, err := c.realCmd.Output() 199 200 return buf, err 201 } 202 func (c *Cmd) StdoutPipe() (io.ReadCloser, error) { 203 c.mirror() 204 return c.realCmd.StdoutPipe() 205 } 206 func (c *Cmd) StdinPipe() (io.WriteCloser, error) { 207 c.mirror() 208 return c.realCmd.StdinPipe() 209 } 210 func (c *Cmd) StderrPipe() (io.ReadCloser, error) { 211 c.mirror() 212 return c.realCmd.StderrPipe() 213 } 214 215 /* 216 217 # github.com/bir3/gocompiler/src/cmd/cgo 218 ../gocompiler/src/cmd/cgo/util.go:64:25: undefined: exec.ExitError 219 # github.com/bir3/gocompiler/src/cmd/gocmd/internal/cache 220 ../gocompiler/src/cmd/gocmd/internal/cache/prog.go:164:14: undefined: exec.CommandContext 221 # github.com/bir3/gocompiler/src/cmd/gocmd/internal/tool 222 ../gocompiler/src/cmd/gocmd/internal/tool/tool.go:128:26: undefined: exec.ExitError 223 # github.com/bir3/gocompiler/src/cmd/gocmd/internal/vcs 224 ../gocompiler/src/cmd/gocmd/internal/vcs/vcs.go:699:28: undefined: exec.ExitError 225 # github.com/bir3/gocompiler/src/cmd/gocmd/internal/modfetch/codehost 226 ../gocompiler/src/cmd/gocmd/internal/modfetch/codehost/codehost.go:363:12: undefined: exec.CommandContext 227 ../gocompiler/src/cmd/gocmd/internal/modfetch/codehost/git.go:844:42: undefined: exec.ExitError 228 # github.com/bir3/gocompiler/src/cmd/compile/internal/importer 229 ../gocompiler/src/cmd/compile/internal/importer/gcimporter.go:50:30: undefined: exec.ExitError 230 231 232 */