import (
"bufio"
+ "bytes"
"encoding/hex"
"errors"
"io"
+ "log"
"os"
"path"
}
func stamp(fdDep, src *os.File) error {
- var hsh string
hsh, err := fileHash(src)
if err != nil {
return err
}
- tracef(CDebug, "stamp: %s <- %s", fdDep.Name(), hsh)
+ tracef(CDebug, "stamp: %s <- %s", fdDep.Name(), hex.EncodeToString(hsh))
return recfileWrite(
fdDep,
recfile.Field{Name: "Type", Value: DepTypeStamp},
- recfile.Field{Name: "Hash", Value: hsh},
+ recfile.Field{Name: "Hash", Value: hex.EncodeToString(hsh)},
)
}
-func fileHash(fd *os.File) (string, error) {
+func fileHash(fd *os.File) ([]byte, error) {
h := blake3.New(32, nil)
if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil {
- return "", err
+ return nil, err
}
- return hex.EncodeToString(h.Sum(nil)), nil
+ return h.Sum(nil), nil
}
-func depWrite(fdDep *os.File, cwd, tgt, hsh string) error {
+func depWrite(fdDep *os.File, cwd, tgt string, hsh []byte) error {
tracef(CDebug, "ifchange: %s <- %s", fdDep.Name(), tgt)
fd, err := os.Open(path.Join(cwd, tgt))
if err != nil {
if isDir {
return nil
}
- if hsh == "" {
+ if hsh == nil {
hsh, err = fileHash(fd)
if err != nil {
return ErrLine(err)
fields := []recfile.Field{
{Name: "Type", Value: DepTypeIfchange},
{Name: "Target", Value: tgt},
- {Name: "Hash", Value: hsh},
+ {Name: "Hash", Value: hex.EncodeToString(hsh)},
}
fields = append(fields, inode.RecfileFields()...)
return recfileWrite(fdDep, fields...)
tgtDir := path.Join(cwd, DirPrefix)
tgtRel := mustRel(tgtDir, tgtAbs)
if _, errStat := os.Stat(tgt); errStat == nil {
- err = ErrLine(depWrite(fdDep, tgtDir, tgtRel, ""))
+ err = ErrLine(depWrite(fdDep, tgtDir, tgtRel, nil))
} else {
tracef(CDebug, "ifchange: %s <- %s (non-existing)", fdDep.Name(), tgtRel)
fields := []recfile.Field{
return nil
}
+type DepInfoIfchange struct {
+ tgt string
+ inode *Inode
+ hash []byte
+}
+
type DepInfo struct {
build string
always bool
- stamp string
+ stamp []byte
ifcreates []string
- ifchanges []map[string]string
+ ifchanges []DepInfoIfchange
+}
+
+func mustHexDecode(s string) []byte {
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return b
}
var missingBuild = errors.New(".rec missing Build:")
-func depRead(fdDep io.Reader) (*DepInfo, error) {
- r := recfile.NewReader(fdDep)
+func depRead(pth string) (*DepInfo, error) {
+ data, err := os.ReadFile(pth)
+ if err != nil {
+ return nil, err
+ }
+ r := recfile.NewReader(bytes.NewReader(data))
m, err := r.NextMap()
if err != nil {
return nil, err
}
depInfo.ifcreates = append(depInfo.ifcreates, dep)
case DepTypeIfchange:
- delete(m, "Type")
- depInfo.ifchanges = append(depInfo.ifchanges, m)
+ inode, err := inodeFromRec(m)
+ if err != nil {
+ log.Print(err)
+ return nil, ErrBadRecFormat
+ }
+ hsh := mustHexDecode(m["Hash"])
+ depInfo.ifchanges = append(depInfo.ifchanges, DepInfoIfchange{
+ tgt: m["Target"], inode: inode, hash: hsh,
+ })
case DepTypeStamp:
hsh := m["Hash"]
if hsh == "" {
return nil, ErrBadRecFormat
}
- depInfo.stamp = hsh
+ depInfo.stamp = mustHexDecode(hsh)
default:
return nil, ErrBadRecFormat
}
package main
import (
+ "bytes"
+ "encoding/hex"
"errors"
"io"
"io/fs"
if err != nil {
return ErrLine(err)
}
- theirHsh := m["Hash"]
+ theirHsh := mustHexDecode(m["Hash"])
fd, err := os.Open(path.Join(root, dep))
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
if err != nil {
return ErrLine(err)
}
- if hsh != theirHsh {
+ if !bytes.Equal(hsh, theirHsh) {
tracef(
CDebug, "depfix: %s/%s -> %s: hash differs",
root, entry.Name(), dep,
fields = []recfile.Field{
{Name: "Type", Value: DepTypeIfchange},
{Name: "Target", Value: dep},
- {Name: "Hash", Value: hsh},
+ {Name: "Hash", Value: hex.EncodeToString(hsh)},
}
fields = append(fields, inode.RecfileFields()...)
fieldses[len(fieldses)-1] = fields
package main
import (
- "os"
"path"
"strings"
)
return nil
}
depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
- fdDep, err := os.Open(depPath)
- if err != nil {
- return nil
- }
- depInfo, err := depRead(fdDep)
- fdDep.Close()
+ depInfo, err := depRead(depPath)
if err != nil {
return nil
}
+ // DepInfoCache[depPath] = depInfo
seen[tgtFull] = struct{}{}
var alwayses []string
returnReady := false
returnReady = true
}
}
- for _, m := range depInfo.ifchanges {
- dep := m["Target"]
- if dep == "" {
+ for _, dep := range depInfo.ifchanges {
+ if dep.tgt == "" {
return alwayses
}
- if dep == tgt {
+ if dep.tgt == tgt {
continue
}
- if !includeSrc && isSrc(cwd, dep) {
+ if !includeSrc && isSrc(cwd, dep.tgt) {
continue
}
if !returnReady {
- depRel := cwdMustRel(cwd, dep)
+ depRel := cwdMustRel(cwd, dep.tgt)
if m, ok := deps[depRel]; ok {
m[tgtRel] = struct{}{}
} else {
deps[depRel] = m
}
alwayses = append(alwayses,
- collectDeps(cwd, dep, level+1, deps, includeSrc, seen)...)
+ collectDeps(cwd, dep.tgt, level+1, deps, includeSrc, seen)...)
}
}
return alwayses
package main
import (
+ "bytes"
"errors"
"fmt"
"io"
return ood, nil
}
depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
- fdDep, err := os.Open(depPath)
+ depInfo, err := depRead(depPath)
if err != nil {
- if isSrc(cwd, tgt) {
- ood = false
- tracef(CDebug, "ood: %s%s -> is source", indent, tgtOrig)
- } else {
- ood = true
- tracef(CDebug, "ood: %s%s -> no dep: %s", indent, tgtOrig, depPath)
+ if errors.Is(err, fs.ErrNotExist) {
+ if isSrc(cwd, tgt) {
+ ood = false
+ tracef(CDebug, "ood: %s%s -> is source", indent, tgtOrig)
+ } else {
+ ood = true
+ tracef(CDebug, "ood: %s%s -> no dep: %s", indent, tgtOrig, depPath)
+ }
+ OODCache[path.Join(cwd, tgt)] = ood
+ return ood, nil
+ }
+ if err != nil {
+ return true, TgtError{tgtOrig, ErrLine(err)}
}
- OODCache[path.Join(cwd, tgt)] = ood
- return ood, nil
- }
- depInfo, err := depRead(fdDep)
- fdDep.Close()
- if err != nil {
- return true, TgtError{tgtOrig, ErrLine(err)}
}
if depInfo.build == BuildUUID {
}
}
- for _, m := range depInfo.ifchanges {
- dep := m["Target"]
- if dep == "" {
+ for _, dep := range depInfo.ifchanges {
+ if dep.tgt == "" {
return ood, TgtError{tgtOrig, ErrMissingTarget}
}
- theirInode, err := inodeFromRec(m)
- if err != nil {
- return ood, TgtError{tgtOrig, fmt.Errorf("invalid format of .rec: %w", err)}
- }
- theirHsh := m["Hash"]
- tracef(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep)
- ood, cached = OODCache[path.Join(cwd, dep)]
+ tracef(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep.tgt)
+ ood, cached = OODCache[path.Join(cwd, dep.tgt)]
if cached {
- tracef(CDebug, "ood: %s%s -> %s: cached: %v", indent, tgtOrig, dep, ood)
+ tracef(CDebug, "ood: %s%s -> %s: cached: %v", indent, tgtOrig, dep.tgt, ood)
if ood {
goto Done
}
continue
}
- inode, err := inodeFromFileByPath(path.Join(cwd, dep))
+ inode, err := inodeFromFileByPath(path.Join(cwd, dep.tgt))
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
- tracef(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep)
+ tracef(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep.tgt)
ood = true
- OODCache[path.Join(cwd, dep)] = ood
+ OODCache[path.Join(cwd, dep.tgt)] = ood
goto Done
}
return ood, TgtError{tgtOrig, ErrLine(err)}
}
- if inode.Size != theirInode.Size {
- tracef(CDebug, "ood: %s%s -> %s: size differs", indent, tgtOrig, dep)
+ if inode.Size != dep.inode.Size {
+ tracef(CDebug, "ood: %s%s -> %s: size differs", indent, tgtOrig, dep.tgt)
ood = true
- OODCache[path.Join(cwd, dep)] = ood
+ OODCache[path.Join(cwd, dep.tgt)] = ood
goto Done
}
- if InodeTrust != InodeTrustNone && inode.Equals(theirInode) {
- tracef(CDebug, "ood: %s%s -> %s: same inode", indent, tgtOrig, dep)
+ if InodeTrust != InodeTrustNone && inode.Equals(dep.inode) {
+ tracef(CDebug, "ood: %s%s -> %s: same inode", indent, tgtOrig, dep.tgt)
} else {
- tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgtOrig, dep)
- fd, err := os.Open(path.Join(cwd, dep))
+ tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgtOrig, dep.tgt)
+ fd, err := os.Open(path.Join(cwd, dep.tgt))
if err != nil {
return ood, TgtError{tgtOrig, ErrLine(err)}
}
if err != nil {
return ood, TgtError{tgtOrig, ErrLine(err)}
}
- if theirHsh != hsh {
- tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep)
+ if !bytes.Equal(dep.hash, hsh) {
+ tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep.tgt)
ood = true
- OODCache[path.Join(cwd, dep)] = ood
+ OODCache[path.Join(cwd, dep.tgt)] = ood
goto Done
}
- tracef(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep)
+ tracef(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep.tgt)
}
- if dep == tgt {
- tracef(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep)
+ if dep.tgt == tgt {
+ tracef(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep.tgt)
continue
}
- if isSrc(cwd, dep) {
- tracef(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep)
- OODCache[path.Join(cwd, dep)] = false
+ if isSrc(cwd, dep.tgt) {
+ tracef(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep.tgt)
+ OODCache[path.Join(cwd, dep.tgt)] = false
continue
}
- if _, ok := seen[cwdMustRel(cwd, dep)]; ok {
- tracef(CDebug, "ood: %s%s -> %s: was always built", indent, tgtOrig, dep)
- OODCache[path.Join(cwd, dep)] = false
+ if _, ok := seen[cwdMustRel(cwd, dep.tgt)]; ok {
+ tracef(CDebug, "ood: %s%s -> %s: was always built", indent, tgtOrig, dep.tgt)
+ OODCache[path.Join(cwd, dep.tgt)] = false
continue
}
- depOOD, err := isOODWithTrace(cwd, dep, level+1, seen)
+ depOOD, err := isOODWithTrace(cwd, dep.tgt, level+1, seen)
if err != nil {
return ood, TgtError{tgtOrig, err}
}
if depOOD {
- tracef(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep)
+ tracef(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep.tgt)
ood = true
goto Done
}
- tracef(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep)
+ tracef(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep.tgt)
}
Done:
import (
"bufio"
+ "bytes"
"crypto/rand"
"encoding/hex"
"errors"
"flag"
"fmt"
"io"
+ "io/fs"
"log"
"os"
"os/exec"
return os.MkdirAll(pth, os.FileMode(0777))
}
-func isModified(cwd, redoDir, tgt string) (bool, *Inode, string, error) {
- fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix))
- if err != nil {
- if os.IsNotExist(err) {
- return false, nil, "", nil
- }
- return false, nil, "", ErrLine(err)
+func isModified(depInfo *DepInfo, cwd, tgt string) (
+ modified bool, ourInode *Inode, hshPrev []byte, err error,
+) {
+ if depInfo == nil {
+ return
}
- defer fdDep.Close()
- r := recfile.NewReader(fdDep)
- var modified bool
- var ourInode *Inode
- var hshPrev string
- for {
- m, err := r.NextMap()
- if err != nil {
- if errors.Is(err, io.EOF) {
- break
- }
- return false, nil, "", ErrLine(err)
- }
- if m["Type"] != DepTypeIfchange || m["Target"] != tgt {
+ for _, dep := range depInfo.ifchanges {
+ if dep.tgt != tgt {
continue
}
ourInode, err = inodeFromFileByPath(path.Join(cwd, tgt))
if err != nil {
if os.IsNotExist(err) {
- return false, nil, "", nil
+ err = nil
+ return
}
- return false, nil, "", ErrLine(err)
- }
- theirInode, err := inodeFromRec(m)
- if err != nil {
- return false, nil, "", ErrLine(err)
+ err = ErrLine(err)
+ return
}
- hshPrev = m["Hash"]
- modified = !ourInode.Equals(theirInode)
+ hshPrev = dep.hash
+ modified = !ourInode.Equals(dep.inode)
break
}
- return modified, ourInode, hshPrev, nil
+ return
}
func syncDir(dir string) error {
return nil
}
- // Check if it was already built in parallel
- if !forced {
- if build, err := depReadBuild(path.Join(redoDir, tgt+DepSuffix)); err == nil && build == BuildUUID {
+ depInfo, err := depRead(path.Join(redoDir, tgt+DepSuffix))
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ err = nil
+ } else {
lockRelease()
- errs <- nil
- return nil
+ return TgtError{tgtOrig, err}
}
}
+ // Check if it was already built in parallel
+ if !forced && depInfo != nil && depInfo.build == BuildUUID {
+ lockRelease()
+ errs <- nil
+ return nil
+ }
+
// Check if target is not modified externally
- modified, inodePrev, hshPrev, err := isModified(cwd, redoDir, tgt)
+ modified, inodePrev, hshPrev, err := isModified(depInfo, cwd, tgt)
if err != nil {
lockRelease()
return TgtError{tgtOrig, err}
}()
return nil
}
+ depInfo = nil
// Start preparing .rec
fdDep, err := tempfile(redoDir, tgt+DepSuffix)
runErr.DoFile = doFileRelPath
}
- if err = depWrite(fdDep, cwdOrig, doFileRelPath, ""); err != nil {
+ if err = depWrite(fdDep, cwdOrig, doFileRelPath, nil); err != nil {
cleanup()
return TgtError{tgtOrig, ErrLine(err)}
}
}
w := bufio.NewWriter(fdStderr)
- var depInfo *DepInfo
- fdDep, err := os.Open(fdDepPath)
- if err != nil {
- err = ErrLine(err)
- goto Err
- }
- depInfo, err = depRead(fdDep)
- fdDep.Close()
+ depInfo, err := depRead(fdDepPath)
if err != nil {
err = ErrLine(err)
goto Err
for _, dep := range depInfo.ifchanges {
fields = append(fields, recfile.Field{
Name: "Ifchange",
- Value: dep["Target"],
+ Value: dep.tgt,
})
}
_, err = recfile.NewWriter(w).WriteFields(fields...)
goto Finish
}
} else {
- var hsh string
- if hshPrev != "" {
+ var hsh []byte
+ if hshPrev != nil {
_, err = fd.Seek(0, io.SeekStart)
if err != nil {
err = ErrLine(err)
err = ErrLine(err)
goto Finish
}
- if hsh == hshPrev {
+ if bytes.Equal(hsh, hshPrev) {
tracef(CDebug, "%s has same hash, not renaming", tgtOrig)
err = ErrLine(os.Remove(fd.Name()))
if err != nil {
goto Finish
}
}
- err = ErrLine(depWrite(fdDep, cwdOrig, tgt, hshPrev))
+ err = ErrLine(depWrite(fdDep, cwdOrig, tgt, hsh))
if err != nil {
goto Finish
}
goto Finish
}
}
+ fdDep.Close()
// Post-commit .rec sanitizing
- fdDep.Close()
- if fdDepR, err := os.Open(fdDepPath); err == nil {
- depInfo, err := depRead(fdDepR)
- fdDepR.Close()
- if err != nil {
- err = ErrLine(err)
- goto Finish
- }
+ if depInfo, err := depRead(fdDepPath); err == nil {
ifchangeSeen := make(map[string]struct{}, len(depInfo.ifchanges))
for _, dep := range depInfo.ifchanges {
- ifchangeSeen[dep["Target"]] = struct{}{}
+ ifchangeSeen[dep.tgt] = struct{}{}
}
for _, dep := range depInfo.ifcreates {
if _, exists := ifchangeSeen[dep]; exists {
tracef(CWarn, "simultaneous ifcreate and ifchange records: %s", tgt)
}
}
+ } else if errors.Is(err, fs.ErrNotExist) {
+ err = nil
+ } else {
+ err = ErrLine(err)
+ goto Finish
}
Finish:
package main
import (
- "os"
+ "errors"
+ "io/fs"
"path"
)
continue
}
seenDeps[depPath] = struct{}{}
- fdDep, err := os.Open(depPath)
+ depInfo, err := depRead(depPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
continue
}
return ErrLine(err)
}
- depInfo, err := depRead(fdDep)
- fdDep.Close()
- if err != nil {
- return ErrLine(err)
- }
- for _, m := range depInfo.ifchanges {
- depTgt := m["Target"]
- depTgtAbsPath := mustAbs(path.Join(cwd, depTgt))
+ for _, dep := range depInfo.ifchanges {
+ depTgtAbsPath := mustAbs(path.Join(cwd, dep.tgt))
if _, ok := seen[depTgtAbsPath]; ok {
continue
}
seen[depTgtAbsPath] = struct{}{}
- if isSrc(cwd, depTgt) {
+ if isSrc(cwd, dep.tgt) {
srcs[cwdMustRel(depTgtAbsPath)] = struct{}{}
} else if depTgtAbsPath != tgtAbsPath {
if err := sourcesWalker(