func parseBuildLogRec(dir, tgt string) (map[string][]string, error) {
fd, err := os.Open(path.Join(dir, RedoDir, tgt+LogRecSuffix))
if err != nil {
- return nil, err
+ return nil, ErrLine(err)
}
r := recfile.NewReader(bufio.NewReader(fd))
rec, err := r.NextMapWithSlice()
fd.Close()
- return rec, err
+ return rec, ErrLine(err)
}
func depthPrefix(depth int) string {
}
durationSec, durationNsec, err := durationToInts(sub.rec["Duration"][0])
if err != nil {
- return err
+ return ErrLine(err)
}
if sub.exitCode > 0 {
fmt.Printf(
func showBuildLogCmd(m map[string][]string, depth int) error {
started, err := tai64n.Decode(m["Started"][0])
if err != nil {
- return err
+ return ErrLine(err)
}
dp := depthPrefix(depth)
fmt.Printf(
}
finished, err := tai64n.Decode(m["Finished"][0])
if err != nil {
- return err
+ return ErrLine(err)
}
durationSec, durationNsec, err := durationToInts(m["Duration"][0])
if err != nil {
- return err
+ return ErrLine(err)
}
fmt.Printf(
"%s%sStarted:\t%s\n%s%sFinished:\t%s\n%s%sDuration:\t%d.%ds\n\n",
}
fd, err := os.Open(path.Join(dirNormalized, RedoDir, tgtNormalized+LogSuffix))
if err != nil {
- return err
+ return ErrLine(err)
}
if !*flagBuildLogRecursive {
w := bufio.NewWriter(os.Stdout)
fd.Close()
if err != nil {
w.Flush()
- return err
+ return ErrLine(err)
}
- return w.Flush()
+ return ErrLine(w.Flush())
}
defer fd.Close()
subs := make([]*BuildLogJob, 0, len(buildLogRec["Ifchange"]))
}
started, err := tai64n.Decode(rec["Started"][0])
if err != nil {
- return err
+ return ErrLine(err)
}
var exitCode int
if len(rec["ExitCode"]) > 0 {
exitCode, err = strconv.Atoi(rec["ExitCode"][0])
if err != nil {
- return err
+ return ErrLine(err)
}
}
subs = append(subs, &BuildLogJob{
for {
if !scanner.Scan() {
if err = scanner.Err(); err != nil {
- return err
+ return ErrLine(err)
}
break
}
text = scanner.Text()
if text[0] != '@' {
- return errors.New("unexpected non-TAI64Ned string")
+ return ErrLine(errors.New("unexpected non-TAI64Ned string"))
}
sep = strings.IndexByte(text, byte(' '))
if sep == -1 {
}
t, err = tai64n.Decode(text[1:sep])
if err != nil {
- return err
+ return ErrLine(err)
}
for sub != nil && t.After(sub.started) {
if err = showBuildLogSub(sub, depth); err != nil {
}
dir, err := os.Open(root)
if err != nil {
- return err
+ return ErrLine(err)
}
defer dir.Close()
for {
if err == io.EOF {
break
}
- return err
+ return ErrLine(err)
}
var pth string
for _, entry := range entries {
fmt.Println(pth)
if !*DryRun {
if err = os.Remove(pth); err != nil {
- return err
+ return ErrLine(err)
}
}
}
fmt.Println(pth)
if !*DryRun {
if err = os.Remove(pth); err != nil {
- return err
+ return ErrLine(err)
}
}
}
fmt.Println(pth)
if !*DryRun {
if err = os.Remove(pth); err != nil {
- return err
+ return ErrLine(err)
}
}
}
}
dir, err := os.Open(root)
if err != nil {
- return err
+ return ErrLine(err)
}
defer dir.Close()
for {
if err == io.EOF {
break
}
- return err
+ return ErrLine(err)
}
for _, entry := range entries {
pth := path.Join(root, entry.Name())
if what == CleanupFull {
fmt.Println(pthRel)
if !*DryRun {
- err = os.RemoveAll(pth)
+ err = ErrLine(os.RemoveAll(pth))
}
} else {
err = redoDirClean(pth, what)
strings.HasPrefix(entry.Name(), TmpPrefix) {
fmt.Println(pthRel)
if !*DryRun {
- err = os.RemoveAll(pth)
+ err = ErrLine(os.RemoveAll(pth))
}
} else {
err = cleanupWalker(pth, what)
fmt.Println(pthRel)
if !*DryRun {
if err = os.Remove(pth); err != nil {
- return err
+ return ErrLine(err)
}
}
}
tracef(CDebug, "ifchange: %s <- %s", fdDep.Name(), tgt)
fd, err := os.Open(path.Join(cwd, tgt))
if err != nil {
- return err
+ return ErrLine(err)
}
defer fd.Close()
fi, err := fd.Stat()
if hsh == "" {
hsh, err = fileHash(fd)
if err != nil {
- return err
+ return ErrLine(err)
}
}
fields := []recfile.Field{
panic(err)
}
if _, errStat := os.Stat(tgt); errStat == nil {
- err = depWrite(fdDep, tgtDir, tgtRel, "")
+ err = ErrLine(depWrite(fdDep, tgtDir, tgtRel, ""))
} else {
tracef(CDebug, "ifchange: %s <- %s (non-existing)", fdDep.Name(), tgtRel)
fields := []recfile.Field{
}
inodeDummy := Inode{}
fields = append(fields, inodeDummy.RecfileFields()...)
- err = recfileWrite(fdDep, fields...)
+ err = ErrLine(recfileWrite(fdDep, fields...))
}
if err != nil {
return err
tracef(CDebug, "depfix: entering %s", root)
dir, err := os.Open(root)
if err != nil {
- return err
+ return ErrLine(err)
}
defer dir.Close()
for {
if err == io.EOF {
break
}
- return err
+ return ErrLine(err)
}
for _, entry := range entries {
if entry.IsDir() {
if os.IsNotExist(err) {
return nil
}
- return err
+ return ErrLine(err)
}
defer dir.Close()
redoDirChanged := false
if err == io.EOF {
break
}
- return err
+ return ErrLine(err)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), DepSuffix) {
fdDepPath := path.Join(redoDir, entry.Name())
fdDep, err := os.Open(fdDepPath)
if err != nil {
- return err
+ return ErrLine(err)
}
defer fdDep.Close()
r := recfile.NewReader(fdDep)
if errors.Is(err, io.EOF) {
break
}
- return err
+ return ErrLine(err)
}
fieldses = append(fieldses, fields)
m := make(map[string]string, len(fields))
tracef(CDebug, "depfix: checking %s/%s -> %s", root, entry.Name(), dep)
theirInode, err := inodeFromRec(m)
if err != nil {
- return err
+ return ErrLine(err)
}
theirHsh := m["Hash"]
fd, err := os.Open(path.Join(root, dep))
)
continue
}
- return err
+ return ErrLine(err)
}
inode, err := inodeFromFileByFd(fd)
if err != nil {
fd.Close()
- return err
+ return ErrLine(err)
}
if inode.Size != theirInode.Size {
tracef(
hsh, err := fileHash(fd)
fd.Close()
if err != nil {
- return err
+ return ErrLine(err)
}
if hsh != theirHsh {
tracef(
redoDirChanged = true
fdDep, err = tempfile(redoDir, entry.Name())
if err != nil {
- return err
+ return ErrLine(err)
}
defer fdDep.Close()
tracef(
)
w := recfile.NewWriter(fdDep)
if _, err := w.WriteFields(fieldses[0]...); err != nil {
- return err
+ return ErrLine(err)
}
fieldses = fieldses[1:]
for _, fields := range fieldses {
if _, err := w.RecordStart(); err != nil {
- return err
+ return ErrLine(err)
}
if _, err := w.WriteFields(fields...); err != nil {
- return err
+ return ErrLine(err)
}
}
if !NoSync {
if err = fdDep.Sync(); err != nil {
- return err
+ return ErrLine(err)
}
}
fdDep.Close()
if err = os.Rename(fdDep.Name(), fdDepPath); err != nil {
- return err
+ return ErrLine(err)
}
tracef(CRedo, "%s", fdDepPath)
}
}
if redoDirChanged && !NoSync {
if err = syncDir(redoDir); err != nil {
- return nil
+ return err
}
}
return nil
depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
fdDep, err := os.Open(depPath)
if err != nil {
- return nil, err
+ return nil, ErrLine(err)
}
defer fdDep.Close()
var dep string
if errors.Is(err, io.EOF) {
break
}
- return nil, err
+ return nil, ErrLine(err)
}
switch m["Type"] {
case DepTypeIfcreate:
--- /dev/null
+/*
+goredo -- djb's redo implementation on pure Go
+Copyright (C) 2020-2023 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 (
+ "errors"
+ "fmt"
+ "runtime"
+)
+
+type ErrLineErr struct {
+ err error
+ file string
+ line int
+ function string
+}
+
+func (err ErrLineErr) Error() string {
+ return fmt.Sprintf("%s:%d: %s: %s", err.file, err.line, err.function, err.err)
+}
+
+func (err ErrLineErr) Unwrap() error {
+ return err.err
+}
+
+func ErrLine(err error) error {
+ if err == nil {
+ return err
+ }
+ pc := make([]uintptr, 10)
+ n := runtime.Callers(2, pc)
+ pc = pc[:n]
+ frames := runtime.CallersFrames(pc)
+ frame, _ := frames.Next()
+ var errLine ErrLineErr
+ if errors.As(err, &errLine) {
+ errLine.function = frame.Function + "," + errLine.function
+ return errLine
+ }
+ return ErrLineErr{err, frame.File, frame.Line, frame.Function}
+}
if err != nil {
Jobs.Wait()
close(errs)
- return false, err
+ return false, ErrLine(err)
}
}
if !ood {
if err = runScript(tgt, errs, forced, traced); err != nil {
Jobs.Wait()
close(errs)
- return false, err
+ return false, ErrLine(err)
}
}
Jobs.Wait()
path.Join(Cwd, tgt),
)
if err != nil {
+ err = ErrLine(err)
break
}
err = ifcreate(fdDep, tgtRel)
var fdTmp *os.File
fdTmp, err = os.CreateTemp("", "whichdo")
if err != nil {
+ err = ErrLine(err)
break
}
if err = os.Remove(fdTmp.Name()); err != nil {
+ err = ErrLine(err)
break
}
cwd, tgt := cwdAndTgt(tgts[0])
doFile, upLevels, err := findDo(fdTmp, cwd, tgt)
if err != nil {
+ err = ErrLine(err)
break
}
_, err = fdTmp.Seek(0, io.SeekStart)
if err != nil {
+ err = ErrLine(err)
break
}
r := recfile.NewReader(fdTmp)
if errors.Is(err, io.EOF) {
break
}
+ err = ErrLine(err)
break CmdSwitch
}
fmt.Println(cwdMustRel(cwd, m["Target"]))
}
tgts, err = targetsWalker(tgts)
if err != nil {
+ err = ErrLine(err)
break
}
sort.Strings(tgts)
var tgtsKnown []string
tgtsKnown, err = targetsWalker([]string{Cwd})
if err != nil {
+ err = ErrLine(err)
break
}
deps := map[string]map[string]struct{}{}
for _, tgt := range tgts {
ood, err = isOOD(Cwd, tgt, 0, nil)
if err != nil {
+ err = ErrLine(err)
break
}
if ood {
if tgtsWasEmpty {
tgts, err = targetsWalker([]string{Cwd})
if err != nil {
+ err = ErrLine(err)
break
}
}
sort.Strings(tgts)
var srcs []string
srcs, err = sourcesWalker(tgts)
+ err = ErrLine(err)
sort.Strings(srcs)
for _, src := range srcs {
fmt.Println(src)
for _, tgt := range tgts {
inode, err = inodeFromFileByPath(tgt)
if err != nil {
+ err = ErrLine(err)
break
}
err = recfileWrite(os.Stdout, append(
[]recfile.Field{{Name: "Target", Value: tgt}},
inode.RecfileFields()...)...)
if err != nil {
+ err = ErrLine(err)
break
}
}
depInfo, err := depRead(fdDep)
fdDep.Close()
if err != nil {
- return true, TgtError{tgtOrig, err}
+ return true, TgtError{tgtOrig, ErrLine(err)}
}
if depInfo.build == BuildUUID {
OODCache[path.Join(cwd, dep)] = ood
goto Done
}
- return ood, TgtError{tgtOrig, err}
+ return ood, TgtError{tgtOrig, ErrLine(err)}
}
if inode.Size != theirInode.Size {
tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgtOrig, dep)
fd, err := os.Open(path.Join(cwd, dep))
if err != nil {
- return ood, TgtError{tgtOrig, err}
+ return ood, TgtError{tgtOrig, ErrLine(err)}
}
hsh, err := fileHash(fd)
fd.Close()
if err != nil {
- return ood, TgtError{tgtOrig, err}
+ return ood, TgtError{tgtOrig, ErrLine(err)}
}
if theirHsh != hsh {
tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep)
if os.IsNotExist(err) {
return false, nil, "", nil
}
- return false, nil, "", err
+ return false, nil, "", ErrLine(err)
}
defer fdDep.Close()
r := recfile.NewReader(fdDep)
if errors.Is(err, io.EOF) {
break
}
- return false, nil, "", err
+ return false, nil, "", ErrLine(err)
}
if m["Type"] != DepTypeIfchange || m["Target"] != tgt {
continue
if os.IsNotExist(err) {
return false, nil, "", nil
}
- return false, nil, "", err
+ return false, nil, "", ErrLine(err)
}
theirInode, err := inodeFromRec(m)
if err != nil {
- return false, nil, "", err
+ return false, nil, "", ErrLine(err)
}
hshPrev = m["Hash"]
modified = !ourInode.Equals(theirInode)
cwd, tgt := cwdAndTgt(tgtOrig)
redoDir := path.Join(cwd, RedoDir)
if err := mkdirs(redoDir); err != nil {
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
shCtx := fmt.Sprintf("sh: %s: cwd:%s", tgtOrig, cwd)
os.FileMode(0666),
)
if err != nil {
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
flock := unix.Flock_t{
Type: unix.F_WRLCK,
if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLK, &flock); err != nil {
if uintptr(err.(syscall.Errno)) != uintptr(unix.EAGAIN) {
fdLock.Close()
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
Jobs.Add(1)
if err = unix.FcntlFlock(fdLock.Fd(), unix.F_GETLK, &flock); err != nil {
fdDep, err := tempfile(redoDir, tgt+DepSuffix)
if err != nil {
lockRelease()
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
fdDepPath := fdDep.Name()
cleanup := func() {
recfile.Field{Name: "Build", Value: BuildUUID},
); err != nil {
cleanup()
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
// Find .do
doFile, upLevels, err := findDo(fdDep, cwd, tgt)
if err != nil {
cleanup()
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
if doFile == "" {
cleanup()
if err = depWrite(fdDep, cwdOrig, doFileRelPath, ""); err != nil {
cleanup()
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
fdDep.Close()
tracef(CWait, "%s", runErr.Name())
fdStdout, err := tempfile(cwdOrig, tgt)
if err != nil {
cleanup()
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
stdoutPath := fdStdout.Name()
fdStdout.Close()
)
if err != nil {
cleanup()
- return TgtError{tgtOrig, err}
+ return TgtError{tgtOrig, ErrLine(err)}
}
}
tracef(CDebug, "%s", shCtx)
var depInfo *DepInfo
fdDep, err := os.Open(fdDepPath)
if err != nil {
+ err = ErrLine(err)
goto Err
}
depInfo, err = depRead(fdDep)
fdDep.Close()
if err != nil {
+ err = ErrLine(err)
goto Err
}
for _, dep := range depInfo.ifchanges {
}
_, err = recfile.NewWriter(w).WriteFields(fields...)
if err != nil {
+ err = ErrLine(err)
goto Err
}
- err = w.Flush()
+ err = ErrLine(w.Flush())
Err:
if err != nil {
log.Print(err)
// Does it produce both stdout and tmp?
fiStdout, err := os.Stat(fdStdout.Name())
if err != nil {
- runErr.Err = err
+ runErr.Err = ErrLine(err)
errs <- runErr
return
}
}
tmpExists = true
} else if !os.IsNotExist(err) {
- runErr.Err = err
+ runErr.Err = ErrLine(err)
errs <- runErr
return
}
if tmpExists {
fd, err = os.Open(tmpPath)
if err != nil {
+ err = ErrLine(err)
goto Finish
}
defer fd.Close()
os.Remove(path.Join(cwdOrig, tgt))
err = ifcreate(fdDep, tgt)
if err != nil {
+ err = ErrLine(err)
goto Finish
}
} else {
if hshPrev != "" {
_, err = fd.Seek(0, io.SeekStart)
if err != nil {
+ err = ErrLine(err)
goto Finish
}
hsh, err = fileHash(fd)
if err != nil {
+ err = ErrLine(err)
goto Finish
}
if hsh == hshPrev {
tracef(CDebug, "%s has same hash, not renaming", tgtOrig)
- err = os.Remove(fd.Name())
+ err = ErrLine(os.Remove(fd.Name()))
if err != nil {
goto Finish
}
- err = os.Chtimes(path.Join(cwdOrig, tgt), finished, finished)
+ err = ErrLine(os.Chtimes(path.Join(cwdOrig, tgt), finished, finished))
if err != nil {
goto Finish
}
if !NoSync {
- err = syncDir(cwdOrig)
+ err = ErrLine(syncDir(cwdOrig))
if err != nil {
goto Finish
}
}
- err = depWrite(fdDep, cwdOrig, tgt, hshPrev)
+ err = ErrLine(depWrite(fdDep, cwdOrig, tgt, hshPrev))
if err != nil {
goto Finish
}
}
}
if !NoSync {
- err = fd.Sync()
+ err = ErrLine(fd.Sync())
if err != nil {
goto Finish
}
}
- err = os.Rename(fd.Name(), path.Join(cwdOrig, tgt))
+ err = ErrLine(os.Rename(fd.Name(), path.Join(cwdOrig, tgt)))
if err != nil {
goto Finish
}
if !NoSync {
- err = syncDir(cwdOrig)
+ err = ErrLine(syncDir(cwdOrig))
if err != nil {
goto Finish
}
}
- err = depWrite(fdDep, cwdOrig, tgt, hsh)
+ err = ErrLine(depWrite(fdDep, cwdOrig, tgt, hsh))
if err != nil {
goto Finish
}
RecCommit:
// Commit .rec
if !NoSync {
- err = fdDep.Sync()
+ err = ErrLine(fdDep.Sync())
if err != nil {
goto Finish
}
}
fdDepPath = path.Join(redoDir, tgt+DepSuffix)
- err = os.Rename(fdDep.Name(), fdDepPath)
+ err = ErrLine(os.Rename(fdDep.Name(), fdDepPath))
if err != nil {
goto Finish
}
if !NoSync {
- err = syncDir(redoDir)
+ err = ErrLine(syncDir(redoDir))
if err != nil {
goto Finish
}
depInfo, err := depRead(fdDepR)
fdDepR.Close()
if err != nil {
+ err = ErrLine(err)
goto Finish
}
ifchangeSeen := make(map[string]struct{}, len(depInfo.ifchanges))
if os.IsNotExist(err) {
continue
}
- return nil, err
+ return nil, ErrLine(err)
}
depInfo, err := depRead(fdDep)
fdDep.Close()
if err != nil {
- return nil, err
+ return nil, ErrLine(err)
}
for _, m := range depInfo.ifchanges {
depTgt := m["Target"]
}
dir, err := os.Open(root)
if err != nil {
- return err
+ return ErrLine(err)
}
defer dir.Close()
for {
if err == io.EOF {
break
}
- return err
+ return ErrLine(err)
}
for _, entry := range entries {
if !entry.IsDir() {
if entry.Name() == RedoDir {
redoDir, err := os.Open(pth)
if err != nil {
- return err
+ return ErrLine(err)
}
redoEntries, err := redoDir.ReadDir(0)
redoDir.Close()
if err != nil {
- return err
+ return ErrLine(err)
}
for _, redoEntry := range redoEntries {
name := redoEntry.Name()