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*
--- /dev/null
+/*
+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
+}
if jsrRaw != "" {
JSR = mustParseFd(jsrRaw, "JSR")
JSW = mustParseFd(jswRaw, "JSW")
- jsRelease()
+ jsRelease("ifchange entered")
return
}
if *JobsN == 0 {
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)
}
CDebug = CYellow
CRedo = CGreen
- CDone = CBlue
+ CWait = CBlue
CLock = CCyan
CErr = CRed
CWarn = CMagenta
var (
NoColor bool
Debug bool
- LogDone bool
+ LogWait bool
LogLock bool
LogJS bool
MyPid int
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
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)")
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")
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" {
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)
}
"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)
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)
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)
}
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 {
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())
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)
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
}
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
}()
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()
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
}
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
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
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:]
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))
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))
} 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
)
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()
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)
// 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
}
if tmpExists {
fd, err = os.Open(tmpPath)
if err != nil {
- errs <- errf(err)
- return
+ goto Finish
}
defer fd.Close()
} else if fiStdout.Size() > 0 {
// 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
}
+/*
+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 (