From: Sergey Matveev Date: Tue, 3 Oct 2023 18:06:01 +0000 (+0300) Subject: Refactor target paths, less CPU, less memory, more clarity X-Git-Tag: v2.0.0~24 X-Git-Url: http://www.git.cypherpunks.ru/?p=goredo.git;a=commitdiff_plain;h=3f8ee87bd625a38b57a0a38e3c34bb26fe6e1b8e Refactor target paths, less CPU, less memory, more clarity --- diff --git a/buildlog.go b/buildlog.go index b0d2f2d..956cf4f 100644 --- a/buildlog.go +++ b/buildlog.go @@ -38,8 +38,7 @@ import ( const HumanTimeFmt = "2006-01-02 15:04:05.000000000 Z07:00" type BuildLogJob struct { - dir string - tgt string + tgt *Tgt started time.Time exitCode int rec map[string][]string @@ -75,8 +74,8 @@ func init() { buildLogSeen = make(map[string]struct{}) } -func parseBuildLogRec(dir, tgt string) (map[string][]string, error) { - fd, err := os.Open(path.Join(dir, RedoDir, tgt+LogRecSuffix)) +func parseBuildLogRec(tgt *Tgt) (map[string][]string, error) { + fd, err := os.Open(path.Join(tgt.h, RedoDir, tgt.t+LogRecSuffix)) if err != nil { return nil, ErrLine(err) } @@ -94,18 +93,17 @@ func depthPrefix(depth int) string { } func showBuildLogSub(sub *BuildLogJob, depth int) error { - abs := mustAbs(path.Join(sub.dir, sub.tgt)) - if _, ok := buildLogSeen[abs]; ok { + if _, ok := buildLogSeen[sub.tgt.a]; ok { return nil } - buildLogSeen[abs] = struct{}{} + buildLogSeen[sub.tgt.a] = struct{}{} dp := depthPrefix(depth) fmt.Printf( "%s%s%s\n", sub.rec["Started"][0], dp, - colourize(CRedo, "redo "+sub.tgt), + colourize(CRedo, "redo "+sub.tgt.String()), ) - if err := showBuildLog(sub.dir, sub.tgt, sub.rec, depth+1); err != nil { + if err := showBuildLog(sub.tgt, sub.rec, depth+1); err != nil { return err } durationSec, durationNsec, err := durationToInts(sub.rec["Duration"][0]) @@ -116,14 +114,14 @@ func showBuildLogSub(sub *BuildLogJob, depth int) error { fmt.Printf( "%s%s%s (code: %d) (%d.%ds)\n\n", sub.rec["Finished"][0], dp, - colourize(CErr, "err "+sub.tgt), + colourize(CErr, "err "+sub.tgt.String()), sub.exitCode, durationSec, durationNsec, ) } else { fmt.Printf( "%s%s%s (%d.%ds)\n\n", sub.rec["Finished"][0], dp, - colourize(CRedo, "done "+sub.tgt), + colourize(CRedo, "done "+sub.tgt.String()), durationSec, durationNsec, ) } @@ -168,11 +166,10 @@ func showBuildLogCmd(m map[string][]string, depth int) error { return nil } -func showBuildLog(dir, tgt string, buildLogRec map[string][]string, depth int) error { +func showBuildLog(tgt *Tgt, buildLogRec map[string][]string, depth int) error { var err error - dirNormalized, tgtNormalized := cwdAndTgt(path.Join(dir, tgt)) if *flagBuildLogCommands || *flagBuildLogRecursive { - buildLogRec, err = parseBuildLogRec(dirNormalized, tgtNormalized) + buildLogRec, err = parseBuildLogRec(tgt) if err != nil { return err } @@ -182,7 +179,7 @@ func showBuildLog(dir, tgt string, buildLogRec map[string][]string, depth int) e return err } } - fd, err := os.Open(path.Join(dirNormalized, RedoDir, tgtNormalized+LogSuffix)) + fd, err := os.Open(path.Join(tgt.h, RedoDir, tgt.t+LogSuffix)) if err != nil { return ErrLine(err) } @@ -198,12 +195,12 @@ func showBuildLog(dir, tgt string, buildLogRec map[string][]string, depth int) e } defer fd.Close() subs := make([]*BuildLogJob, 0, len(buildLogRec["Ifchange"])) - for _, dep := range buildLogRec["Ifchange"] { - subDir, subTgt := cwdAndTgt(path.Join(dirNormalized, dep)) - if subDir == dirNormalized && subTgt == tgtNormalized { + for _, depPath := range buildLogRec["Ifchange"] { + dep := NewTgt(path.Join(tgt.h, depPath)) + if dep.a == tgt.a { continue } - rec, err := parseBuildLogRec(subDir, subTgt) + rec, err := parseBuildLogRec(dep) if err != nil { if errors.Is(err, fs.ErrNotExist) { continue @@ -225,7 +222,6 @@ func showBuildLog(dir, tgt string, buildLogRec map[string][]string, depth int) e } } subs = append(subs, &BuildLogJob{ - dir: dirNormalized, tgt: dep, started: started, exitCode: exitCode, diff --git a/dep.go b/dep.go index 6644ac5..e587e6a 100644 --- a/dep.go +++ b/dep.go @@ -86,9 +86,9 @@ func fileHash(fd *os.File) ([]byte, error) { return h.Sum(nil), nil } -func depWrite(fdDep *os.File, cwd, tgt string, hsh []byte) error { +func depWrite(fdDep *os.File, cwd string, tgt *Tgt, hsh []byte) error { tracef(CDebug, "ifchange: %s <- %s", fdDep.Name(), tgt) - fd, err := os.Open(path.Join(cwd, tgt)) + fd, err := os.Open(tgt.a) if err != nil { return ErrLine(err) } @@ -108,30 +108,30 @@ func depWrite(fdDep *os.File, cwd, tgt string, hsh []byte) error { } fields := []recfile.Field{ {Name: "Type", Value: DepTypeIfchange}, - {Name: "Target", Value: tgt}, + {Name: "Target", Value: tgt.RelTo(cwd)}, {Name: "Hash", Value: hex.EncodeToString(hsh)}, } fields = append(fields, inode.RecfileFields()...) return recfileWrite(fdDep, fields...) } -func depsWrite(fdDep *os.File, tgts []string) error { +func depsWrite(fdDep *os.File, tgts []*Tgt) error { if fdDep == nil { tracef(CDebug, "no opened fdDep: %s", tgts) return nil } var err error + var cwd string for _, tgt := range tgts { - tgtAbs := mustAbs(tgt) - cwd := Cwd + cwd = Cwd if DepCwd != "" && Cwd != DepCwd { cwd = DepCwd } tgtDir := path.Join(cwd, DirPrefix) - tgtRel := mustRel(tgtDir, tgtAbs) - if _, errStat := os.Stat(tgt); errStat == nil { - err = ErrLine(depWrite(fdDep, tgtDir, tgtRel, nil)) + if _, errStat := os.Stat(tgt.a); errStat == nil { + err = ErrLine(depWrite(fdDep, tgtDir, tgt, nil)) } else { + tgtRel := tgt.RelTo(tgtDir) tracef(CDebug, "ifchange: %s <- %s (non-existing)", fdDep.Name(), tgtRel) fields := []recfile.Field{ {Name: "Type", Value: DepTypeIfchange}, @@ -149,7 +149,7 @@ func depsWrite(fdDep *os.File, tgts []string) error { } type DepInfoIfchange struct { - tgt string + tgt *Tgt inode *Inode hash []byte } @@ -158,7 +158,7 @@ type DepInfo struct { build string always bool stamp []byte - ifcreates []string + ifcreates []*Tgt ifchanges []DepInfoIfchange } @@ -172,8 +172,8 @@ func mustHexDecode(s string) []byte { var missingBuild = errors.New(".rec missing Build:") -func depRead(pth string) (*DepInfo, error) { - data, err := os.ReadFile(pth) +func depRead(tgt *Tgt) (*DepInfo, error) { + data, err := os.ReadFile(tgt.Dep()) if err != nil { return nil, err } @@ -204,7 +204,8 @@ func depRead(pth string) (*DepInfo, error) { if dep == "" { return nil, ErrBadRecFormat } - depInfo.ifcreates = append(depInfo.ifcreates, dep) + depInfo.ifcreates = append(depInfo.ifcreates, + NewTgt(path.Join(tgt.h, dep))) case DepTypeIfchange: dep := m["Target"] if dep == "" { @@ -217,7 +218,7 @@ func depRead(pth string) (*DepInfo, error) { } hsh := mustHexDecode(m["Hash"]) depInfo.ifchanges = append(depInfo.ifchanges, DepInfoIfchange{ - tgt: dep, inode: inode, hash: hsh, + tgt: NewTgt(path.Join(tgt.h, dep)), inode: inode, hash: hsh, }) case DepTypeStamp: hsh := m["Hash"] @@ -232,6 +233,29 @@ func depRead(pth string) (*DepInfo, error) { return &depInfo, nil } +func depReadOnlyIfchanges(pth string) (ifchanges []string, err error) { + data, err := os.ReadFile(pth) + if err != nil { + return + } + r := recfile.NewReader(bytes.NewReader(data)) + var m map[string]string + for { + m, err = r.NextMap() + if err != nil { + if errors.Is(err, io.EOF) { + err = nil + break + } + return + } + if m["Type"] == DepTypeIfchange { + ifchanges = append(ifchanges, m["Target"]) + } + } + return +} + func depReadBuild(pth string) (string, error) { fd, err := os.Open(pth) if err != nil { diff --git a/dot.go b/dot.go index b8c28b7..9199f7d 100644 --- a/dot.go +++ b/dot.go @@ -34,15 +34,13 @@ type DotNodes struct { to string } -func dotWalker(data map[DotNodes]bool, tgtOrig string) (map[DotNodes]bool, error) { - cwd, tgt := cwdAndTgt(tgtOrig) - depPath := path.Join(cwd, RedoDir, tgt+DepSuffix) - fdDep, err := os.Open(depPath) +func dotWalker(data map[DotNodes]bool, tgt *Tgt) (map[DotNodes]bool, error) { + fdDep, err := os.Open(tgt.Dep()) if err != nil { return nil, ErrLine(err) } defer fdDep.Close() - var dep string + var dep *Tgt r := recfile.NewReader(fdDep) for { m, err := r.NextMap() @@ -54,17 +52,17 @@ func dotWalker(data map[DotNodes]bool, tgtOrig string) (map[DotNodes]bool, error } switch m["Type"] { case DepTypeIfcreate: - data[DotNodes{tgtOrig, cwdMustRel(cwd, m["Target"])}] = true + data[DotNodes{tgt.String(), NewTgt(m["Target"]).String()}] = true case DepTypeIfchange: - dep = m["Target"] - if dep == tgt { + dep = NewTgt(path.Join(tgt.h, m["Target"])) + if dep.a == tgt.a { continue } - data[DotNodes{tgtOrig, cwdMustRel(cwd, dep)}] = false - if isSrc(cwd, dep) { + data[DotNodes{tgt.String(), dep.String()}] = false + if isSrc(dep) { continue } - data, err = dotWalker(data, cwdMustRel(cwd, dep)) + data, err = dotWalker(data, dep) if err != nil { return nil, err } @@ -73,7 +71,7 @@ func dotWalker(data map[DotNodes]bool, tgtOrig string) (map[DotNodes]bool, error return data, nil } -func dotPrint(tgts []string) error { +func dotPrint(tgts []*Tgt) error { data := map[DotNodes]bool{} var err error for _, tgt := range tgts { diff --git a/ifchange.go b/ifchange.go index 77d6d93..9f7f4b5 100644 --- a/ifchange.go +++ b/ifchange.go @@ -18,90 +18,80 @@ along with this program. If not, see . package main import ( - "path" "strings" ) func collectDeps( - cwd, tgtOrig string, + tgt *Tgt, level int, - deps map[string]map[string]struct{}, + deps map[string]map[string]*Tgt, includeSrc bool, seen map[string]struct{}, -) []string { - cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig)) - tgtFull := path.Join(cwd, tgt) - if _, ok := seen[tgtFull]; ok { +) []*Tgt { + if _, ok := seen[tgt.a]; ok { return nil } - depPath := path.Join(cwd, RedoDir, tgt+DepSuffix) - depInfo, err := depRead(depPath) + depInfo, err := depRead(tgt) if err != nil { return nil } - // DepInfoCache[depPath] = depInfo - seen[tgtFull] = struct{}{} - var alwayses []string + seen[tgt.a] = struct{}{} + var alwayses []*Tgt returnReady := false - tgtRel := cwdMustRel(cwd, tgt) if depInfo.always { if depInfo.build == BuildUUID { tracef( CDebug, "ood: %s%s always, but already build", - strings.Repeat(". ", level), tgtOrig, + strings.Repeat(". ", level), tgt, ) returnReady = true } else { - tracef(CDebug, "ood: %s%s always", strings.Repeat(". ", level), tgtOrig) - alwayses = append(alwayses, tgtRel) + tracef(CDebug, "ood: %s%s always", strings.Repeat(". ", level), tgt) + alwayses = append(alwayses, tgt) returnReady = true } } for _, dep := range depInfo.ifchanges { - if dep.tgt == tgt { + if dep.tgt.a == tgt.a { continue } - if !includeSrc && isSrc(cwd, dep.tgt) { + if !includeSrc && isSrc(dep.tgt) { continue } if !returnReady { - depRel := cwdMustRel(cwd, dep.tgt) - if m, ok := deps[depRel]; ok { - m[tgtRel] = struct{}{} + if m, ok := deps[dep.tgt.a]; ok { + m[tgt.a] = tgt } else { - m = map[string]struct{}{} - m[tgtRel] = struct{}{} - deps[depRel] = m + deps[dep.tgt.a] = map[string]*Tgt{tgt.a: tgt} } alwayses = append(alwayses, - collectDeps(cwd, dep.tgt, level+1, deps, includeSrc, seen)...) + collectDeps(dep.tgt, level+1, deps, includeSrc, seen)...) } } return alwayses } -func buildDependants(tgts []string) map[string]struct{} { +func buildDependants(tgts []*Tgt) map[string]*Tgt { defer Jobs.Wait() tracef(CDebug, "collecting deps") - seen := map[string]struct{}{} - deps := map[string]map[string]struct{}{} - collectDepsSeen := make(map[string]struct{}) - for _, tgtInitial := range tgts { - for _, tgt := range collectDeps(Cwd, tgtInitial, 0, deps, false, collectDepsSeen) { - if tgt != tgtInitial { - seen[tgt] = struct{}{} + seen := make(map[string]*Tgt) + deps := make(map[string]map[string]*Tgt) + { + collectDepsSeen := make(map[string]struct{}) + for _, tgtInitial := range tgts { + for _, tgt := range collectDeps(tgtInitial, 0, deps, false, collectDepsSeen) { + if tgt.a != tgtInitial.a { + seen[tgt.a] = tgt + } } } } if len(seen) == 0 { return seen } - collectDepsSeen = nil levelOrig := Level - defer func() { - Level = levelOrig - }() + defer func() { Level = levelOrig }() Level = 1 tracef(CDebug, "building %d alwayses: %v", len(seen), seen) errs := make(chan error, len(seen)) @@ -113,7 +103,7 @@ func buildDependants(tgts []string) map[string]struct{} { } close(okChecker) }() - for tgt := range seen { + for _, tgt := range seen { if err := runScript(tgt, errs, false, false); err != nil { tracef(CErr, "always run error: %s, skipping dependants", err) Jobs.Wait() @@ -129,17 +119,17 @@ func buildDependants(tgts []string) map[string]struct{} { return nil } - queueSrc := make([]string, 0, len(seen)) - for tgt := range seen { + queueSrc := make([]*Tgt, 0, len(seen)) + for _, tgt := range seen { queueSrc = append(queueSrc, tgt) } RebuildDeps: tracef(CDebug, "checking %d dependant targets: %v", len(queueSrc), queueSrc) - queue := map[string]struct{}{} + queue := make(map[string]*Tgt) for _, tgt := range queueSrc { - for dep := range deps[tgt] { - queue[dep] = struct{}{} + for _, dep := range deps[tgt.a] { + queue[dep.a] = dep } } @@ -147,15 +137,15 @@ RebuildDeps: errs = make(chan error, len(queue)) okChecker = make(chan struct{}) jobs := 0 - queueSrc = []string{} + queueSrc = make([]*Tgt, 0) go func() { for err := range errs { ok = isOkRun(err) && ok } close(okChecker) }() - for tgt := range queue { - ood, err := isOODWithTrace(Cwd, tgt, 0, seen) + for _, tgt := range queue { + ood, err := isOODWithTrace(tgt, 0, seen) if err != nil { tracef(CErr, "dependant error: %s, skipping dependants", err) return nil @@ -168,7 +158,7 @@ RebuildDeps: return nil } queueSrc = append(queueSrc, tgt) - seen[tgt] = struct{}{} + seen[tgt.a] = tgt jobs++ } Jobs.Wait() @@ -185,17 +175,18 @@ RebuildDeps: goto RebuildDeps } -func ifchange(tgts []string, forced, traced bool) (bool, error) { - // only unique elements - m := make(map[string]struct{}) - for _, t := range tgts { - m[t] = struct{}{} - } - tgts = tgts[:0] - for t := range m { - tgts = append(tgts, t) +func ifchange(tgts []*Tgt, forced, traced bool) (bool, error) { + { + // only unique elements + m := make(map[string]*Tgt) + for _, t := range tgts { + m[t.a] = t + } + tgts = tgts[:0] + for _, t := range m { + tgts = append(tgts, t) + } } - m = nil jsInit() if !IsTopRedo { @@ -220,13 +211,13 @@ func ifchange(tgts []string, forced, traced bool) (bool, error) { close(okChecker) }() for _, tgt := range tgts { - if _, ok := seen[tgt]; ok { + if _, ok := seen[tgt.a]; ok { tracef(CDebug, "%s was already build as a dependant", tgt) continue } ood = true if !forced { - ood, err = isOODWithTrace(Cwd, tgt, 0, seen) + ood, err = isOODWithTrace(tgt, 0, seen) if err != nil { Jobs.Wait() close(errs) @@ -236,7 +227,7 @@ func ifchange(tgts []string, forced, traced bool) (bool, error) { if !ood { continue } - if isSrc(Cwd, tgt) { + if isSrc(tgt) { tracef(CDebug, "%s is source, not redoing", tgt) continue } diff --git a/main.go b/main.go index 39c6d11..bba64d0 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,6 @@ import ( "os" "os/signal" "path" - "path/filepath" "runtime" "sort" "strconv" @@ -45,17 +44,17 @@ const ( CmdNameRedoAffects = "redo-affects" CmdNameRedoAlways = "redo-always" CmdNameRedoCleanup = "redo-cleanup" + 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 ( @@ -252,7 +251,7 @@ func main() { if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLK, &flock); err != nil { 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 == "" { @@ -279,7 +278,10 @@ func main() { fdDep = mustParseFd(v, EnvDepFd) } - tgts := flag.Args() + tgts := make([]*Tgt, 0, len(flag.Args())) + for _, arg := range flag.Args() { + tgts = append(tgts, NewTgt(arg)) + } BuildUUID = os.Getenv(EnvBuildUUID) tgtsWasEmpty := len(tgts) == 0 if BuildUUID == "" { @@ -294,7 +296,7 @@ func main() { raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:], ) if tgtsWasEmpty { - tgts = []string{"all"} + tgts = append(tgts, NewTgt("all")) } } @@ -302,12 +304,6 @@ func main() { 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() { @@ -333,7 +329,7 @@ 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 } @@ -348,15 +344,7 @@ CmdSwitch: log.Fatalln("no", EnvDepFd) } for _, tgt := range tgts { - tgtRel, err := filepath.Rel( - path.Join(Cwd, DirPrefix), - path.Join(Cwd, tgt), - ) - if err != nil { - err = ErrLine(err) - break - } - err = ifcreate(fdDep, tgtRel) + err = ifcreate(fdDep, tgt.RelTo(path.Join(Cwd, DirPrefix))) if err != nil { break } @@ -368,7 +356,7 @@ CmdSwitch: err = always(fdDep) case CmdNameRedoCleanup: for _, what := range tgts { - err = cleanupWalker(Cwd, what) + err = cleanupWalker(Cwd, what.t) if err != nil { break } @@ -384,8 +372,7 @@ CmdSwitch: if len(tgts) != 1 { 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.Fatal("single target expected") @@ -400,8 +387,8 @@ CmdSwitch: err = ErrLine(err) break } - cwd, tgt := cwdAndTgt(tgts[0]) - doFile, upLevels, err := findDo(fdTmp, cwd, tgt) + tgt := tgts[0] + doFile, upLevels, err := findDo(fdTmp, tgt.h, tgt.t) if err != nil { err = ErrLine(err) break @@ -421,13 +408,13 @@ CmdSwitch: err = ErrLine(err) break CmdSwitch } - fmt.Println(cwdMustRel(cwd, m["Target"])) + fmt.Println(cwdMustRel(tgt.h, m["Target"])) } if doFile == "" { ok = false } else { p := make([]string, 0, upLevels+2) - p = append(p, cwd) + p = append(p, tgt.h) for i := 0; i < upLevels; i++ { p = append(p, "..") } @@ -436,55 +423,66 @@ CmdSwitch: fmt.Println(rel) } case CmdNameRedoTargets: + raws := make([]string, 0, len(tgts)) + for _, tgt := range tgts { + raws = append(raws, tgt.String()) + } 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.Fatal("no targets specified") } - var tgtsKnown []string - tgtsKnown, err = targetsWalker([]string{Cwd}) - if err != nil { - err = ErrLine(err) - break - } - deps := map[string]map[string]struct{}{} - for _, tgt := range tgtsKnown { - collectDeps(Cwd, tgt, 0, deps, true, map[string]struct{}{}) - } - 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, make(map[string]struct{})) + } + seen := make(map[string]*Tgt) + for _, tgt := range tgts { + collectWholeDeps(deps[tgt.a], deps, seen) + } + res = make([]string, 0, len(seen)) + for _, dep := range seen { + res = append(res, dep.String()) + } } - 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.String()) + } 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 @@ -494,27 +492,34 @@ CmdSwitch: } } case CmdNameRedoSources: - if tgtsWasEmpty { - tgts, err = targetsWalker([]string{Cwd}) - if err != nil { - err = ErrLine(err) - break + srcs := make(map[string]*Tgt) + { + raws := make([]string, 0, len(tgts)) + for _, tgt := range tgts { + raws = append(raws, tgt.String()) + } + 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)) } - sort.Strings(tgts) - seen := make(map[string]struct{}) - seenDeps := make(map[string]struct{}) - srcs := make(map[string]struct{}) - err = ErrLine(sourcesWalker(tgts, seen, seenDeps, srcs)) - seen = nil - seenDeps = nil if err != nil { break } - seenDeps = nil res := make([]string, 0, len(srcs)) - for p := range srcs { - res = append(res, p) + for _, tgt := range srcs { + res = append(res, tgt.String()) } srcs = nil sort.Strings(res) @@ -526,13 +531,13 @@ CmdSwitch: case CmdNameRedoInode: 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) diff --git a/ood.go b/ood.go index 5dc543f..dffaa4e 100644 --- a/ood.go +++ b/ood.go @@ -27,7 +27,6 @@ import ( "io/fs" "log" "os" - "path" "strings" "golang.org/x/sys/unix" @@ -70,7 +69,7 @@ func FileExists(p string) bool { } type TgtError struct { - Tgt string + Tgt *Tgt Err error } @@ -80,183 +79,165 @@ func (e TgtError) Error() string { return fmt.Sprintf("%s: %s", e.Tgt, e.Err) } -func isSrc(cwd, tgt string) bool { - d, f := path.Split(path.Join(cwd, tgt)) - if !FileExists(path.Join(d, f)) { +func isSrc(tgt *Tgt) bool { + if !FileExists(tgt.a) { return false } - if FileExists(path.Join(d, f+".do")) { + if FileExists(tgt.a + ".do") { return false } - if FileExists(path.Join(d, RedoDir, f+DepSuffix)) { + if FileExists(tgt.Dep()) { return false } return true } -func isOODByBuildUUID(cwd, tgtOrig string) bool { - cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig)) - depPath := path.Join(cwd, RedoDir, tgt+DepSuffix) - build, err := depReadBuild(depPath) +func isOODByBuildUUID(tgt *Tgt) bool { + build, err := depReadBuild(tgt.Dep()) return err != nil || build != BuildUUID } -func isOOD(cwd, tgtOrig string, level int, seen map[string]struct{}) (bool, error) { +func isOOD(tgt *Tgt, level int, seen map[string]*Tgt) (bool, error) { indent := strings.Repeat(". ", level) - tracef(CDebug, "ood: %s%s checking", indent, tgtOrig) - cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig)) - ood, cached := OODCache[path.Join(cwd, tgt)] + tracef(CDebug, "ood: %s%s checking", indent, tgt) + ood, cached := OODCache[tgt.a] if cached { - tracef(CDebug, "ood: %s%s -> cached: %v", indent, tgtOrig, ood) + tracef(CDebug, "ood: %s%s -> cached: %v", indent, tgt, ood) return ood, nil } - depPath := path.Join(cwd, RedoDir, tgt+DepSuffix) - depInfo, err := depRead(depPath) + depInfo, err := depRead(tgt) if err != nil { if errors.Is(err, fs.ErrNotExist) { - if isSrc(cwd, tgt) { + if isSrc(tgt) { ood = false - tracef(CDebug, "ood: %s%s -> is source", indent, tgtOrig) + tracef(CDebug, "ood: %s%s -> is source", indent, tgt) } else { ood = true - tracef(CDebug, "ood: %s%s -> no dep: %s", indent, tgtOrig, depPath) + tracef(CDebug, "ood: %s%s -> no dep: %s", indent, tgt, tgt.Dep()) } - OODCache[path.Join(cwd, tgt)] = ood + OODCache[tgt.a] = ood return ood, nil } if err != nil { - return true, TgtError{tgtOrig, ErrLine(err)} + return true, TgtError{tgt, ErrLine(err)} } } if depInfo.build == BuildUUID { - tracef(CDebug, "ood: %s%s -> already built", indent, tgtOrig) - OODCache[path.Join(cwd, tgt)] = false + tracef(CDebug, "ood: %s%s -> already built", indent, tgt) + OODCache[tgt.a] = false return false, nil } - if !FileExists(path.Join(cwd, tgt)) { - tracef(CDebug, "ood: %s%s -> non-existent", indent, tgtOrig) - OODCache[path.Join(cwd, tgt)] = true + if !FileExists(tgt.a) { + tracef(CDebug, "ood: %s%s -> non-existent", indent, tgt) + OODCache[tgt.a] = true return true, nil } for _, dep := range depInfo.ifcreates { - if FileExists(path.Join(cwd, dep)) { - tracef(CDebug, "ood: %s%s -> %s created", indent, tgtOrig, dep) + if FileExists(dep.a) { + tracef(CDebug, "ood: %s%s -> %s created", indent, tgt, dep) ood = true goto Done } } for _, dep := range depInfo.ifchanges { - tracef(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep.tgt) - ood, cached = OODCache[path.Join(cwd, dep.tgt)] + tracef(CDebug, "ood: %s%s -> %s: checking", indent, tgt, dep.tgt) + ood, cached = OODCache[dep.tgt.a] if cached { - tracef(CDebug, "ood: %s%s -> %s: cached: %v", indent, tgtOrig, dep.tgt, ood) + tracef(CDebug, "ood: %s%s -> %s: cached: %v", indent, tgt, dep.tgt, ood) if ood { goto Done } continue } - inode, err := inodeFromFileByPath(path.Join(cwd, dep.tgt)) + inode, err := inodeFromFileByPath(dep.tgt.a) if err != nil { if errors.Is(err, fs.ErrNotExist) { - tracef(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep.tgt) + tracef(CDebug, "ood: %s%s -> %s: not exists", indent, tgt, dep.tgt) ood = true - OODCache[path.Join(cwd, dep.tgt)] = ood + OODCache[dep.tgt.a] = ood goto Done } - return ood, TgtError{tgtOrig, ErrLine(err)} + return ood, TgtError{tgt, ErrLine(err)} } if inode.Size != dep.inode.Size { - tracef(CDebug, "ood: %s%s -> %s: size differs", indent, tgtOrig, dep.tgt) + tracef(CDebug, "ood: %s%s -> %s: size differs", indent, tgt, dep.tgt) ood = true - OODCache[path.Join(cwd, dep.tgt)] = ood + OODCache[dep.tgt.a] = ood goto Done } if InodeTrust != InodeTrustNone && inode.Equals(dep.inode) { - tracef(CDebug, "ood: %s%s -> %s: same inode", indent, tgtOrig, dep.tgt) + tracef(CDebug, "ood: %s%s -> %s: same inode", indent, tgt, dep.tgt) } else { - tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgtOrig, dep.tgt) - fd, err := os.Open(path.Join(cwd, dep.tgt)) + tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgt, dep.tgt) + fd, err := os.Open(dep.tgt.a) if err != nil { - return ood, TgtError{tgtOrig, ErrLine(err)} + return ood, TgtError{tgt, ErrLine(err)} } hsh, err := fileHash(fd) fd.Close() if err != nil { - return ood, TgtError{tgtOrig, ErrLine(err)} + return ood, TgtError{tgt, ErrLine(err)} } if !bytes.Equal(dep.hash, hsh) { - tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep.tgt) + tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgt, dep.tgt) ood = true - OODCache[path.Join(cwd, dep.tgt)] = ood + OODCache[dep.tgt.a] = ood goto Done } - tracef(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep.tgt) + tracef(CDebug, "ood: %s%s -> %s: same hash", indent, tgt, dep.tgt) } - if dep.tgt == tgt { - tracef(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep.tgt) + if dep.tgt.a == tgt.a { + tracef(CDebug, "ood: %s%s -> %s: same target", indent, tgt, dep.tgt) continue } - if isSrc(cwd, dep.tgt) { - tracef(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep.tgt) - OODCache[path.Join(cwd, dep.tgt)] = false + if isSrc(dep.tgt) { + tracef(CDebug, "ood: %s%s -> %s: is source", indent, tgt, dep.tgt) + OODCache[dep.tgt.a] = false continue } - 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 + if _, ok := seen[dep.tgt.a]; ok { + tracef(CDebug, "ood: %s%s -> %s: was always built", indent, tgt, dep.tgt) + OODCache[dep.tgt.a] = false continue } - depOOD, err := isOODWithTrace(cwd, dep.tgt, level+1, seen) + depOOD, err := isOODWithTrace(dep.tgt, level+1, seen) if err != nil { - return ood, TgtError{tgtOrig, err} + return ood, TgtError{tgt, err} } if depOOD { - tracef(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep.tgt) + tracef(CDebug, "ood: %s%s -> %s: ood", indent, tgt, dep.tgt) ood = true goto Done } - tracef(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep.tgt) + tracef(CDebug, "ood: %s%s -> %s: !ood", indent, tgt, dep.tgt) } Done: - tracef(CDebug, "ood: %s%s: %v", indent, tgtOrig, ood) - OODCache[path.Join(cwd, tgt)] = ood + tracef(CDebug, "ood: %s%s: %v", indent, tgt, ood) + OODCache[tgt.a] = ood return ood, nil } -func isOODWithTrace( - cwd, tgtOrig string, - level int, - seen map[string]struct{}, -) (bool, error) { - p := mustAbs(path.Join(cwd, tgtOrig)) - _, ood := OODTgts[p] +func isOODWithTrace(tgt *Tgt, level int, seen map[string]*Tgt) (bool, error) { + _, ood := OODTgts[tgt.a] var err error if ood { - if !isOODByBuildUUID(cwd, tgtOrig) { - tracef( - CDebug, - "ood: %s%s -> already built", - strings.Repeat(". ", level), tgtOrig, - ) + if !isOODByBuildUUID(tgt) { + tracef(CDebug, "ood: %s%s -> already built", strings.Repeat(". ", level), tgt) return false, nil } - tracef( - CDebug, - "ood: %s%s true, external decision", - strings.Repeat(". ", level), tgtOrig, - ) + tracef(CDebug, "ood: %s%s true, external decision", strings.Repeat(". ", level), tgt) goto RecordOODTgt } - ood, err = isOOD(cwd, tgtOrig, level, seen) + ood, err = isOOD(tgt, level, seen) if !ood { return ood, err } @@ -271,7 +252,7 @@ RecordOODTgt: if _, err = FdOODTgts.Seek(0, io.SeekEnd); err != nil { log.Fatal(err) } - if _, err := FdOODTgts.WriteString(p + "\x00"); err != nil { + if _, err := FdOODTgts.WriteString(tgt.a + "\x00"); err != nil { log.Fatal(err) } flock.Type = unix.F_UNLCK diff --git a/path.go b/path.go index efb3e98..0c96196 100644 --- a/path.go +++ b/path.go @@ -29,3 +29,37 @@ func cwdAndTgt(tgt string) (string, string) { cwd, tgt := path.Split(tgt) return mustAbs(cwd), tgt } + +type Tgt struct { + // a/h/t resemble zsh'es :a, :h, :t modifiers + a string // absolute path + h string // head of the path, directory + t string // tail of the path, only name + rel string // relative to Cwd + dep string // path to dependency file +} + +func NewTgt(tgt string) *Tgt { + t := Tgt{a: mustAbs(tgt)} + t.h, t.t = path.Split(t.a) + if len(t.h) > 1 { + t.h = t.h[:len(t.h)-1] + } + t.rel = mustRel(Cwd, t.a) + return &t +} + +func (tgt *Tgt) String() string { + return tgt.rel +} + +func (tgt *Tgt) Dep() string { + if tgt.dep == "" { + tgt.dep = path.Join(tgt.h, RedoDir, tgt.t+DepSuffix) + } + return tgt.dep +} + +func (tgt *Tgt) RelTo(cwd string) string { + return mustRel(cwd, tgt.a) +} diff --git a/run.go b/run.go index 1d03e06..bea8366 100644 --- a/run.go +++ b/run.go @@ -102,7 +102,7 @@ func init() { } type RunError struct { - Tgt string + Tgt *Tgt DoFile string Started *time.Time Finished *time.Time @@ -112,7 +112,7 @@ type RunError struct { func (e *RunError) Name() string { var name string if e.DoFile == "" { - name = e.Tgt + name = e.Tgt.String() } else { name = fmt.Sprintf("%s (%s)", e.Tgt, e.DoFile) } @@ -133,17 +133,17 @@ func mkdirs(pth string) error { return os.MkdirAll(pth, os.FileMode(0777)) } -func isModified(depInfo *DepInfo, cwd, tgt string) ( +func isModified(depInfo *DepInfo, tgt *Tgt) ( modified bool, ourInode *Inode, hshPrev []byte, err error, ) { if depInfo == nil { return } for _, dep := range depInfo.ifchanges { - if dep.tgt != tgt { + if dep.tgt.a != tgt.a { continue } - ourInode, err = inodeFromFileByPath(path.Join(cwd, tgt)) + ourInode, err = inodeFromFileByPath(tgt.a) if err != nil { if os.IsNotExist(err) { err = nil @@ -169,14 +169,13 @@ func syncDir(dir string) error { return err } -func runScript(tgtOrig string, errs chan error, forced, traced bool) error { - cwd, tgt := cwdAndTgt(tgtOrig) - redoDir := path.Join(cwd, RedoDir) +func runScript(tgt *Tgt, errs chan error, forced, traced bool) error { + redoDir := path.Join(tgt.h, RedoDir) if err := mkdirs(redoDir); err != nil { - return TgtError{tgtOrig, ErrLine(err)} + return TgtError{tgt, ErrLine(err)} } - shCtx := fmt.Sprintf("sh: %s: cwd:%s", tgtOrig, cwd) + shCtx := fmt.Sprintf("sh: %s: cwd:%s", tgt, tgt.h) jsToken := jsAcquire(shCtx) jsNeedsRelease := true defer func() { @@ -187,12 +186,12 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { // Acquire lock fdLock, err := os.OpenFile( - path.Join(redoDir, tgt+LockSuffix), + path.Join(redoDir, tgt.t+LockSuffix), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.FileMode(0666), ) if err != nil { - return TgtError{tgtOrig, ErrLine(err)} + return TgtError{tgt, ErrLine(err)} } flock := unix.Flock_t{ Type: unix.F_WRLCK, @@ -212,13 +211,13 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { 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, ErrLine(err)} + return TgtError{tgt, ErrLine(err)} } Jobs.Add(1) if err = unix.FcntlFlock(fdLock.Fd(), unix.F_GETLK, &flock); err != nil { log.Fatalln(err, fdLock.Name()) } - tracef(CDebug, "waiting: %s (pid=%d)", tgtOrig, flock.Pid) + tracef(CDebug, "waiting: %s (pid=%d)", tgt, flock.Pid) if FdStatus != nil { if _, err = FdStatus.Write([]byte{StatusWait}); err != nil { log.Fatal(err) @@ -236,13 +235,13 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { log.Fatalln(err, fdLock.Name()) } lockRelease() - tracef(CDebug, "waiting done: %s", tgtOrig) + tracef(CDebug, "waiting done: %s", tgt) if FdStatus != nil { if _, err = FdStatus.Write([]byte{StatusWaited}); err != nil { log.Fatal(err) } } - build, err := depReadBuild(path.Join(redoDir, tgt+DepSuffix)) + build, err := depReadBuild(tgt.Dep()) if err == nil { if build != BuildUUID { err = errors.New("was not built: build differs") @@ -253,7 +252,7 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { } } if err != nil { - err = TgtError{tgtOrig, err} + err = TgtError{tgt, err} } errs <- err }() @@ -262,13 +261,13 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { // 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)) + depInfo, err := depRead(tgt) if err != nil { if errors.Is(err, fs.ErrNotExist) { err = nil } else { lockRelease() - return TgtError{tgtOrig, err} + return TgtError{tgt, ErrLine(err)} } } @@ -280,17 +279,17 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { } // Check if target is not modified externally - modified, inodePrev, hshPrev, err := isModified(depInfo, cwd, tgt) + modified, inodePrev, hshPrev, err := isModified(depInfo, tgt) if err != nil { lockRelease() - return TgtError{tgtOrig, err} + return TgtError{tgt, ErrLine(err)} } if modified { lockRelease() if StopIfMod { - return fmt.Errorf("%s externally modified", tgtOrig) + return fmt.Errorf("%s externally modified", tgt) } - tracef(CWarn, "%s externally modified: not redoing", tgtOrig) + tracef(CWarn, "%s externally modified: not redoing", tgt) go func() { errs <- nil }() @@ -299,10 +298,10 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { depInfo = nil // Start preparing .rec - fdDep, err := tempfile(redoDir, tgt+DepSuffix) + fdDep, err := tempfile(redoDir, tgt.t+DepSuffix) if err != nil { lockRelease() - return TgtError{tgtOrig, ErrLine(err)} + return TgtError{tgt, ErrLine(err)} } fdDepOpened := true fdDepPath := fdDep.Name() @@ -317,42 +316,46 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { recfile.Field{Name: "Build", Value: BuildUUID}, ); err != nil { cleanup() - return TgtError{tgtOrig, ErrLine(err)} - } - - // Find .do - doFile, upLevels, err := findDo(fdDep, cwd, tgt) - if err != nil { - cleanup() - return TgtError{tgtOrig, ErrLine(err)} - } - if doFile == "" { - cleanup() - return TgtError{tgtOrig, errors.New("no .do found")} + return TgtError{tgt, ErrLine(err)} } + var cwd string + var dirPrefix string + var doFile *Tgt + basename := tgt.t + runErr := RunError{Tgt: tgt} // Determine basename and DIRPREFIX - doFileRelPath := doFile - ents := strings.Split(cwd, "/") - ents = ents[len(ents)-upLevels:] - dirPrefix := path.Join(ents...) - cwdOrig := cwd - for i := 0; i < upLevels; i++ { - cwd = path.Join(cwd, "..") - doFileRelPath = path.Join("..", doFileRelPath) - } - cwd = path.Clean(cwd) - doFilePath := path.Join(cwd, doFile) - basename := tgt - runErr := RunError{Tgt: tgtOrig} - if strings.HasPrefix(doFile, "default.") { - basename = tgt[:len(tgt)-(len(doFile)-len("default.")-len(".do"))-1] - runErr.DoFile = doFileRelPath + { + doFileRelPath, upLevels, err := findDo(fdDep, tgt.h, tgt.t) + if err != nil { + cleanup() + return TgtError{tgt, ErrLine(err)} + } + if doFileRelPath == "" { + cleanup() + return TgtError{tgt, errors.New("no .do found")} + } + // ents := strings.Split(strings.TrimSuffix(tgt.h, "/"), "/") + ents := strings.Split(tgt.h, "/") + ents = ents[len(ents)-upLevels:] + dirPrefix = path.Join(ents...) + ups := make([]string, 0, upLevels+2) + ups = append(ups, tgt.h) + for i := 0; i < upLevels; i++ { + ups = append(ups, "..") + } + ups = append(ups, doFileRelPath) + cwd = path.Clean(path.Join(ups[:len(ups)-1]...)) + doFile = NewTgt(path.Join(ups...)) + if strings.HasPrefix(doFile.t, "default.") { + basename = basename[:len(basename)-(len(doFile.t)-len("default.")-len(".do"))-1] + runErr.DoFile = doFile.String() + } } - if err = depWrite(fdDep, cwdOrig, doFileRelPath, nil); err != nil { + if err = depWrite(fdDep, tgt.h, doFile, nil); err != nil { cleanup() - return TgtError{tgtOrig, ErrLine(err)} + return TgtError{tgt, ErrLine(err)} } fdDep.Close() fdDepOpened = false @@ -361,8 +364,8 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { // Prepare command line var cmdName string var args []string - if err = unix.Access(doFilePath, unix.X_OK); err == nil { - cmdName = doFilePath + if err = unix.Access(doFile.String(), unix.X_OK); err == nil { + cmdName = doFile.t args = make([]string, 0, 3) } else { cmdName = "/bin/sh" @@ -371,14 +374,14 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { } else { args = append(args, "-e") } - args = append(args, doFile) + args = append(args, doFile.t) } // Temporary file for stdout - fdStdout, err := tempfile(cwdOrig, tgt) + fdStdout, err := tempfile(tgt.h, tgt.t) if err != nil { cleanup() - return TgtError{tgtOrig, ErrLine(err)} + return TgtError{tgt, ErrLine(err)} } stdoutPath := fdStdout.Name() fdStdout.Close() @@ -386,13 +389,13 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { tmpPathRel := mustRel(cwd, tmpPath) args = append( args, - path.Join(dirPrefix, tgt), + path.Join(dirPrefix, tgt.t), path.Join(dirPrefix, basename), tmpPathRel, ) shCtx = fmt.Sprintf( "sh: %s: %s %s cwd:%s dirprefix:%s", - tgtOrig, cmdName, args, cwd, dirPrefix, + tgt, cmdName, args, cwd, dirPrefix, ) cmd := exec.Command(cmdName, args...) @@ -431,13 +434,13 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { var fdStderr *os.File if StderrKeep { fdStderr, err = os.OpenFile( - path.Join(redoDir, tgt+LogSuffix), + path.Join(redoDir, tgt.t+LogSuffix), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(0666), ) if err != nil { cleanup() - return TgtError{tgtOrig, ErrLine(err)} + return TgtError{tgt, ErrLine(err)} } } tracef(CDebug, "%s", shCtx) @@ -505,7 +508,7 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { fdStdout.Close() if fdStderr != nil { fdStderr.Close() - logRecPath := path.Join(redoDir, tgt+LogRecSuffix) + logRecPath := path.Join(redoDir, tgt.t+LogRecSuffix) if fdStderr, err = os.OpenFile( logRecPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, @@ -546,18 +549,21 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { Value: strconv.Itoa(exitErr.ProcessState.ExitCode()), }) } - w := bufio.NewWriter(fdStderr) - 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.tgt, - }) + w := bufio.NewWriter(fdStderr) + { + var ifchanges []string + ifchanges, err = depReadOnlyIfchanges(tgt.Dep()) + if err != nil { + err = ErrLine(err) + goto Err + } + for _, dep := range ifchanges { + fields = append(fields, recfile.Field{ + Name: "Ifchange", + Value: dep, + }) + } } _, err = recfile.NewWriter(w).WriteFields(fields...) if err != nil { @@ -603,7 +609,7 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { RunningProcs[cmd.Process.Pid] = cmd.Process RunningProcsM.Unlock() pid := fmt.Sprintf("[%d]", cmd.Process.Pid) - tracef(CDebug, "%s runs %s", tgtOrig, pid) + tracef(CDebug, "%s runs %s", tgt, pid) stderrTerm := make(chan struct{}) go func() { @@ -651,7 +657,7 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { } // Was $1 touched? - if inode, err := inodeFromFileByPath(path.Join(cwdOrig, tgt)); err == nil { + if inode, err := inodeFromFileByPath(tgt.a); err == nil { if inodePrev == nil { runErr.Err = Err1WasTouched errs <- runErr @@ -706,8 +712,8 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { // Do we need to ifcreate it, or ifchange with renaming? if fd == nil { - os.Remove(path.Join(cwdOrig, tgt)) - err = ifcreate(fdDep, tgt) + os.Remove(tgt.a) + err = ifcreate(fdDep, tgt.t) if err != nil { err = ErrLine(err) goto Finish @@ -726,22 +732,22 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { goto Finish } if bytes.Equal(hsh, hshPrev) { - tracef(CDebug, "%s has same hash, not renaming", tgtOrig) + tracef(CDebug, "%s has same hash, not renaming", tgt) err = ErrLine(os.Remove(fd.Name())) if err != nil { goto Finish } - err = ErrLine(os.Chtimes(path.Join(cwdOrig, tgt), finished, finished)) + err = ErrLine(os.Chtimes(tgt.a, finished, finished)) if err != nil { goto Finish } if !NoSync { - err = ErrLine(syncDir(cwdOrig)) + err = ErrLine(syncDir(tgt.h)) if err != nil { goto Finish } } - err = ErrLine(depWrite(fdDep, cwdOrig, tgt, hsh)) + err = ErrLine(depWrite(fdDep, tgt.h, tgt, hsh)) if err != nil { goto Finish } @@ -754,17 +760,17 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { goto Finish } } - err = ErrLine(os.Rename(fd.Name(), path.Join(cwdOrig, tgt))) + err = ErrLine(os.Rename(fd.Name(), tgt.a)) if err != nil { goto Finish } if !NoSync { - err = ErrLine(syncDir(cwdOrig)) + err = ErrLine(syncDir(tgt.h)) if err != nil { goto Finish } } - err = ErrLine(depWrite(fdDep, cwdOrig, tgt, hsh)) + err = ErrLine(depWrite(fdDep, tgt.h, tgt, hsh)) if err != nil { goto Finish } @@ -778,8 +784,7 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { goto Finish } } - fdDepPath = path.Join(redoDir, tgt+DepSuffix) - err = ErrLine(os.Rename(fdDep.Name(), fdDepPath)) + err = ErrLine(os.Rename(fdDep.Name(), tgt.Dep())) if err != nil { goto Finish } @@ -793,13 +798,13 @@ func runScript(tgtOrig string, errs chan error, forced, traced bool) error { fdDepOpened = false // Post-commit .rec sanitizing - if depInfo, err := depRead(fdDepPath); err == nil { + if depInfo, err := depRead(tgt); err == nil { ifchangeSeen := make(map[string]struct{}, len(depInfo.ifchanges)) for _, dep := range depInfo.ifchanges { - ifchangeSeen[dep.tgt] = struct{}{} + ifchangeSeen[dep.tgt.a] = struct{}{} } for _, dep := range depInfo.ifcreates { - if _, exists := ifchangeSeen[dep]; exists { + if _, exists := ifchangeSeen[dep.a]; exists { tracef(CWarn, "simultaneous ifcreate and ifchange records: %s", tgt) } } diff --git a/sources.go b/sources.go index 95b8e4e..acf842b 100644 --- a/sources.go +++ b/sources.go @@ -20,24 +20,20 @@ package main import ( "errors" "io/fs" - "path" ) func sourcesWalker( - tgts []string, + tgts []*Tgt, seen map[string]struct{}, seenDeps map[string]struct{}, - srcs map[string]struct{}, + srcs map[string]*Tgt, ) error { for _, tgt := range tgts { - tgtAbsPath := mustAbs(path.Join(Cwd, tgt)) - cwd, f := path.Split(path.Join(Cwd, tgt)) - depPath := path.Join(cwd, RedoDir, f+DepSuffix) - if _, ok := seenDeps[depPath]; ok { + if _, ok := seenDeps[tgt.Dep()]; ok { continue } - seenDeps[depPath] = struct{}{} - depInfo, err := depRead(depPath) + seenDeps[tgt.Dep()] = struct{}{} + depInfo, err := depRead(tgt) if err != nil { if errors.Is(err, fs.ErrNotExist) { continue @@ -45,16 +41,15 @@ func sourcesWalker( return ErrLine(err) } for _, dep := range depInfo.ifchanges { - depTgtAbsPath := mustAbs(path.Join(cwd, dep.tgt)) - if _, ok := seen[depTgtAbsPath]; ok { + if _, ok := seen[dep.tgt.a]; ok { continue } - seen[depTgtAbsPath] = struct{}{} - if isSrc(cwd, dep.tgt) { - srcs[cwdMustRel(depTgtAbsPath)] = struct{}{} - } else if depTgtAbsPath != tgtAbsPath { + seen[dep.tgt.a] = struct{}{} + if isSrc(dep.tgt) { + srcs[dep.tgt.a] = dep.tgt + } else if dep.tgt.a != tgt.a { if err := sourcesWalker( - []string{cwdMustRel(depTgtAbsPath)}, + []*Tgt{dep.tgt}, seen, seenDeps, srcs, ); err != nil { return err diff --git a/targets.go b/targets.go index 6459a2d..662612e 100644 --- a/targets.go +++ b/targets.go @@ -25,7 +25,6 @@ import ( ) func targetsCollect(root string, tgts map[string]struct{}) error { - root = mustAbs(root) dir, err := os.Open(root) if err != nil { return ErrLine(err) @@ -72,9 +71,9 @@ func targetsCollect(root string, tgts map[string]struct{}) error { } func targetsWalker(tgts []string) ([]string, error) { - tgtsMap := map[string]struct{}{} + tgtsMap := make(map[string]struct{}) for _, tgt := range tgts { - if err := targetsCollect(tgt, tgtsMap); err != nil { + if err := targetsCollect(mustAbs(tgt), tgtsMap); err != nil { return nil, err } } @@ -86,12 +85,15 @@ func targetsWalker(tgts []string) ([]string, error) { } func collectWholeDeps( - tgts map[string]struct{}, - deps map[string]map[string]struct{}, - seen map[string]struct{}, + tgts map[string]*Tgt, + deps map[string]map[string]*Tgt, + seen map[string]*Tgt, ) { - for tgt := range tgts { - seen[tgt] = struct{}{} - collectWholeDeps(deps[tgt], deps, seen) + for _, tgt := range tgts { + if _, exists := seen[tgt.a]; exists { + continue + } + seen[tgt.a] = tgt + collectWholeDeps(deps[tgt.a], deps, seen) } }