3636lineParallelTemplate , _ = template .New ("parallel line" ).Parse (` {{.Status}} ` + color .Reset + ` {{printf "%1s" .Prefix}} ├─ {{printf "%-25s" .Title}} {{.Msg}}{{.Split}}{{.Eta}}` )
3737
3838// lineLastParallelTemplate is the string template used to display the status values of a task that is the LAST child of another task
39- lineLastParallelTemplate , _ = template .New ("last parallel line" ).Parse (` {{.Status}} ` + color .Reset + ` {{printf "%1s" .Prefix}} ╰ ─ {{printf "%-25s" .Title}} {{.Msg}}{{.Split}}{{.Eta}}` )
39+ lineLastParallelTemplate , _ = template .New ("last parallel line" ).Parse (` {{.Status}} ` + color .Reset + ` {{printf "%1s" .Prefix}} └ ─ {{printf "%-25s" .Title}} {{.Msg}}{{.Split}}{{.Eta}}` )
4040)
4141
4242// TaskStats is a global struct keeping track of the number of running tasks, failed tasks, completed tasks, and total tasks
@@ -130,6 +130,12 @@ type TaskCommand struct {
130130
131131// ReturnCode is simply the value returned from the child process after Cmd execution
132132ReturnCode int
133+
134+ // EnvReadFile is an extra pipe given to the child shell process for exfiltrating env vars back up to bashful (to provide as input for future tasks)
135+ EnvReadFile * os.File
136+
137+ // Environment is a list of env vars from the exited child process
138+ Environment map [string ]string
133139}
134140
135141// CommandStatus represents whether a task command is about to run, already running, or has completed (in which case, was it successful or not)
@@ -252,12 +258,20 @@ func (task *Task) inflateCmd() {
252258shell = "sh"
253259}
254260
255- task .Command .Cmd = exec .Command (shell , "-c" , fmt .Sprintf ("\" %q\" " , task .Config .CmdString ))
261+ readFd , writeFd , err := os .Pipe ()
262+ CheckError (err , "Could not open env pipe for child shell" )
263+
264+ task .Command .Cmd = exec .Command (shell , "-c" , task .Config .CmdString + "; env >&3" )
265+
266+ // allow the child process to provide env vars via a pipe (FD3)
267+ task .Command .Cmd .ExtraFiles = []* os.File {writeFd }
268+ task .Command .EnvReadFile = readFd
256269
257270// set this command as a process group
258271task .Command .Cmd .SysProcAttr = & syscall.SysProcAttr {Setpgid : true }
259272
260273task .Command .ReturnCode = - 1
274+ task .Command .Environment = map [string ]string {}
261275}
262276
263277func (task * Task ) UpdateExec (execpath string ) {
@@ -440,7 +454,7 @@ func variableSplitFunc(data []byte, atEOF bool) (advance int, token []byte, err
440454}
441455
442456// runSingleCmd executes a tasks primary command (not child task commands) and monitors command events
443- func (task * Task ) runSingleCmd (resultChan chan CmdEvent , waiter * sync.WaitGroup ) {
457+ func (task * Task ) runSingleCmd (resultChan chan CmdEvent , waiter * sync.WaitGroup , environment map [ string ] string ) {
444458logToMain ("Started Task: " + task .Config .Name , INFO_FORMAT )
445459
446460task .Command .StartTime = time .Now ()
@@ -457,6 +471,11 @@ func (task *Task) runSingleCmd(resultChan chan CmdEvent, waiter *sync.WaitGroup)
457471stdoutPipe , _ := task .Command .Cmd .StdoutPipe ()
458472stderrPipe , _ := task .Command .Cmd .StderrPipe ()
459473
474+ // copy env vars into proc
475+ for k , v := range environment {
476+ task .Command .Cmd .Env = append (task .Command .Cmd .Env , fmt .Sprintf ("%s=%s" , k , v ))
477+ }
478+
460479task .Command .Cmd .Start ()
461480
462481var readPipe func (chan string , io.ReadCloser )
@@ -539,6 +558,23 @@ func (task *Task) runSingleCmd(resultChan chan CmdEvent, waiter *sync.WaitGroup)
539558
540559logToMain ("Completed Task: " + task .Config .Name + " (rc: " + returnCodeMsg + ")" , INFO_FORMAT )
541560
561+ // close the write end of the pipe since the child shell is positively no longer writting to it
562+ task .Command .Cmd .ExtraFiles [0 ].Close ()
563+ data , err := ioutil .ReadAll (task .Command .EnvReadFile )
564+ CheckError (err , "Could not read env vars from child shell" )
565+
566+ if environment != nil {
567+ lines := strings .Split (string (data [:]), "\n " )
568+ for _ , line := range lines {
569+ fields := strings .SplitN (strings .TrimSpace (line ), "=" , 2 )
570+ if len (fields ) == 2 {
571+ environment [fields [0 ]] = fields [1 ]
572+ } else if len (fields ) == 1 {
573+ environment [fields [0 ]] = ""
574+ }
575+ }
576+ }
577+
542578if returnCode == 0 || task .Config .IgnoreFailure {
543579resultChan <- CmdEvent {Task : task , Status : StatusSuccess , Complete : true , ReturnCode : returnCode }
544580} else {
@@ -551,7 +587,6 @@ func (task *Task) runSingleCmd(resultChan chan CmdEvent, waiter *sync.WaitGroup)
551587
552588// Pave prints the initial task (and child task) formatted status to the screen using newline characters to advance rows (not ansi control codes)
553589func (task * Task ) Pave () {
554- logToMain (" Pave Task: " + task .Config .Name , MAJOR_FORMAT )
555590var message bytes.Buffer
556591hasParentCmd := task .Config .CmdString != ""
557592hasHeader := len (task .Children ) > 0
@@ -582,14 +617,14 @@ func (task *Task) Pave() {
582617}
583618
584619// StartAvailableTasks will kick start the maximum allowed number of commands (both primary and child task commands). Repeated invocation will iterate to new commands (and not repeat already completed commands)
585- func (task * Task ) StartAvailableTasks () {
620+ func (task * Task ) StartAvailableTasks (environment map [ string ] string ) {
586621if task .Config .CmdString != "" && ! task .Command .Started && TaskStats .runningCmds < config .Options .MaxParallelCmds {
587- go task .runSingleCmd (task .resultChan , & task .waiter )
622+ go task .runSingleCmd (task .resultChan , & task .waiter , environment )
588623task .Command .Started = true
589624TaskStats .runningCmds ++
590625}
591626for ; TaskStats .runningCmds < config .Options .MaxParallelCmds && task .lastStartedTask < len (task .Children ); task .lastStartedTask ++ {
592- go task .Children [task .lastStartedTask ].runSingleCmd (task .resultChan , & task .waiter )
627+ go task .Children [task .lastStartedTask ].runSingleCmd (task .resultChan , & task .waiter , nil )
593628task .Children [task .lastStartedTask ].Command .Started = true
594629TaskStats .runningCmds ++
595630}
@@ -607,7 +642,7 @@ func (task *Task) Completed(rc int) {
607642}
608643
609644// listenAndDisplay updates the screen frame with the latest task and child task updates as they occur (either in realtime or in a polling loop). Returns when all child processes have been completed.
610- func (task * Task ) listenAndDisplay () {
645+ func (task * Task ) listenAndDisplay (environment map [ string ] string ) {
611646scr := Screen ()
612647// just wait for stuff to come back
613648for TaskStats .runningCmds > 0 {
@@ -642,7 +677,7 @@ func (task *Task) listenAndDisplay() {
642677// update the state before displaying...
643678if msgObj .Complete {
644679eventTask .Completed (msgObj .ReturnCode )
645- task .StartAvailableTasks ()
680+ task .StartAvailableTasks (environment )
646681task .status = msgObj .Status
647682if msgObj .Status == StatusError {
648683// update the group status to indicate a failed subtask
@@ -688,13 +723,13 @@ func (task *Task) listenAndDisplay() {
688723}
689724
690725// Run will run the current tasks primary command and/or all child commands. When execution has completed, the screen frame will advance.
691- func (task * Task ) Run () {
726+ func (task * Task ) Run (environment map [ string ] string ) {
692727
693728var message bytes.Buffer
694729
695730task .Pave ()
696- task .StartAvailableTasks ()
697- task .listenAndDisplay ()
731+ task .StartAvailableTasks (environment )
732+ task .listenAndDisplay (environment )
698733
699734scr := Screen ()
700735hasHeader := len (task .Children ) > 0
0 commit comments