import (
"bufio"
+ "bytes"
"crypto/rand"
"encoding/hex"
"errors"
"flag"
"fmt"
"io"
+ "io/fs"
"log"
"os"
"os/exec"
"path"
- "path/filepath"
"strconv"
"strings"
"sync"
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 {
log.Fatal(err)
}
}
- var depInfo *DepInfo
- fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix))
- if err != nil {
- if os.IsNotExist(err) {
+ build, err := depReadBuild(path.Join(redoDir, tgt+DepSuffix))
+ if err == nil {
+ if build != BuildUUID {
+ err = errors.New("was not built: build differs")
+ }
+ } else {
+ if errors.Is(err, fs.ErrNotExist) {
err = errors.New("was not built: no .rec")
}
- goto Finish
- }
- defer fdDep.Close()
- depInfo, err = depRead(fdDep)
- if err != nil {
- goto Finish
- }
- if depInfo.build != BuildUUID {
- err = errors.New("was not built: build differs")
}
- Finish:
if err != nil {
err = TgtError{tgtOrig, err}
- fdLock.Close()
}
errs <- err
}()
return nil
}
- // Check if it was already built in parallel
- if !forced {
- if fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix)); err == nil {
- depInfo, err := depRead(fdDep)
- fdDep.Close()
- if err == nil && depInfo.build == BuildUUID {
- lockRelease()
- errs <- nil
- return nil
- }
+ // It scans the whole .rec file while searching for the single target,
+ // but that one is always located at the very end
+ depInfo, err := depRead(path.Join(redoDir, tgt+DepSuffix))
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ err = nil
+ } else {
+ lockRelease()
+ 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)
lockRelease()
return TgtError{tgtOrig, ErrLine(err)}
}
+ fdDepOpened := true
fdDepPath := fdDep.Name()
cleanup := func() {
lockRelease()
- fdDep.Close()
+ if fdDepOpened {
+ fdDep.Close()
+ }
os.Remove(fdDep.Name())
}
if _, err = recfile.NewWriter(fdDep).WriteFields(
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)}
}
fdDep.Close()
+ fdDepOpened = false
tracef(CWait, "%s", runErr.Name())
// Prepare command line
stdoutPath := fdStdout.Name()
fdStdout.Close()
tmpPath := stdoutPath + ".3" // and for $3
- tmpPathRel, err := filepath.Rel(cwd, tmpPath)
- if err != nil {
- panic(err)
- }
+ tmpPathRel := mustRel(cwd, tmpPath)
args = append(
args,
path.Join(dirPrefix, tgt),
errs <- runErr
return
}
+ fdDepOpened = true
cmd.ExtraFiles = append(cmd.ExtraFiles, fdDep)
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", EnvDepFd, 3+fdNum))
fdNum++
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvDepCwd, cwd))
defer func() {
- fdDep.Close()
+ if fdDepOpened {
+ fdDep.Close()
+ }
fdStdout.Close()
if fdStderr != nil {
fdStderr.Close()
}
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...)
return
}
tmpExists = true
- } else if !os.IsNotExist(err) {
+ } else if !errors.Is(err, fs.ErrNotExist) {
runErr.Err = ErrLine(err)
errs <- runErr
return
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()
+ fdDepOpened = false
// 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: