import (
"bufio"
"bytes"
- "crypto/rand"
- "errors"
+ "encoding/hex"
"flag"
"fmt"
"io"
"os"
"os/signal"
"path"
- "path/filepath"
"runtime"
"sort"
"strconv"
"syscall"
+ "github.com/google/uuid"
"go.cypherpunks.ru/recfile"
"golang.org/x/sys/unix"
)
CmdNameRedoAffects = "redo-affects"
CmdNameRedoAlways = "redo-always"
CmdNameRedoCleanup = "redo-cleanup"
+ CmdNameRedoDep2Rec = "redo-dep2rec"
+ CmdNameRedoDepFix = "redo-depfix"
CmdNameRedoDot = "redo-dot"
CmdNameRedoIfchange = "redo-ifchange"
CmdNameRedoIfcreate = "redo-ifcreate"
+ CmdNameRedoInode = "redo-inode"
CmdNameRedoLog = "redo-log"
CmdNameRedoOOD = "redo-ood"
CmdNameRedoSources = "redo-sources"
CmdNameRedoStamp = "redo-stamp"
CmdNameRedoTargets = "redo-targets"
CmdNameRedoWhichdo = "redo-whichdo"
- CmdNameRedoDepFix = "redo-depfix"
- CmdNameRedoInode = "redo-inode"
)
var (
Cwd string
- BuildUUID string
+ BuildUUID uuid.UUID
IsTopRedo bool // is it the top redo instance
UmaskCur int
)
}
flag.Usage = func() { usage(os.Args[0]) }
- flag.Parse()
+
+ BuildUUIDStr := os.Getenv(EnvBuildUUID)
+ IsTopRedo = BuildUUIDStr == ""
+
+ var args []string
+ if IsTopRedo {
+ flag.Parse()
+ args = flag.Args()
+ } else {
+ args = os.Args[1:]
+ }
+
if *warranty {
fmt.Println(Warranty)
return
CmdNameRedoAffects,
CmdNameRedoAlways,
CmdNameRedoCleanup,
+ CmdNameRedoDep2Rec,
CmdNameRedoDepFix,
CmdNameRedoDot,
CmdNameRedoIfchange,
CmdNameRedoIfcreate,
+ CmdNameRedoInode,
CmdNameRedoLog,
CmdNameRedoOOD,
CmdNameRedoSources,
fmt.Println(os.Args[0], "<-", cmdName)
if err := os.Symlink(os.Args[0], cmdName); err != nil {
rc = 1
- log.Println(err)
+ log.Print(err)
}
}
os.Exit(rc)
var err error
Cwd, err = os.Getwd()
if err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
TopDir = os.Getenv(EnvTopDir)
if TopDir == "" {
TopDir = "/"
} else {
- TopDir, err = filepath.Abs(TopDir)
- if err != nil {
- panic(err)
- }
+ TopDir = mustAbs(TopDir)
}
DirPrefix = os.Getenv(EnvDirPrefix)
DepCwd = os.Getenv(EnvDepCwd)
default:
log.Fatalln("unknown", EnvInodeTrust, "value")
}
- tracef(CDebug, "inode-trust: %s", InodeTrust)
// Those are internal envs
FdOODTgts, err = os.CreateTemp("", "ood-tgts")
if err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
if err = os.Remove(FdOODTgts.Name()); err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
FdOODTgtsLock, err = os.CreateTemp("", "ood-tgts.lock")
if err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
if err = os.Remove(FdOODTgtsLock.Name()); err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
+ var fdLock *os.File
if v := os.Getenv(EnvOODTgtsFd); v != "" {
fd := mustParseFd(v, EnvOODTgtsFd)
- fdLock := mustParseFd(v, EnvOODTgtsLockFd)
+ fdLock = mustParseFd(v, EnvOODTgtsLockFd)
+ defer fdLock.Close()
flock := unix.Flock_t{
Type: unix.F_WRLCK,
Whence: io.SeekStart,
}
if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLKW, &flock); err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
if _, err = fd.Seek(0, io.SeekStart); err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
tgtsRaw, err := io.ReadAll(bufio.NewReader(fd))
if err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
flock.Type = unix.F_UNLCK
if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLK, &flock); err != nil {
- log.Fatalln(err)
+ log.Fatal(err)
}
- OODTgts = map[string]struct{}{}
+ OODTgts = make(map[string]struct{})
for _, tgtRaw := range bytes.Split(tgtsRaw, []byte{0}) {
t := string(tgtRaw)
if t == "" {
fdDep = mustParseFd(v, EnvDepFd)
}
- tgts := flag.Args()
- BuildUUID = os.Getenv(EnvBuildUUID)
+ tgts := make([]*Tgt, 0, len(args))
+ for _, arg := range args {
+ tgts = append(tgts, NewTgt(arg))
+ }
tgtsWasEmpty := len(tgts) == 0
- if BuildUUID == "" {
- IsTopRedo = true
- raw := new([16]byte)
- if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil {
- log.Fatalln(err)
- }
- raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4
- BuildUUID = fmt.Sprintf(
- "%x-%x-%x-%x-%x",
- raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
- )
+ if BuildUUIDStr == "" {
+ BuildUUID = uuid.New()
if tgtsWasEmpty {
- tgts = []string{"all"}
+ tgts = append(tgts, NewTgt("all"))
+ }
+ tracef(CDebug, "inode-trust: %s", InodeTrust)
+ } else {
+ BuildUUID, err = uuid.Parse(BuildUUIDStr)
+ if err != nil {
+ log.Fatal(err)
}
}
statusInit()
}
- for i, tgt := range tgts {
- if path.IsAbs(tgt) {
- tgts[i] = cwdMustRel(tgt)
- }
- }
-
killed := make(chan os.Signal, 1)
signal.Notify(killed, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
go func() {
ok := true
err = nil
- tracef(
- CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
- BuildUUID, cmdName, tgts, Cwd, DirPrefix,
- )
+ tracef(CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
+ BuildUUID, cmdName, tgts, Cwd, DirPrefix)
-CmdSwitch:
switch cmdName {
case CmdNameRedo:
for _, tgt := range tgts {
- ok, err = ifchange([]string{tgt}, true, traced)
+ ok, err = ifchange([]*Tgt{tgt}, true, traced)
if err != nil || !ok {
break
}
if fdDep == nil {
log.Fatalln("no", EnvDepFd)
}
+ fdDepW := bufio.NewWriter(fdDep)
for _, tgt := range tgts {
- tgtRel, err := filepath.Rel(
- path.Join(Cwd, DirPrefix),
- path.Join(Cwd, tgt),
- )
- if err != nil {
- break
- }
- err = ifcreate(fdDep, tgtRel)
+ err = ifcreate(fdDepW, fdDep.Name(), tgt.RelTo(path.Join(Cwd, DirPrefix)))
if err != nil {
break
}
}
+ err = fdDepW.Flush()
case CmdNameRedoAlways:
if fdDep == nil {
log.Fatalln("no", EnvDepFd)
}
- err = always(fdDep)
+ err = always(fdDep, fdDep.Name())
case CmdNameRedoCleanup:
for _, what := range tgts {
- err = cleanupWalker(Cwd, what)
+ err = cleanupWalker(Cwd, path.Base(what.a))
if err != nil {
break
}
if fdDep == nil {
log.Fatalln("no", EnvDepFd)
}
- err = stamp(fdDep, os.Stdin)
+ var hsh Hash
+ hsh, err = fileHash(os.Stdin)
+ if err != nil {
+ break
+ }
+ err = stamp(fdDep, fdDep.Name(), hsh)
case CmdNameRedoLog:
if len(tgts) != 1 {
- log.Fatalln("single target expected")
+ log.Fatal("single target expected")
}
- d, t := cwdAndTgt(tgts[0])
- err = showBuildLog(d, t, nil, 0)
+ err = showBuildLog(tgts[0], nil, 0)
case CmdNameRedoWhichdo:
if len(tgts) != 1 {
- log.Fatalln("single target expected")
+ log.Fatal("single target expected")
}
- var fdTmp *os.File
- fdTmp, err = os.CreateTemp("", "whichdo")
+ var dos []string
+ dos, err = whichdo(tgts[0])
if err != nil {
break
}
- if err = os.Remove(fdTmp.Name()); err != nil {
- break
- }
- cwd, tgt := cwdAndTgt(tgts[0])
- doFile, upLevels, err := findDo(fdTmp, cwd, tgt)
- if err != nil {
- break
- }
- _, err = fdTmp.Seek(0, io.SeekStart)
- if err != nil {
- break
- }
- r := recfile.NewReader(fdTmp)
- for {
- m, err := r.NextMap()
- if err != nil {
- if errors.Is(err, io.EOF) {
- break
- }
- break CmdSwitch
- }
- fmt.Println(cwdMustRel(cwd, m["Target"]))
- }
- if doFile == "" {
- ok = false
- } else {
- p := make([]string, 0, upLevels+2)
- p = append(p, cwd)
- for i := 0; i < upLevels; i++ {
- p = append(p, "..")
- }
- p = append(p, doFile)
- rel, err := filepath.Rel(Cwd, path.Join(p...))
- if err != nil {
- panic(err)
- }
- fmt.Println(rel)
+ sort.Strings(dos)
+ for _, do := range dos {
+ fmt.Println(do)
}
case CmdNameRedoTargets:
+ raws := make([]string, 0, len(tgts))
+ for _, tgt := range tgts {
+ raws = append(raws, tgt.rel)
+ }
if tgtsWasEmpty {
- tgts = []string{Cwd}
+ raws = []string{Cwd}
}
- tgts, err = targetsWalker(tgts)
+ raws, err = targetsWalker(raws)
if err != nil {
+ err = ErrLine(err)
break
}
- sort.Strings(tgts)
- for _, tgt := range tgts {
+ sort.Strings(raws)
+ for _, tgt := range raws {
fmt.Println(tgt)
}
case CmdNameRedoAffects:
if tgtsWasEmpty {
- log.Fatalln("no targets specified")
- }
- var tgtsKnown []string
- tgtsKnown, err = targetsWalker([]string{Cwd})
- if err != nil {
- break
- }
- deps := map[string]map[string]struct{}{}
- for _, tgt := range tgtsKnown {
- collectDeps(Cwd, tgt, 0, deps, true, map[string]struct{}{})
+ log.Fatal("no targets specified")
}
- seen := map[string]struct{}{}
- for _, tgt := range tgts {
- collectWholeDeps(deps[tgt], deps, seen)
- }
- tgts := make([]string, 0, len(seen))
- for dep := range seen {
- tgts = append(tgts, dep)
+ var res []string
+ {
+ var tgtsKnown []string
+ tgtsKnown, err = targetsWalker([]string{Cwd})
+ if err != nil {
+ err = ErrLine(err)
+ break
+ }
+ deps := make(map[string]map[string]*Tgt)
+ for _, tgt := range tgtsKnown {
+ collectDeps(NewTgt(tgt), 0, deps, true)
+ }
+ seen := make(map[string]*Tgt)
+ for _, tgt := range tgts {
+ collectWholeDeps(deps[tgt.rel], deps, seen)
+ }
+ res = make([]string, 0, len(seen))
+ for _, dep := range seen {
+ res = append(res, dep.rel)
+ }
}
- sort.Strings(tgts)
- for _, dep := range tgts {
+ sort.Strings(res)
+ for _, dep := range res {
fmt.Println(dep)
}
case CmdNameRedoOOD:
+ raws := make([]string, 0, len(tgts))
+ for _, tgt := range tgts {
+ raws = append(raws, tgt.rel)
+ }
if tgtsWasEmpty {
- tgts, err = targetsWalker([]string{Cwd})
+ raws, err = targetsWalker([]string{Cwd})
if err != nil {
break
}
}
- sort.Strings(tgts)
+ sort.Strings(raws)
var ood bool
- for _, tgt := range tgts {
- ood, err = isOOD(Cwd, tgt, 0, nil)
+ for _, tgt := range raws {
+ ood, err = isOOD(NewTgt(tgt), 0, nil)
if err != nil {
+ err = ErrLine(err)
break
}
if ood {
}
}
case CmdNameRedoSources:
- if tgtsWasEmpty {
- tgts, err = targetsWalker([]string{Cwd})
- if err != nil {
- break
+ srcs := make(map[string]*Tgt)
+ {
+ raws := make([]string, 0, len(tgts))
+ for _, tgt := range tgts {
+ raws = append(raws, tgt.rel)
+ }
+ if tgtsWasEmpty {
+ raws, err = targetsWalker([]string{Cwd})
+ if err != nil {
+ err = ErrLine(err)
+ break
+ }
+ }
+ sort.Strings(raws)
+ tgts = tgts[:0]
+ for _, raw := range raws {
+ tgts = append(tgts, NewTgt(raw))
}
+ seen := make(map[string]struct{})
+ seenDeps := make(map[string]struct{})
+ err = ErrLine(sourcesWalker(tgts, seen, seenDeps, srcs))
+ }
+ if err != nil {
+ break
+ }
+ res := make([]string, 0, len(srcs))
+ for _, tgt := range srcs {
+ res = append(res, tgt.rel)
}
- sort.Strings(tgts)
- var srcs []string
- srcs, err = sourcesWalker(tgts)
- sort.Strings(srcs)
- for _, src := range srcs {
+ srcs = nil
+ sort.Strings(res)
+ for _, src := range res {
fmt.Println(src)
}
case CmdNameRedoDepFix:
+ IfchangeCache = nil
+ DepFixHashCache = make(map[string]Hash)
err = depFix(Cwd)
case CmdNameRedoInode:
- var inode *Inode
+ var inode Inode
for _, tgt := range tgts {
- inode, err = inodeFromFileByPath(tgt)
+ inode, err = inodeFromFileByPath(tgt.a)
if err != nil {
+ err = ErrLine(err)
break
}
err = recfileWrite(os.Stdout, append(
- []recfile.Field{{Name: "Target", Value: tgt}},
+ []recfile.Field{{Name: "Target", Value: tgt.String()}},
inode.RecfileFields()...)...)
+ if err != nil {
+ err = ErrLine(err)
+ break
+ }
+ }
+ case CmdNameRedoDep2Rec:
+ var data []byte
+ data, err = os.ReadFile(tgts[0].a)
+ if err != nil {
+ break
+ }
+ var build uuid.UUID
+ build, data, err = depHeadParse(data)
+ if err != nil {
+ break
+ }
+ w := bufio.NewWriter(os.Stdout)
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Build", Value: build.String()},
+ }...)
+ if err != nil {
+ break
+ }
+ var typ byte
+ var chunk []byte
+ for len(data) > 0 {
+ typ, chunk, data, _ = chunkRead(data)
+ switch typ {
+ case DepTypeAlways:
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Type", Value: "always"},
+ }...)
+ case DepTypeStamp:
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Type", Value: "stamp"},
+ {Name: "Hash", Value: hex.EncodeToString(chunk)},
+ }...)
+ case DepTypeIfcreate:
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Type", Value: "ifcreate"},
+ {Name: "Target", Value: string(chunk)},
+ }...)
+ case DepTypeIfchange:
+ name := string(chunk[InodeLen+HashLen:])
+ meta := string(chunk[:InodeLen+HashLen])
+ fields := []recfile.Field{
+ {Name: "Type", Value: "ifchange"},
+ {Name: "Target", Value: name},
+ }
+ fields = append(fields, recfile.Field{
+ Name: "Hash", Value: Hash(meta[InodeLen:]).String(),
+ })
+ fields = append(fields, Inode(meta[:InodeLen]).RecfileFields()...)
+ err = recfileWrite(w, fields...)
+ case DepTypeIfchangeDummy:
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Type", Value: "ifchange"},
+ {Name: "Target", Value: string(chunk)},
+ }...)
+ }
if err != nil {
break
}
}
+ err = w.Flush()
default:
log.Fatalln("unknown command", cmdName)
}
if err != nil {
- log.Println(err)
+ log.Print(err)
}
rc := 0
if !ok || err != nil {