]> Cypherpunks.ru repositories - goredo.git/blobdiff - run.go
Trace only non-dependant targets
[goredo.git] / run.go
diff --git a/run.go b/run.go
index 58b9f6daff2fa933a17c6ff906766676169c86dd..bb455e845df4b20abc91be0a10c584367b43a301 100644 (file)
--- a/run.go
+++ b/run.go
@@ -23,6 +23,7 @@ import (
        "bufio"
        "encoding/hex"
        "errors"
+       "flag"
        "fmt"
        "io"
        "os"
@@ -30,6 +31,7 @@ import (
        "path"
        "strconv"
        "strings"
+       "sync"
        "syscall"
        "time"
 
@@ -38,6 +40,15 @@ import (
 )
 
 const (
+       EnvDepFd        = "REDO_DEP_FD"
+       EnvDirPrefix    = "REDO_DIRPREFIX"
+       EnvBuildUUID    = "REDO_BUILD_UUID"
+       EnvStderrPrefix = "REDO_STDERR_PREFIX"
+       EnvTrace        = "REDO_TRACE"
+       EnvStderrKeep   = "REDO_LOGS"
+       EnvStderrSilent = "REDO_SILENT"
+       EnvNoSync       = "REDO_NO_SYNC"
+
        RedoDir    = ".redo"
        LockSuffix = ".lock"
        DepSuffix  = ".dep"
@@ -45,6 +56,45 @@ const (
        LogSuffix  = ".log"
 )
 
+var (
+       NoSync       bool = false
+       StderrKeep   bool = false
+       StderrSilent bool = false
+       StderrPrefix string
+       Jobs         sync.WaitGroup
+
+       flagTrace        = flag.Bool("x", false, "trace current target (sh -x) (set REDO_TRACE=1 for others too)")
+       flagStderrKeep   = flag.Bool("logs", false, "keep job's stderr (REDO_LOGS=1)")
+       flagStderrSilent = flag.Bool("silent", false, "do not print job's stderr (REDO_SILENT=1)")
+)
+
+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 mkdirs(pth string) error {
        if _, err := os.Stat(pth); err == nil {
                return nil
@@ -62,23 +112,24 @@ func tempfile(dir, prefix string) (*os.File, error) {
        return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, os.FileMode(0666))
 }
 
-func isModified(cwd, redoDir, tgt string) (bool, error) {
+func isModified(cwd, redoDir, tgt string) (bool, string, error) {
        fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix))
        if err != nil {
                if os.IsNotExist(err) {
-                       return false, nil
+                       return false, "", nil
                }
-               return false, err
+               return false, "", err
        }
        defer fdDep.Close()
        r := recfile.NewReader(fdDep)
+       var ourTs string
        for {
                m, err := r.NextMap()
                if err != nil {
                        if err == io.EOF {
                                break
                        }
-                       return false, err
+                       return false, "", err
                }
                if m["Target"] != tgt {
                        continue
@@ -86,48 +137,21 @@ func isModified(cwd, redoDir, tgt string) (bool, error) {
                fd, err := os.Open(path.Join(cwd, tgt))
                if err != nil {
                        if os.IsNotExist(err) {
-                               return false, nil
+                               return false, "", nil
                        }
-                       return false, err
+                       return false, "", err
                }
                defer fd.Close()
-               ourTs, err := fileCtime(fd)
+               ourTs, err = fileCtime(fd)
                if err != nil {
-                       return false, err
+                       return false, "", err
                }
                if ourTs != m["Ctime"] {
-                       return true, nil
+                       return true, ourTs, nil
                }
                break
        }
-       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)
+       return false, ourTs, nil
 }
 
 func syncDir(dir string) error {
@@ -140,9 +164,8 @@ func syncDir(dir string) error {
        return err
 }
 
-func runScript(tgt string, errs chan error) error {
-       tgtOrig := tgt
-       cwd, tgt := cwdAndTgt(tgt)
+func runScript(tgtOrig string, errs chan error, traced bool) error {
+       cwd, tgt := cwdAndTgt(tgtOrig)
        redoDir := path.Join(cwd, RedoDir)
        if err := mkdirs(redoDir); err != nil {
                return TgtErr{tgtOrig, err}
@@ -178,20 +201,21 @@ func runScript(tgt string, errs chan error) error {
                        unix.Flock(int(fdLock.Fd()), unix.LOCK_EX)
                        lockRelease()
                        trace(CDebug, "waiting done: %s", tgtOrig)
-                       var builtNow bool
+                       var depInfo *DepInfo
                        fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix))
                        if err != nil {
                                if os.IsNotExist(err) {
-                                       err = errors.New("was not built")
+                                       err = errors.New("was not built: no .dep")
                                }
                                goto Finish
                        }
-                       builtNow, _, err = isBuiltNow(fdDep)
+                       defer fdDep.Close()
+                       depInfo, err = depRead(fdDep)
                        if err != nil {
                                goto Finish
                        }
-                       if !builtNow {
-                               err = errors.New("was not built")
+                       if depInfo.build != BuildUUID {
+                               err = errors.New("was not built: build differs")
                        }
                Finish:
                        if err != nil {
@@ -203,7 +227,7 @@ func runScript(tgt string, errs chan error) error {
        }
 
        // Check if target is not modified externally
-       modified, err := isModified(cwd, redoDir, tgt)
+       modified, tsPrev, err := isModified(cwd, redoDir, tgt)
        if err != nil {
                lockRelease()
                return TgtErr{tgtOrig, err}
@@ -211,8 +235,10 @@ func runScript(tgt string, errs chan error) error {
        if modified {
                trace(CWarn, "%s externally modified: not redoing", tgtOrig)
                lockRelease()
-               errs <- nil
-               return TgtErr{tgtOrig, err}
+               go func() {
+                       errs <- nil
+               }()
+               return nil
        }
 
        // Start preparing .dep
@@ -298,7 +324,7 @@ func runScript(tgt string, errs chan error) error {
                } else {
                        // Shell
                        cmdName = "/bin/sh"
-                       if Trace {
+                       if traced {
                                args = append(args, "-ex")
                        } else {
                                args = append(args, "-e")
@@ -319,29 +345,29 @@ func runScript(tgt string, errs chan error) error {
        cmd := exec.Command(cmdName, args...)
        cmd.Dir = cwd
        cmd.Stdout = fdStdout
-       cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%d", RedoLevelEnv, Level+1))
-       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", RedoDirPrefixEnv, dirPrefix))
-       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", RedoBuildUUIDEnv, BuildUUID))
+       cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%d", EnvLevel, Level+1))
+       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvDirPrefix, dirPrefix))
+       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvBuildUUID, BuildUUID))
+
        childStderrPrefix := tempsuffix()
        cmd.Env = append(cmd.Env, fmt.Sprintf(
-               "%s=%s", RedoStderrPrefixEnv, childStderrPrefix,
+               "%s=%s", EnvStderrPrefix, childStderrPrefix,
        ))
 
        cmd.ExtraFiles = append(cmd.ExtraFiles, fdDep)
        fdNum := 0
-       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoDepFdEnv, 3+fdNum))
+       cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", EnvDepFd, 3+fdNum))
        fdNum++
        if JSR == nil {
                // infinite jobs
-               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=NO", RedoJSRFdEnv))
-               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=NO", RedoJSWFdEnv))
+               cmd.Env = append(cmd.Env, fmt.Sprintf("%s=NO", EnvJSFd))
        } else {
                cmd.ExtraFiles = append(cmd.ExtraFiles, JSR)
                cmd.ExtraFiles = append(cmd.ExtraFiles, JSW)
-               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++
+               cmd.Env = append(cmd.Env, fmt.Sprintf(
+                       "%s=%d,%d", EnvJSFd, 3+fdNum+0, 3+fdNum+1,
+               ))
+               fdNum += 2
        }
 
        // Preparing stderr
@@ -432,6 +458,18 @@ func runScript(tgt string, errs chan error) error {
                        return
                }
 
+               // Was $1 touched?
+               if fd, err := os.Open(path.Join(cwdOrig, tgt)); err == nil {
+                       ts, err := fileCtime(fd)
+                       fd.Close()
+                       if err == nil && ts != tsPrev {
+                               runErr.Err = errors.New("$1 was explicitly touched")
+                               errs <- runErr
+                               fd.Close()
+                               return
+                       }
+               }
+
                // Does it produce both stdout and tmp?
                fiStdout, err := os.Stat(fdStdout.Name())
                if err != nil {
@@ -518,3 +556,15 @@ func runScript(tgt string, errs chan error) error {
        }()
        return nil
 }
+
+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
+}