]> Cypherpunks.ru repositories - goredo.git/blobdiff - main.go
Download link for 2.6.2 release
[goredo.git] / main.go
diff --git a/main.go b/main.go
index fdd0aac75a97dcec4f7aa5c458fa64a2b5fa8052..eb67c8069fa2534b645368e1504b8b6963bb7853 100644 (file)
--- a/main.go
+++ b/main.go
@@ -1,45 +1,72 @@
-/*
-goredo -- redo implementation on pure Go
-Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
+// goredo -- djb's redo implementation on pure Go
+// Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 package main
 
 import (
-       "crypto/rand"
+       "bufio"
+       "bytes"
+       "encoding/hex"
+       "errors"
        "flag"
        "fmt"
        "io"
-       "io/ioutil"
+       "io/fs"
        "log"
        "os"
+       "os/signal"
        "path"
-       "path/filepath"
+       "runtime"
+       "sort"
        "strconv"
+       "syscall"
 
+       "github.com/google/uuid"
        "go.cypherpunks.ru/recfile"
        "golang.org/x/sys/unix"
 )
 
+const (
+       CmdNameGoredo       = "goredo"
+       CmdNameRedo         = "redo"
+       CmdNameRedoAffects  = "redo-affects"
+       CmdNameRedoAlways   = "redo-always"
+       CmdNameRedoCleanup  = "redo-cleanup"
+       CmdNameRedoDep2Rec  = "redo-dep2rec"
+       CmdNameRedoDepFix   = "redo-depfix"
+       CmdNameRedoDot      = "redo-dot"
+       CmdNameRedoIfchange = "redo-ifchange"
+       CmdNameRedoIfcreate = "redo-ifcreate"
+       CmdNameRedoInode    = "redo-inode"
+       CmdNameRedoLog      = "redo-log"
+       CmdNameRedoOOD      = "redo-ood"
+       CmdNameRedoSources  = "redo-sources"
+       CmdNameRedoStamp    = "redo-stamp"
+       CmdNameRedoTargets  = "redo-targets"
+       CmdNameRedoWhichdo  = "redo-whichdo"
+)
+
 var (
        Cwd       string
-       BuildUUID string
+       BuildUUID uuid.UUID
+       IsTopRedo bool // is it the top redo instance
+       UmaskCur  int
 )
 
-func mustSetenv(key, value string) {
-       if err := os.Setenv(key, value); err != nil {
+func mustSetenv(key string) {
+       if err := os.Setenv(key, "1"); err != nil {
                panic(err)
        }
 }
@@ -56,98 +83,197 @@ func mustParseFd(v, name string) *os.File {
        return fd
 }
 
+func CmdName() string {
+       return path.Base(os.Args[0])
+}
+
 func main() {
        version := flag.Bool("version", false, "print version")
        warranty := flag.Bool("warranty", false, "print warranty information")
-       symlinks := flag.Bool("symlinks", false, "create necessary symlinks in current direcotyr")
 
-       flag.Usage = usage
-       flag.Parse()
+       var symlinks *bool
+       cmdName := CmdName()
+       if cmdName == CmdNameGoredo {
+               symlinks = flag.Bool("symlinks", false, "create necessary symlinks in current directory")
+       }
+
+       flag.Usage = func() { usage(os.Args[0]) }
+
+       BuildUUIDStr := os.Getenv(EnvBuildUUID)
+       IsTopRedo = BuildUUIDStr == ""
+
+       var args []string
+       if IsTopRedo {
+               flag.Parse()
+               args = flag.Args()
+       } else {
+               args = os.Args[1:]
+       }
+
        if *warranty {
                fmt.Println(Warranty)
                return
        }
        if *version {
-               fmt.Println(versionGet())
+               fmt.Println("goredo", Version, "built with", runtime.Version())
                return
        }
-       if *symlinks {
+       if cmdName == CmdNameGoredo && *symlinks {
                rc := 0
                for _, cmdName := range []string{
-                       "redo",
-                       "redo-always",
-                       "redo-cleanup",
-                       "redo-dot",
-                       "redo-ifchange",
-                       "redo-ifcreate",
-                       "redo-log",
-                       "redo-stamp",
-                       "redo-whichdo",
+                       CmdNameRedo,
+                       CmdNameRedoAffects,
+                       CmdNameRedoAlways,
+                       CmdNameRedoCleanup,
+                       CmdNameRedoDep2Rec,
+                       CmdNameRedoDepFix,
+                       CmdNameRedoDot,
+                       CmdNameRedoIfchange,
+                       CmdNameRedoIfcreate,
+                       CmdNameRedoInode,
+                       CmdNameRedoLog,
+                       CmdNameRedoOOD,
+                       CmdNameRedoSources,
+                       CmdNameRedoStamp,
+                       CmdNameRedoTargets,
+                       CmdNameRedoWhichdo,
                } {
+                       fmt.Println(os.Args[0], "<-", cmdName)
                        if err := os.Symlink(os.Args[0], cmdName); err != nil {
                                rc = 1
-                               log.Println(err)
+                               log.Print(err)
                        }
                }
                os.Exit(rc)
        }
-       log.SetFlags(0)
+       log.SetFlags(log.Lshortfile)
+
+       UmaskCur = syscall.Umask(0)
+       syscall.Umask(UmaskCur)
 
        var err error
-       Cwd, err = unix.Getwd()
+       Cwd, err = os.Getwd()
        if err != nil {
-               panic(err)
+               log.Fatal(err)
        }
 
-       NoColor = os.Getenv(EnvNoColor) != ""
-       NoHash = os.Getenv(EnvNoHash) == "1"
-       NoSync = os.Getenv(EnvNoSync) == "1"
-
        TopDir = os.Getenv(EnvTopDir)
-       if TopDir != "" {
-               TopDir, err = filepath.Abs(TopDir)
-               if err != nil {
-                       panic(err)
-               }
+       if TopDir == "" {
+               TopDir = "/"
+       } else {
+               TopDir = mustAbs(TopDir)
        }
+       DirPrefix = os.Getenv(EnvDirPrefix)
+       DepCwd = os.Getenv(EnvDepCwd)
 
-       if *flagStderrKeep {
-               mustSetenv(EnvStderrKeep, "1")
+       if flagStderrKeep != nil && *flagStderrKeep {
+               mustSetenv(EnvStderrKeep)
+       }
+       if flagStderrSilent != nil && *flagStderrSilent {
+               mustSetenv(EnvStderrSilent)
        }
-       if *flagStderrSilent {
-               mustSetenv(EnvStderrSilent, "1")
+       if flagNoProgress != nil && *flagNoProgress {
+               mustSetenv(EnvNoProgress)
        }
-       if *flagDebug {
-               mustSetenv(EnvDebug, "1")
+       if flagDebug != nil && *flagDebug {
+               mustSetenv(EnvDebug)
        }
-       if *flagLogWait {
-               mustSetenv(EnvLogWait, "1")
+       if flagLogWait != nil && *flagLogWait {
+               mustSetenv(EnvLogWait)
        }
-       if *flagLogLock {
-               mustSetenv(EnvLogLock, "1")
+       if flagLogLock != nil && *flagLogLock {
+               mustSetenv(EnvLogLock)
        }
-       if *flagLogPid {
-               mustSetenv(EnvLogPid, "1")
+       if flagLogPid != nil && *flagLogPid {
+               mustSetenv(EnvLogPid)
        }
-       if *flagLogJS {
-               mustSetenv(EnvLogJS, "1")
+       if flagLogJS != nil && *flagLogJS {
+               mustSetenv(EnvLogJS)
        }
        StderrKeep = os.Getenv(EnvStderrKeep) == "1"
        StderrSilent = os.Getenv(EnvStderrSilent) == "1"
+       NoProgress = os.Getenv(EnvNoProgress) == "1"
        Debug = os.Getenv(EnvDebug) == "1"
        LogWait = os.Getenv(EnvLogWait) == "1"
        LogLock = os.Getenv(EnvLogLock) == "1"
        LogJS = os.Getenv(EnvLogJS) == "1"
        if Debug || os.Getenv(EnvLogPid) == "1" {
-               MyPid = os.Getpid()
+               MyPID = os.Getpid()
        }
-       if *flagTrace {
-               Trace = true
-       } else {
-               Trace = os.Getenv(EnvTrace) == "1"
+       var traced bool
+       if flagTraceAll != nil && *flagTraceAll {
+               mustSetenv(EnvTrace)
+       }
+       if os.Getenv(EnvTrace) == "1" {
+               TracedAll = true
+               traced = true
+       } else if flagTrace != nil {
+               traced = *flagTrace
+       }
+       NoColor = os.Getenv(EnvNoColor) != ""
+       NoSync = os.Getenv(EnvNoSync) == "1"
+       StopIfMod = os.Getenv(EnvStopIfMod) == "1"
+       switch s := os.Getenv(EnvInodeTrust); s {
+       case "none":
+               InodeTrust = InodeTrustNone
+       case "", "ctime":
+               InodeTrust = InodeTrustCtime
+       case "mtime":
+               InodeTrust = InodeTrustMtime
+       default:
+               log.Fatalln("unknown", EnvInodeTrust, "value")
        }
 
        // Those are internal envs
+       FdOODTgts, err = os.CreateTemp("", "ood-tgts")
+       if err != nil {
+               log.Fatal(err)
+       }
+       if err = os.Remove(FdOODTgts.Name()); err != nil {
+               log.Fatal(err)
+       }
+       FdOODTgtsLock, err = os.CreateTemp("", "ood-tgts.lock")
+       if err != nil {
+               log.Fatal(err)
+       }
+       if err = os.Remove(FdOODTgtsLock.Name()); err != nil {
+               log.Fatal(err)
+       }
+
+       var fdLock *os.File
+       if v := os.Getenv(EnvOODTgtsFd); v != "" {
+               fd := mustParseFd(v, EnvOODTgtsFd)
+               fdLock = mustParseFd(v, EnvOODTgtsLockFd)
+               defer fdLock.Close()
+               flock := unix.Flock_t{
+                       Type:   unix.F_WRLCK,
+                       Whence: io.SeekStart,
+               }
+               if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLKW, &flock); err != nil {
+                       log.Fatal(err)
+               }
+               if _, err = fd.Seek(0, io.SeekStart); err != nil {
+                       log.Fatal(err)
+               }
+               tgtsRaw, err := io.ReadAll(bufio.NewReader(fd))
+               if err != nil {
+                       log.Fatal(err)
+               }
+               flock.Type = unix.F_UNLCK
+               if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLK, &flock); err != nil {
+                       log.Fatal(err)
+               }
+               OODTgts = make(map[string]struct{})
+               for _, tgtRaw := range bytes.Split(tgtsRaw, []byte{0}) {
+                       t := string(tgtRaw)
+                       if t == "" {
+                               continue
+                       }
+                       OODTgts[t] = struct{}{}
+                       tracef(CDebug, "ood: known to be: %s", t)
+               }
+       }
+
        StderrPrefix = os.Getenv(EnvStderrPrefix)
        if v := os.Getenv(EnvLevel); v != "" {
                Level, err = strconv.Atoi(v)
@@ -158,119 +284,320 @@ func main() {
                        panic("negative " + EnvLevel)
                }
        }
+
        var fdDep *os.File
        if v := os.Getenv(EnvDepFd); v != "" {
                fdDep = mustParseFd(v, EnvDepFd)
        }
-       BuildUUID = os.Getenv(EnvBuildUUID)
-       if BuildUUID == "" {
-               raw := new([16]byte)
-               if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil {
-                       panic(err)
+
+       tgts := make([]*Tgt, 0, len(args))
+       for _, arg := range args {
+               tgts = append(tgts, NewTgt(arg))
+       }
+       tgtsWasEmpty := len(tgts) == 0
+       if BuildUUIDStr == "" {
+               BuildUUID = uuid.New()
+               if tgtsWasEmpty {
+                       tgts = append(tgts, NewTgt("all"))
+               }
+               tracef(CDebug, "inode-trust: %s", InodeTrust)
+       } else {
+               BuildUUID, err = uuid.Parse(BuildUUIDStr)
+               if err != nil {
+                       log.Fatal(err)
                }
-               raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4
-               BuildUUID = fmt.Sprintf(
-                       "%x-%x-%x-%x-%x",
-                       raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
-               )
        }
 
-       tgts := flag.Args()
-       if len(tgts) == 0 {
-               tgts = []string{"all"}
+       if cmdName == CmdNameRedo {
+               statusInit()
        }
+
+       killed := make(chan os.Signal, 1)
+       signal.Notify(killed, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
+       go func() {
+               <-killed
+               tracef(CDebug, "[%s] killed", BuildUUID)
+               jsReleaseAll()
+               RunningProcsM.Lock()
+               for pid, proc := range RunningProcs {
+                       tracef(CDebug, "[%s] killing child %d", BuildUUID, pid)
+                       _ = proc.Signal(syscall.SIGTERM)
+               }
+               os.Exit(1)
+       }()
+
        ok := true
        err = nil
-       cmdName := path.Base(os.Args[0])
-       trace(CDebug, "[%s] run: %s %s [%s]", BuildUUID, cmdName, tgts, Cwd)
+       tracef(CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
+               BuildUUID, cmdName, tgts, Cwd, DirPrefix)
 
-CmdSwitch:
        switch cmdName {
-       case "redo":
-               Force = true
-               ok, err = ifchange(tgts)
-       case "redo-ifchange":
-               ok, err = ifchange(tgts)
-               writeDeps(fdDep, tgts)
-       case "redo-ifcreate":
+       case CmdNameRedo:
+               for _, tgt := range tgts {
+                       ok, err = ifchange([]*Tgt{tgt}, true, traced)
+                       if err != nil || !ok {
+                               break
+                       }
+               }
+       case CmdNameRedoIfchange:
+               ok, err = ifchange(tgts, *flagForcedIfchange, traced)
+               if err == nil {
+                       err = depsWrite(fdDep, tgts)
+               }
+       case CmdNameRedoIfcreate:
                if fdDep == nil {
                        log.Fatalln("no", EnvDepFd)
                }
+               fdDepW := bufio.NewWriter(fdDep)
                for _, tgt := range tgts {
-                       err = ifcreate(fdDep, tgt)
+                       err = ifcreate(fdDepW, fdDep.Name(), tgt.RelTo(path.Join(Cwd, DirPrefix)))
                        if err != nil {
                                break
                        }
                }
-       case "redo-always":
+               err = fdDepW.Flush()
+       case CmdNameRedoAlways:
                if fdDep == nil {
                        log.Fatalln("no", EnvDepFd)
                }
-               err = always(fdDep)
-       case "redo-cleanup":
+               err = always(fdDep, fdDep.Name())
+       case CmdNameRedoCleanup:
                for _, what := range tgts {
-                       err = cleanupWalker(Cwd, what)
+                       err = cleanupWalker(Cwd, path.Base(what.a))
                        if err != nil {
                                break
                        }
                }
-       case "redo-dot":
+       case CmdNameRedoDot:
                err = dotPrint(tgts)
-       case "redo-stamp":
+       case CmdNameRedoStamp:
                if fdDep == nil {
                        log.Fatalln("no", EnvDepFd)
                }
-               err = stamp(fdDep, os.Stdin)
-       case "redo-log":
-               if len(tgts) != 1 {
-                       log.Fatalln("single target expected")
-               }
-               d, t := cwdAndTgt(tgts[0])
-               var fd *os.File
-               fd, err = os.Open(path.Join(d, RedoDir, t+LogSuffix))
+               var hsh Hash
+               hsh, err = fileHash(os.Stdin)
                if err != nil {
                        break
                }
-               _, err = io.Copy(os.Stdout, fd)
-       case "redo-whichdo":
+               err = stamp(fdDep, fdDep.Name(), hsh)
+       case CmdNameRedoLog:
+               if len(tgts) != 1 {
+                       log.Fatal("single target expected")
+               }
+               err = showBuildLog(tgts[0], nil, 0)
+       case CmdNameRedoWhichdo:
                if len(tgts) != 1 {
-                       log.Fatalln("single target expected")
+                       log.Fatal("single target expected")
                }
-               var fdTmp *os.File
-               fdTmp, err = ioutil.TempFile("", "")
+               var dos []string
+               dos, err = whichdo(tgts[0])
                if err != nil {
-                       break
+                       if errors.Is(err, fs.ErrNotExist) {
+                               err = nil
+                               ok = false
+                       } else {
+                               break
+                       }
+               }
+               for _, do := range dos {
+                       fmt.Println(do)
                }
-               os.Remove(fdTmp.Name())
-               var doFile string
-               cwd, tgt := cwdAndTgt(tgts[0])
-               doFile, _, err = findDo(fdTmp, cwd, tgt)
+       case CmdNameRedoTargets:
+               raws := make([]string, 0, len(tgts))
+               for _, tgt := range tgts {
+                       raws = append(raws, tgt.rel)
+               }
+               if tgtsWasEmpty {
+                       raws = []string{Cwd}
+               }
+               raws, err = targetsWalker(raws)
                if err != nil {
+                       err = ErrLine(err)
                        break
                }
-               fdTmp.Seek(0, 0)
-               r := recfile.NewReader(fdTmp)
-               for {
-                       m, err := r.NextMap()
+               sort.Strings(raws)
+               for _, tgt := range raws {
+                       fmt.Println(tgt)
+               }
+       case CmdNameRedoAffects:
+               if tgtsWasEmpty {
+                       log.Fatal("no targets specified")
+               }
+               var res []string
+               {
+                       var tgtsKnown []string
+                       tgtsKnown, err = targetsWalker([]string{Cwd})
                        if err != nil {
-                               if err == io.EOF {
+                               err = ErrLine(err)
+                               break
+                       }
+                       deps := make(map[string]map[string]*Tgt)
+                       for _, tgt := range tgtsKnown {
+                               collectDeps(NewTgt(tgt), 0, deps, true)
+                       }
+                       seen := make(map[string]*Tgt)
+                       for _, tgt := range tgts {
+                               collectWholeDeps(deps[tgt.rel], deps, seen)
+                       }
+                       res = make([]string, 0, len(seen))
+                       for _, dep := range seen {
+                               res = append(res, dep.rel)
+                       }
+               }
+               sort.Strings(res)
+               for _, dep := range res {
+                       fmt.Println(dep)
+               }
+       case CmdNameRedoOOD:
+               raws := make([]string, 0, len(tgts))
+               for _, tgt := range tgts {
+                       raws = append(raws, tgt.rel)
+               }
+               if tgtsWasEmpty {
+                       raws, err = targetsWalker([]string{Cwd})
+                       if err != nil {
+                               break
+                       }
+               }
+               sort.Strings(raws)
+               var ood bool
+               for _, tgt := range raws {
+                       ood, err = isOOD(NewTgt(tgt), 0, nil)
+                       if err != nil {
+                               err = ErrLine(err)
+                               break
+                       }
+                       if ood {
+                               fmt.Println(tgt)
+                       }
+               }
+       case CmdNameRedoSources:
+               srcs := make(map[string]*Tgt)
+               {
+                       raws := make([]string, 0, len(tgts))
+                       for _, tgt := range tgts {
+                               raws = append(raws, tgt.rel)
+                       }
+                       if tgtsWasEmpty {
+                               raws, err = targetsWalker([]string{Cwd})
+                               if err != nil {
+                                       err = ErrLine(err)
                                        break
                                }
-                               break CmdSwitch
                        }
-                       fmt.Println(cwdMustRel(cwd, m["Target"]))
+                       sort.Strings(raws)
+                       tgts = tgts[:0]
+                       for _, raw := range raws {
+                               tgts = append(tgts, NewTgt(raw))
+                       }
+                       seen := make(map[string]struct{})
+                       seenDeps := make(map[string]struct{})
+                       err = ErrLine(sourcesWalker(tgts, seen, seenDeps, srcs))
+               }
+               if err != nil {
+                       break
+               }
+               res := make([]string, 0, len(srcs))
+               for _, tgt := range srcs {
+                       res = append(res, tgt.rel)
+               }
+               srcs = nil
+               sort.Strings(res)
+               for _, src := range res {
+                       fmt.Println(src)
+               }
+       case CmdNameRedoDepFix:
+               IfchangeCache = nil
+               DepFixHashCache = make(map[string]Hash)
+               err = depFix(Cwd)
+       case CmdNameRedoInode:
+               var inode *Inode
+               for _, tgt := range tgts {
+                       inode, err = inodeFromFileByPath(tgt.a)
+                       if err != nil {
+                               err = ErrLine(err)
+                               break
+                       }
+                       err = recfileWrite(os.Stdout, append(
+                               []recfile.Field{{Name: "Target", Value: tgt.String()}},
+                               inode.RecfileFields()...)...)
+                       if err != nil {
+                               err = ErrLine(err)
+                               break
+                       }
+               }
+       case CmdNameRedoDep2Rec:
+               var data []byte
+               data, err = os.ReadFile(tgts[0].a)
+               if err != nil {
+                       break
+               }
+               var build uuid.UUID
+               build, data, err = depHeadParse(data)
+               if err != nil {
+                       break
+               }
+               w := bufio.NewWriter(os.Stdout)
+               err = recfileWrite(w, []recfile.Field{
+                       {Name: "Build", Value: build.String()},
+               }...)
+               if err != nil {
+                       break
+               }
+               var typ byte
+               var chunk []byte
+               var inode Inode
+               for len(data) > 0 {
+                       typ, chunk, data, _ = chunkRead(data)
+                       switch typ {
+                       case DepTypeAlways:
+                               err = recfileWrite(w, []recfile.Field{
+                                       {Name: "Type", Value: "always"},
+                               }...)
+                       case DepTypeStamp:
+                               err = recfileWrite(w, []recfile.Field{
+                                       {Name: "Type", Value: "stamp"},
+                                       {Name: "Hash", Value: hex.EncodeToString(chunk)},
+                               }...)
+                       case DepTypeIfcreate:
+                               err = recfileWrite(w, []recfile.Field{
+                                       {Name: "Type", Value: "ifcreate"},
+                                       {Name: "Target", Value: string(chunk)},
+                               }...)
+                       case DepTypeIfchange:
+                               name := string(chunk[InodeLen+HashLen:])
+                               meta := chunk[:InodeLen+HashLen]
+                               fields := []recfile.Field{
+                                       {Name: "Type", Value: "ifchange"},
+                                       {Name: "Target", Value: name},
+                               }
+                               fields = append(fields, recfile.Field{
+                                       Name: "Hash", Value: Hash(meta[InodeLen:]).String(),
+                               })
+                               inode = Inode(meta[:][:InodeLen])
+                               fields = append(fields, inode.RecfileFields()...)
+                               err = recfileWrite(w, fields...)
+                       case DepTypeIfchangeNonex:
+                               err = recfileWrite(w, []recfile.Field{
+                                       {Name: "Type", Value: "ifchange"},
+                                       {Name: "Target", Value: string(chunk)},
+                               }...)
+                       }
+                       if err != nil {
+                               break
+                       }
                }
-               ok = doFile != ""
+               err = w.Flush()
        default:
                log.Fatalln("unknown command", cmdName)
        }
        if err != nil {
-               log.Println(err)
+               log.Print(err)
        }
        rc := 0
        if !ok || err != nil {
                rc = 1
        }
-       trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
+       tracef(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
        os.Exit(rc)
 }