]> Cypherpunks.ru repositories - goredo.git/commitdiff
Fix JS deadlock and various optimizations
authorSergey Matveev <stargrave@stargrave.org>
Fri, 20 Nov 2020 20:56:36 +0000 (23:56 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sat, 21 Nov 2020 11:44:41 +0000 (14:44 +0300)
README
ifchange.go [new file with mode: 0644]
js.go
log.go
main.go
ood.go
run.go
usage.go

diff --git a/README b/README
index db2222ededb298583937ed792d695578debe370a..a918baefed4f23da7566afae23cbcf10caa93608 100644 (file)
--- a/README
+++ b/README
@@ -33,6 +33,7 @@ NOTES                                                     *goredo-notes*
   REDO_TOP_DIR environment variable, or by having .redo/top file in it
 * executable .do is run as is
 * shebangless .do is run with /bin/sh -e[x]
+* target's completion messages are written after they finish
 
 FEATURES                                               *goredo-features*
 
diff --git a/ifchange.go b/ifchange.go
new file mode 100644 (file)
index 0000000..972da4d
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+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/>.
+*/
+
+package main
+
+func isOkRun(err error) bool {
+       if err == nil {
+               return true
+       }
+       if err, ok := err.(RunErr); ok && err.Err == nil {
+               trace(CRedo, "%s", err.Name())
+               return true
+       }
+       trace(CErr, "%s", err)
+       return false
+}
+
+func ifchange(tgts []string) (bool, error) {
+       jsInit()
+       defer jsAcquire("ifchange exiting")
+       defer Jobs.Wait()
+       errs := make(chan error, len(tgts))
+       jobs := 0
+       ok := true
+       var err error
+       for _, tgt := range tgts {
+               var ood bool
+               if Force {
+                       ood = true
+               } else {
+                       ood, err = isOOD(Cwd, tgt, 0)
+                       if err != nil {
+                               return false, err
+                       }
+               }
+               if !ood {
+                       continue
+               }
+               if isSrc(Cwd, tgt) {
+                       trace(CDebug, "%s is source, not redoing", tgt)
+                       continue
+               }
+               if err = runScript(tgt, errs); err != nil {
+                       return false, err
+               }
+               if Force {
+                       // Sequentially run jobs
+                       err = <-errs
+                       Jobs.Wait()
+                       if isOkRun(err) {
+                               continue
+                       }
+                       return false, nil
+               }
+               jobs++
+       }
+       for i := 0; i < jobs; i++ {
+               err = <-errs
+               ok = ok && isOkRun(err)
+       }
+       return ok, nil
+}
diff --git a/js.go b/js.go
index ab1190e00c00759f48899030271986704d3c9ae4..d3ed7bb3fb752b5b7566c1251db7ae11b3c93a1f 100644 (file)
--- a/js.go
+++ b/js.go
@@ -39,7 +39,7 @@ func jsInit() {
        if jsrRaw != "" {
                JSR = mustParseFd(jsrRaw, "JSR")
                JSW = mustParseFd(jswRaw, "JSW")
-               jsRelease()
+               jsRelease("ifchange entered")
                return
        }
        if *JobsN == 0 {
@@ -52,26 +52,27 @@ func jsInit() {
                panic(err)
        }
        for i := uint(0); i < *JobsN; i++ {
-               jsRelease()
+               jsRelease("initial fill")
        }
 }
 
-func jsRelease() {
+func jsRelease(ctx string) {
        if JSW == nil {
                return
        }
-       trace(CJS, "release")
+       trace(CJS, "release from %s", ctx)
        if n, err := JSW.Write([]byte{0}); err != nil || n != 1 {
                panic("can not write JSW")
        }
 }
 
-func jsAcquire() {
+func jsAcquire(ctx string) {
        if JSR == nil {
                return
        }
-       trace(CJS, "acquire")
+       trace(CJS, "acquire for %s", ctx)
        if n, err := JSR.Read([]byte{0}); err != nil || n != 1 {
                panic("can not read JSR")
        }
+       trace(CJS, "acquired for %s", ctx)
 }
diff --git a/log.go b/log.go
index 8669a642f98c1561e50643b01f987f2c09250c57..335ca53c3d0a146079286a4c4b04caed91c6f450 100644 (file)
--- a/log.go
+++ b/log.go
@@ -39,7 +39,7 @@ const (
 
        CDebug = CYellow
        CRedo  = CGreen
-       CDone  = CBlue
+       CWait  = CBlue
        CLock  = CCyan
        CErr   = CRed
        CWarn  = CMagenta
@@ -50,7 +50,7 @@ const (
 var (
        NoColor bool
        Debug   bool
-       LogDone bool
+       LogWait bool
        LogLock bool
        LogJS   bool
        MyPid   int
@@ -70,13 +70,13 @@ func trace(level, format string, args ...interface{}) {
                        return
                }
                p += "dbg  "
-       case CRedo:
-               p += "redo "
-       case CDone:
-               if !(LogDone || Debug) {
+       case CWait:
+               if !(LogWait || Debug) {
                        return
                }
-               p += "done "
+               p += "wait "
+       case CRedo:
+               p += "redo "
        case CLock:
                if !(LogLock || Debug) {
                        return
diff --git a/main.go b/main.go
index df9296f74b16875fb103947a3d5956cdebfec268..d0d3ab282cac7bb30cbff76d4ff222f7ce82486a 100644 (file)
--- a/main.go
+++ b/main.go
@@ -81,61 +81,12 @@ func mustParseFd(v, name string) *os.File {
        return fd
 }
 
-func ifchange(tgts []string) (bool, error) {
-       jsInit()
-       defer Jobs.Wait()
-       defer jsAcquire()
-       errs := make(chan error, len(tgts))
-       jobs := 0
-       ok := true
-       var err error
-       for _, tgt := range tgts {
-               var ood bool
-               if Force {
-                       ood = true
-               } else {
-                       ood, err = isOOD(Cwd, tgt, 0)
-                       if err != nil {
-                               return false, err
-                       }
-               }
-               if !ood {
-                       continue
-               }
-               if isSrc(Cwd, tgt) {
-                       trace(CDebug, "%s is source", tgt)
-                       continue
-               }
-               if err = runScript(tgt, errs); err != nil {
-                       return false, err
-               }
-               if Force {
-                       // Sequentially run jobs
-                       err = <-errs
-                       Jobs.Wait()
-                       if err != nil {
-                               trace(CErr, "%s", err)
-                               return false, nil
-                       }
-                       continue
-               }
-               jobs++
-       }
-       for i := 0; i < jobs; i++ {
-               if err = <-errs; err != nil {
-                       ok = false
-                       trace(CErr, "%s", err)
-               }
-       }
-       return ok, nil
-}
-
 func main() {
        xflag := flag.Bool("x", false, "trace current target (sh -x) (set REDO_TRACE=1 for others too)")
        stderrKeep := flag.Bool("stderr-keep", false, "keep job's stderr (REDO_STDERR_KEEP=1)")
        stderrSilent := flag.Bool("stderr-silent", false, "do not print job's stderr (REDO_STDERR_SILENT=1)")
        debug := flag.Bool("debug", false, "enable debug logging (REDO_DEBUG=1)")
-       logDone := flag.Bool("log-done", false, "enable done messages logging (REDO_LOG_DONE=1)")
+       logWait := flag.Bool("log-wait", false, "enable wait messages logging (REDO_LOG_WAIT=1)")
        logLock := flag.Bool("log-lock", false, "enable lock messages logging (REDO_LOG_LOCK=1)")
        logPid := flag.Bool("log-pid", false, "append PIDs (REDO_LOG_PID=1)")
        logJS := flag.Bool("log-js", false, "enable jobserver messages logging (REDO_LOG_JS=1)")
@@ -199,8 +150,8 @@ func main() {
        if *debug {
                mustSetenv("REDO_DEBUG", "1")
        }
-       if *logDone {
-               mustSetenv("REDO_LOG_DONE", "1")
+       if *logWait {
+               mustSetenv("REDO_LOG_WAIT", "1")
        }
        if *logLock {
                mustSetenv("REDO_LOG_LOCK", "1")
@@ -214,7 +165,7 @@ func main() {
        StderrKeep = os.Getenv("REDO_STDERR_KEEP") == "1"
        StderrSilent = os.Getenv("REDO_STDERR_SILENT") == "1"
        Debug = os.Getenv("REDO_DEBUG") == "1"
-       LogDone = os.Getenv("REDO_LOG_DONE") == "1"
+       LogWait = os.Getenv("REDO_LOG_WAIT") == "1"
        LogLock = os.Getenv("REDO_LOG_LOCK") == "1"
        LogJS = os.Getenv("REDO_LOG_JS") == "1"
        if Debug || os.Getenv("REDO_LOG_PID") == "1" {
@@ -347,7 +298,10 @@ CmdSwitch:
        if err != nil {
                log.Println(err)
        }
+       rc := 0
        if !ok || err != nil {
-               os.Exit(1)
+               rc = 1
        }
+       trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
+       os.Exit(rc)
 }
diff --git a/ood.go b/ood.go
index a7c2d22422443c75b3d4c84d5f876ed0e5fc483a..4189ed459cae740df414d766ac3e6e8ceaedb675 100644 (file)
--- a/ood.go
+++ b/ood.go
@@ -31,6 +31,17 @@ import (
        "go.cypherpunks.ru/recfile"
 )
 
+type TgtErr struct {
+       Tgt string
+       Err error
+}
+
+func (e TgtErr) Unwrap() error { return e.Err }
+
+func (e TgtErr) Error() string {
+       return fmt.Sprintf("%s: %s", e.Tgt, e.Err)
+}
+
 func cwdAndTgt(tgt string) (string, string) {
        cwd, tgt := path.Split(tgt)
        cwd, err := filepath.Abs(cwd)
@@ -58,11 +69,52 @@ func isBuiltNow(fdDep *os.File) (bool, *recfile.Reader, error) {
                return false, nil, err
        }
        if m["Build"] == "" {
-               return false, r, errors.New("dep. missing Build:")
+               return false, r, errors.New(".dep missing Build:")
        }
        return m["Build"] == BuildUUID, r, nil
 }
 
+func rebuildStamped(cwd, tgt, depPath string) (string, error) {
+       relTgt, err := filepath.Rel(Cwd, path.Join(cwd, tgt))
+       if err != nil {
+               panic(err)
+       }
+       errs := make(chan error, 1)
+       if err = runScript(relTgt, errs); err != nil {
+               return "", err
+       }
+       if err = <-errs; !isOkRun(err) {
+               return "", errors.New("build failed")
+       }
+       fdDep, err := os.Open(depPath)
+       if err != nil {
+               return "", err
+       }
+       defer fdDep.Close()
+       builtNow, r, err := isBuiltNow(fdDep)
+       if err != nil {
+               return "", err
+       }
+       if !builtNow {
+               return "", errors.New("is not built")
+       }
+       var stampTheir string
+       for {
+               m, err := r.NextMap()
+               if err != nil {
+                       if err == io.EOF {
+                               break
+                       }
+                       return "", err
+               }
+               if m["Type"] == "stamp" {
+                       stampTheir = m["Hash"]
+                       break
+               }
+       }
+       return stampTheir, nil
+}
+
 func isOOD(cwd, tgt string, level int) (bool, error) {
        tgtOrig := tgt
        indent := strings.Repeat(". ", level)
@@ -79,7 +131,7 @@ func isOOD(cwd, tgt string, level int) (bool, error) {
 
        builtNow, r, err := isBuiltNow(fdDep)
        if err != nil {
-               return true, err
+               return true, TgtErr{tgtOrig, err}
        }
        if builtNow {
                trace(CDebug, "ood: %s%s -> already built", indent, tgtOrig)
@@ -87,134 +139,114 @@ func isOOD(cwd, tgt string, level int) (bool, error) {
        }
 
        var stampOur string
+       ifcreates := []map[string]string{}
+       ifchanges := []map[string]string{}
        for {
                m, err := r.NextMap()
                if err != nil {
                        if err == io.EOF {
                                break
                        }
-                       return true, err
+                       return true, TgtErr{tgtOrig, err}
                }
                switch m["Type"] {
                case "always":
                        trace(CDebug, "ood: %s%s -> always", indent, tgtOrig)
                        ood = true
                case "ifcreate":
-                       theirTgt := m["Target"]
-                       if theirTgt == "" {
-                               return ood, errors.New("invalid format of dep." + tgtOrig)
-                       }
-                       if _, err = os.Stat(path.Join(cwd, theirTgt)); err == nil {
-                               trace(CDebug, "ood: %s%s -> created", indent, tgtOrig)
-                               ood = true
-                       }
+                       ifcreates = append(ifcreates, m)
                case "ifchange":
-                       dep := m["Target"]
-                       theirTs := m["Ctime"]
-                       theirHsh := m["Hash"]
-                       if dep == "" || theirTs == "" {
-                               return ood, errors.New("invalid format of dep." + tgtOrig)
-                       }
-                       trace(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep)
-                       fd, err := os.Open(path.Join(cwd, dep))
-                       if err != nil {
-                               if os.IsNotExist(err) {
-                                       trace(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep)
-                                       ood = true
-                                       continue
-                               }
-                               return ood, err
-                       }
-                       defer fd.Close()
-                       ts, err := fileCtime(fd)
-                       if err != nil {
-                               return ood, err
-                       }
-                       if theirTs == ts {
-                               trace(CDebug, "ood: %s%s -> %s: same ctime", indent, tgtOrig, dep)
-                       } else if NoHash || theirHsh == "" {
-                               trace(CDebug, "ood: %s%s -> %s: ctime differs", indent, tgtOrig, dep)
-                               ood = true
-                       } else {
-                               hsh, err := fileHash(fd)
-                               if err != nil {
-                                       return ood, err
-                               }
-                               if theirHsh == hsh {
-                                       trace(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep)
-                               } else {
-                                       trace(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep)
-                                       ood = true
-                               }
-                       }
-                       fd.Close()
-                       if ood {
-                               continue
-                       }
-                       if dep == tgt {
-                               trace(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep)
-                               continue
-                       }
-                       if isSrc(cwd, dep) {
-                               trace(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep)
-                               continue
-                       } else {
-                               depOod, err := isOOD(cwd, dep, level+1)
-                               if depOod {
-                                       ood = true
-                                       trace(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep)
-                               } else {
-                                       trace(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep)
-                               }
-                               if err != nil {
-                                       return ood, err
-                               }
-                       }
+                       ifchanges = append(ifchanges, m)
                case "stamp":
                        stampOur = m["Hash"]
                        trace(CDebug, "ood: %s%s -> stamped: %s", indent, tgtOrig, stampOur)
                default:
-                       return ood, errors.New("invalid format of dep." + tgtOrig)
+                       return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
                }
        }
-       if ood && stampOur != "" {
-               errs := make(chan error, 0)
-               trace(CDebug, "ood: %s%s running, because stamped", indent, tgtOrig)
-               relTgt, err := filepath.Rel(Cwd, path.Join(cwd, tgt))
-               if err != nil {
-                       return true, err
+       if ood {
+               goto StampCheck
+       }
+
+       for _, m := range ifcreates {
+               theirTgt := m["Target"]
+               if theirTgt == "" {
+                       return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
                }
-               if err = runScript(relTgt, errs); err != nil {
-                       return true, err
+               if _, err = os.Stat(path.Join(cwd, theirTgt)); err == nil {
+                       trace(CDebug, "ood: %s%s -> created", indent, tgtOrig)
+                       ood = true
+                       goto StampCheck
                }
-               if err = <-errs; err != nil {
-                       return true, err
+       }
+
+       for _, m := range ifchanges {
+               dep := m["Target"]
+               theirTs := m["Ctime"]
+               theirHsh := m["Hash"]
+               if dep == "" || theirTs == "" {
+                       return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
                }
-               fdDep, err := os.Open(depPath)
+               trace(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep)
+               fd, err := os.Open(path.Join(cwd, dep))
                if err != nil {
-                       return true, err
+                       if os.IsNotExist(err) {
+                               trace(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep)
+                               ood = true
+                               goto StampCheck
+                       }
+                       return ood, TgtErr{tgtOrig, err}
                }
-               defer fdDep.Close()
-               builtNow, r, err := isBuiltNow(fdDep)
+               defer fd.Close()
+               ts, err := fileCtime(fd)
                if err != nil {
-                       return true, err
-               }
-               if !builtNow {
-                       return true, fmt.Errorf("%s is not built", tgtOrig)
+                       return ood, TgtErr{tgtOrig, err}
                }
-               var stampTheir string
-               for {
-                       m, err := r.NextMap()
+               if theirTs == ts {
+                       trace(CDebug, "ood: %s%s -> %s: same ctime", indent, tgtOrig, dep)
+               } else if NoHash || theirHsh == "" {
+                       trace(CDebug, "ood: %s%s -> %s: ctime differs", indent, tgtOrig, dep)
+                       ood = true
+                       goto StampCheck
+               } else {
+                       hsh, err := fileHash(fd)
                        if err != nil {
-                               if err == io.EOF {
-                                       break
-                               }
-                               return true, err
+                               return ood, TgtErr{tgtOrig, err}
                        }
-                       if m["Type"] == "stamp" {
-                               stampTheir = m["Hash"]
-                               break
+                       if theirHsh != hsh {
+                               trace(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep)
+                               ood = true
+                               goto StampCheck
                        }
+                       trace(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep)
+               }
+               fd.Close()
+               if dep == tgt {
+                       trace(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep)
+                       continue
+               }
+               if isSrc(cwd, dep) {
+                       trace(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep)
+                       continue
+               }
+               depOod, err := isOOD(cwd, dep, level+1)
+               if err != nil {
+                       return ood, TgtErr{tgtOrig, err}
+               }
+               if depOod {
+                       trace(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep)
+                       ood = true
+                       goto StampCheck
+               }
+               trace(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep)
+       }
+
+StampCheck:
+       if ood && stampOur != "" {
+               trace(CDebug, "ood: %s%s run, because stamped", indent, tgtOrig)
+               stampTheir, err := rebuildStamped(cwd, tgt, depPath)
+               if err != nil {
+                       return true, TgtErr{tgtOrig, err}
                }
                trace(CDebug, "ood: %s%s -> stamp: %s %s", indent, tgtOrig, stampOur, stampTheir)
                if stampOur == stampTheir {
diff --git a/run.go b/run.go
index 36e18d2c21c5771dfd90bac244104e07006eeeed..58b9f6daff2fa933a17c6ff906766676169c86dd 100644 (file)
--- a/run.go
+++ b/run.go
@@ -103,25 +103,59 @@ func isModified(cwd, redoDir, tgt string) (bool, error) {
        return false, nil
 }
 
+type RunErr struct {
+       Tgt      string
+       DoFile   string
+       Started  *time.Time
+       Finished *time.Time
+       Err      error
+}
+
+func (e RunErr) Unwrap() error { return e.Err }
+
+func (e *RunErr) Name() string {
+       var name string
+       if e.DoFile == "" {
+               name = e.Tgt
+       } else {
+               name = fmt.Sprintf("%s (%s)", e.Tgt, e.DoFile)
+       }
+       if e.Finished == nil {
+               return name
+       }
+       return fmt.Sprintf("%s (%fsec)", name, e.Finished.Sub(*e.Started).Seconds())
+}
+
+func (e RunErr) Error() string {
+       return fmt.Sprintf("%s: %s", e.Name(), e.Err)
+}
+
+func syncDir(dir string) error {
+       fd, err := os.Open(dir)
+       if err != nil {
+               return err
+       }
+       err = fd.Sync()
+       fd.Close()
+       return err
+}
+
 func runScript(tgt string, errs chan error) error {
        tgtOrig := tgt
        cwd, tgt := cwdAndTgt(tgt)
        redoDir := path.Join(cwd, RedoDir)
-       errf := func(err error) error {
-               return fmt.Errorf("%s: %s", tgtOrig, err)
-       }
        if err := mkdirs(redoDir); err != nil {
-               return errf(err)
+               return TgtErr{tgtOrig, err}
        }
 
        // Acquire lock
        fdLock, err := os.OpenFile(
-               path.Join(redoDir, "lock."+tgt),
+               path.Join(redoDir, tgt+LockSuffix),
                os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
                os.FileMode(0666),
        )
        if err != nil {
-               return errf(err)
+               return TgtErr{tgtOrig, err}
        }
        lockRelease := func() {
                trace(CLock, "LOCK_UN: %s", fdLock.Name())
@@ -129,10 +163,12 @@ func runScript(tgt string, errs chan error) error {
                fdLock.Close()
        }
        trace(CLock, "LOCK_NB: %s", fdLock.Name())
+
+       // Waiting for job completion, already taken by someone else
        if err = unix.Flock(int(fdLock.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
                if uintptr(err.(syscall.Errno)) != uintptr(unix.EWOULDBLOCK) {
                        fdLock.Close()
-                       return errf(err)
+                       return TgtErr{tgtOrig, err}
                }
                trace(CDebug, "waiting: %s", tgtOrig)
                Jobs.Add(1)
@@ -146,7 +182,7 @@ func runScript(tgt string, errs chan error) error {
                        fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix))
                        if err != nil {
                                if os.IsNotExist(err) {
-                                       err = fmt.Errorf("%s is not built", tgtOrig)
+                                       err = errors.New("was not built")
                                }
                                goto Finish
                        }
@@ -155,11 +191,11 @@ func runScript(tgt string, errs chan error) error {
                                goto Finish
                        }
                        if !builtNow {
-                               err = fmt.Errorf("%s is not built", tgtOrig)
+                               err = errors.New("was not built")
                        }
                Finish:
                        if err != nil {
-                               err = errf(err)
+                               err = TgtErr{tgtOrig, err}
                        }
                        errs <- err
                }()
@@ -170,24 +206,20 @@ func runScript(tgt string, errs chan error) error {
        modified, err := isModified(cwd, redoDir, tgt)
        if err != nil {
                lockRelease()
-               return errf(err)
+               return TgtErr{tgtOrig, err}
        }
        if modified {
                trace(CWarn, "%s externally modified: not redoing", tgtOrig)
                lockRelease()
-               Jobs.Add(1)
-               go func() {
-                       errs <- nil
-                       Jobs.Done()
-               }()
-               return nil
+               errs <- nil
+               return TgtErr{tgtOrig, err}
        }
 
-       // Start preparing dep.
+       // Start preparing .dep
        fdDep, err := tempfile(redoDir, tgt+DepSuffix)
        if err != nil {
                lockRelease()
-               return errf(err)
+               return TgtErr{tgtOrig, err}
        }
        cleanup := func() {
                lockRelease()
@@ -198,22 +230,22 @@ func runScript(tgt string, errs chan error) error {
                recfile.Field{Name: "Build", Value: BuildUUID},
        ); err != nil {
                cleanup()
-               return errf(err)
+               return TgtErr{tgtOrig, err}
        }
 
        // Find .do
        doFile, upLevels, err := findDo(fdDep, cwd, tgt)
        if err != nil {
                cleanup()
-               return errf(err)
+               return TgtErr{tgtOrig, err}
        }
        if doFile == "" {
                cleanup()
-               return errf(errors.New("no .do found"))
+               return TgtErr{tgtOrig, errors.New("no .do found")}
        }
        if err = writeDep(fdDep, cwd, doFile); err != nil {
                cleanup()
-               return errf(err)
+               return TgtErr{tgtOrig, err}
        }
 
        // Determine basename and DIRPREFIX
@@ -226,12 +258,12 @@ func runScript(tgt string, errs chan error) error {
        }
        cwd = path.Clean(cwd)
        basename := tgt
+       runErr := RunErr{Tgt: tgtOrig}
        if strings.HasPrefix(doFile, "default.") {
                basename = tgt[:len(tgt)-(len(doFile)-len("default.")-len(".do"))-1]
-               trace(CRedo, "%s (%s)", tgtOrig, doFile)
-       } else {
-               trace(CRedo, "%s", tgtOrig)
+               runErr.DoFile = doFile
        }
+       trace(CWait, "%s", runErr.Name())
        doFile = path.Base(doFile)
 
        // Prepare command line
@@ -245,13 +277,13 @@ func runScript(tgt string, errs chan error) error {
                fd, err := os.Open(path.Join(cwd, doFile))
                if err != nil {
                        cleanup()
-                       return errf(err)
+                       return TgtErr{tgtOrig, err}
                }
                buf := make([]byte, 512)
                n, err := fd.Read(buf)
                if err != nil {
                        cleanup()
-                       return errf(err)
+                       return TgtErr{tgtOrig, err}
                }
                if n > 3 && string(buf[:3]) == "#!/" {
                        // Shebanged
@@ -259,7 +291,7 @@ func runScript(tgt string, errs chan error) error {
                        nlIdx := strings.Index(t, "\n")
                        if nlIdx == -1 {
                                cleanup()
-                               return errf(errors.New("not fully read shebang"))
+                               return TgtErr{tgtOrig, errors.New("not fully read shebang")}
                        }
                        args = strings.Split(t[:nlIdx], " ")
                        cmdName, args = args[0], args[1:]
@@ -279,7 +311,7 @@ func runScript(tgt string, errs chan error) error {
        fdStdout, err := tempfile(cwd, tgt)
        if err != nil {
                cleanup()
-               return errf(err)
+               return TgtErr{tgtOrig, err}
        }
        tmpPath := fdStdout.Name() + ".3" // and for $3
        args = append(args, tgt, basename, path.Base(tmpPath))
@@ -291,9 +323,14 @@ func runScript(tgt string, errs chan error) error {
        cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", RedoDirPrefixEnv, dirPrefix))
        cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", RedoBuildUUIDEnv, BuildUUID))
        childStderrPrefix := tempsuffix()
-       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", RedoStderrPrefixEnv, childStderrPrefix))
+       cmd.Env = append(cmd.Env, fmt.Sprintf(
+               "%s=%s", RedoStderrPrefixEnv, childStderrPrefix,
+       ))
+
        cmd.ExtraFiles = append(cmd.ExtraFiles, fdDep)
-       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoDepFdEnv, 3+0))
+       fdNum := 0
+       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoDepFdEnv, 3+fdNum))
+       fdNum++
        if JSR == nil {
                // infinite jobs
                cmd.Env = append(cmd.Env, fmt.Sprintf("%s=NO", RedoJSRFdEnv))
@@ -301,8 +338,10 @@ func runScript(tgt string, errs chan error) error {
        } else {
                cmd.ExtraFiles = append(cmd.ExtraFiles, JSR)
                cmd.ExtraFiles = append(cmd.ExtraFiles, JSW)
-               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoJSRFdEnv, 3+1))
-               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoJSWFdEnv, 3+2))
+               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoJSRFdEnv, 3+fdNum))
+               fdNum++
+               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoJSWFdEnv, 3+fdNum))
+               fdNum++
        }
 
        // Preparing stderr
@@ -319,18 +358,18 @@ func runScript(tgt string, errs chan error) error {
                )
                if err != nil {
                        cleanup()
-                       return errf(err)
+                       return TgtErr{tgtOrig, err}
                }
                fdStderr.Truncate(0)
        }
-       trace(CDebug, "sh: %s: %s %s [%s]", tgtOrig, cmdName, args, cwd)
+       shCtx := fmt.Sprintf("sh: %s: %s %s [%s]", tgtOrig, cmdName, args, cwd)
+       trace(CDebug, "%s", shCtx)
 
        Jobs.Add(1)
        go func() {
-               jsAcquire()
-               started := time.Now()
+               jsAcquire(shCtx)
                defer func() {
-                       jsRelease()
+                       jsRelease(shCtx)
                        lockRelease()
                        fdDep.Close()
                        fdStdout.Close()
@@ -341,13 +380,14 @@ func runScript(tgt string, errs chan error) error {
                        os.Remove(fdStdout.Name())
                        os.Remove(tmpPath)
                        os.Remove(fdLock.Name())
-                       finished := time.Now()
-                       trace(CDone, "%s (%fsec)", tgtOrig, finished.Sub(started).Seconds())
                        Jobs.Done()
                }()
+               started := time.Now()
+               runErr.Started = &started
                err := cmd.Start()
                if err != nil {
-                       errs <- errf(err)
+                       runErr.Err = err
+                       errs <- runErr
                        return
                }
                pid := fmt.Sprintf("[%d]", cmd.Process.Pid)
@@ -384,27 +424,33 @@ func runScript(tgt string, errs chan error) error {
                // Wait for job completion
                <-stderrTerm
                err = cmd.Wait()
+               finished := time.Now()
+               runErr.Finished = &finished
                if err != nil {
-                       errs <- errf(err)
+                       runErr.Err = err
+                       errs <- runErr
                        return
                }
 
                // Does it produce both stdout and tmp?
                fiStdout, err := os.Stat(fdStdout.Name())
                if err != nil {
-                       errs <- errf(err)
+                       runErr.Err = err
+                       errs <- runErr
                        return
                }
                tmpExists := false
                _, err = os.Stat(tmpPath)
                if err == nil {
                        if fiStdout.Size() > 0 {
-                               errs <- errf(fmt.Errorf("%s created both tmp and stdout", tgtOrig))
+                               runErr.Err = errors.New("created both tmp and stdout")
+                               errs <- runErr
                                return
                        }
                        tmpExists = true
                } else if !os.IsNotExist(err) {
-                       errs <- errf(err)
+                       runErr.Err = err
+                       errs <- runErr
                        return
                }
 
@@ -413,8 +459,7 @@ func runScript(tgt string, errs chan error) error {
                if tmpExists {
                        fd, err = os.Open(tmpPath)
                        if err != nil {
-                               errs <- errf(err)
-                               return
+                               goto Finish
                        }
                        defer fd.Close()
                } else if fiStdout.Size() > 0 {
@@ -423,63 +468,53 @@ func runScript(tgt string, errs chan error) error {
 
                // Do we need to ifcreate it, of ifchange with renaming?
                if fd == nil {
-                       if err = ifcreate(fdDep, tgt); err != nil {
-                               errs <- errf(err)
-                               return
+                       err = ifcreate(fdDep, tgt)
+                       if err != nil {
+                               goto Finish
                        }
                } else {
                        if !NoSync {
-                               if err = fd.Sync(); err != nil {
-                                       errs <- errf(err)
-                                       return
+                               err = fd.Sync()
+                               if err != nil {
+                                       goto Finish
                                }
                        }
-                       if err = os.Rename(fd.Name(), path.Join(cwdOrig, tgt)); err != nil {
-                               errs <- errf(err)
-                               return
+                       err = os.Rename(fd.Name(), path.Join(cwdOrig, tgt))
+                       if err != nil {
+                               goto Finish
                        }
                        if !NoSync {
-                               fdDir, err := os.Open(cwdOrig)
+                               err = syncDir(cwdOrig)
                                if err != nil {
-                                       errs <- errf(err)
-                                       return
-                               }
-                               defer fdDir.Close()
-                               if err = fdDir.Sync(); err != nil {
-                                       errs <- errf(err)
-                                       return
+                                       goto Finish
                                }
                        }
-                       if err = writeDep(fdDep, cwdOrig, tgt); err != nil {
-                               errs <- errf(err)
-                               return
+                       err = writeDep(fdDep, cwdOrig, tgt)
+                       if err != nil {
+                               goto Finish
                        }
                }
 
-               // Commit dep.
+               // Commit .dep
                if !NoSync {
-                       if err = fdDep.Sync(); err != nil {
-                               errs <- errf(err)
-                               return
+                       err = fdDep.Sync()
+                       if err != nil {
+                               goto Finish
                        }
                }
-               if err = os.Rename(fdDep.Name(), path.Join(redoDir, tgt+DepSuffix)); err != nil {
-                       errs <- errf(err)
-                       return
+               err = os.Rename(fdDep.Name(), path.Join(redoDir, tgt+DepSuffix))
+               if err != nil {
+                       goto Finish
                }
                if !NoSync {
-                       fdDir, err := os.Open(redoDir)
+                       err = syncDir(redoDir)
                        if err != nil {
-                               errs <- errf(err)
-                               return
-                       }
-                       defer fdDir.Close()
-                       if err = fdDir.Sync(); err != nil {
-                               errs <- errf(err)
-                               return
+                               goto Finish
                        }
                }
-               errs <- nil
+       Finish:
+               runErr.Err = err
+               errs <- runErr
        }()
        return nil
 }
index 704873cc38a7b8c2e0e8d207be3bb43429867584..d5dae5ea0f2851e3017ed0cab0c9fb1ecb676522 100644 (file)
--- a/usage.go
+++ b/usage.go
@@ -1,3 +1,20 @@
+/*
+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/>.
+*/
+
 package main
 
 import (