cuelang.org/go@v0.10.1/pkg/tool/exec/exec.go (about) 1 // Copyright 2019 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package exec 16 17 import ( 18 "fmt" 19 "os/exec" 20 "strings" 21 22 "cuelang.org/go/cue" 23 "cuelang.org/go/cue/errors" 24 "cuelang.org/go/internal/task" 25 ) 26 27 func init() { 28 task.Register("tool/exec.Run", newExecCmd) 29 30 // For backwards compatibility. 31 task.Register("exec", newExecCmd) 32 } 33 34 type execCmd struct{} 35 36 func newExecCmd(v cue.Value) (task.Runner, error) { 37 return &execCmd{}, nil 38 } 39 40 func (c *execCmd) Run(ctx *task.Context) (res interface{}, err error) { 41 cmd, doc, err := mkCommand(ctx) 42 if err != nil { 43 return cue.Value{}, err 44 } 45 46 // TODO: set environment variables, if defined. 47 stream := func(name string) (stream cue.Value, ok bool) { 48 c := ctx.Obj.LookupPath(cue.ParsePath(name)) 49 if err := c.Null(); c.Err() != nil || err == nil { 50 return 51 } 52 return c, true 53 } 54 55 if v, ok := stream("stdin"); !ok { 56 cmd.Stdin = ctx.Stdin 57 } else if cmd.Stdin, err = v.Reader(); err != nil { 58 return nil, errors.Wrapf(err, v.Pos(), "invalid input") 59 } 60 _, captureOut := stream("stdout") 61 if !captureOut { 62 cmd.Stdout = ctx.Stdout 63 } 64 _, captureErr := stream("stderr") 65 if !captureErr { 66 cmd.Stderr = ctx.Stderr 67 } 68 69 v := ctx.Obj.LookupPath(cue.ParsePath("mustSucceed")) 70 mustSucceed, err := v.Bool() 71 if err != nil { 72 return nil, errors.Wrapf(err, v.Pos(), "invalid bool value") 73 } 74 75 update := map[string]interface{}{} 76 if captureOut { 77 var stdout []byte 78 stdout, err = cmd.Output() 79 update["stdout"] = string(stdout) 80 } else { 81 err = cmd.Run() 82 } 83 update["success"] = err == nil 84 85 if err == nil { 86 return update, nil 87 } 88 89 if captureErr { 90 if exit := (*exec.ExitError)(nil); errors.As(err, &exit) { 91 update["stderr"] = string(exit.Stderr) 92 } else { 93 update["stderr"] = err.Error() 94 } 95 } 96 97 if !mustSucceed { 98 return update, nil 99 } 100 101 return nil, fmt.Errorf("command %q failed: %v", doc, err) 102 } 103 104 // mkCommand builds an [exec.Cmd] from a CUE task value, 105 // also returning the full list of arguments as a string slice 106 // so that it can be used in error messages. 107 func mkCommand(ctx *task.Context) (c *exec.Cmd, doc []string, err error) { 108 v := ctx.Lookup("cmd") 109 if ctx.Err != nil { 110 return nil, nil, ctx.Err 111 } 112 113 var bin string 114 var args []string 115 switch v.Kind() { 116 case cue.StringKind: 117 str, _ := v.String() 118 list := strings.Fields(str) 119 bin, args = list[0], list[1:] 120 121 case cue.ListKind: 122 list, _ := v.List() 123 if !list.Next() { 124 return nil, nil, errors.New("empty command list") 125 } 126 bin, err = list.Value().String() 127 if err != nil { 128 return nil, nil, err 129 } 130 for list.Next() { 131 str, err := list.Value().String() 132 if err != nil { 133 return nil, nil, err 134 } 135 args = append(args, str) 136 } 137 } 138 139 if bin == "" { 140 return nil, nil, errors.New("empty command") 141 } 142 143 cmd := exec.CommandContext(ctx.Context, bin, args...) 144 145 cmd.Dir, _ = ctx.Obj.LookupPath(cue.ParsePath("dir")).String() 146 147 env := ctx.Obj.LookupPath(cue.ParsePath("env")) 148 149 // List case. 150 for iter, _ := env.List(); iter.Next(); { 151 v, _ := iter.Value().Default() 152 str, err := v.String() 153 if err != nil { 154 return nil, nil, errors.Wrapf(err, v.Pos(), 155 "invalid environment variable value %q", v) 156 } 157 cmd.Env = append(cmd.Env, str) 158 } 159 160 // Struct case. 161 for iter, _ := env.Fields(); iter.Next(); { 162 label := iter.Label() 163 v, _ := iter.Value().Default() 164 var str string 165 switch v.Kind() { 166 case cue.StringKind: 167 str, _ = v.String() 168 case cue.IntKind, cue.FloatKind, cue.NumberKind: 169 str = fmt.Sprint(v) 170 default: 171 return nil, nil, errors.Newf(v.Pos(), 172 "invalid environment variable value %q", v) 173 } 174 cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", label, str)) 175 } 176 177 return cmd, append([]string{bin}, args...), nil 178 }