/*
-goredo -- redo implementation on pure Go
-Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
+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
package main
-func isOkRun(err error) bool {
- if err == nil {
- return true
+import (
+ "strings"
+)
+
+func collectDeps(
+ tgt *Tgt,
+ level int,
+ deps map[string]map[string]*Tgt,
+ includeSrc bool,
+) []*Tgt {
+ if _, ok := DepCache[tgt.rel]; ok {
+ return nil
+ }
+ dep, err := depRead(tgt)
+ if err != nil {
+ return nil
}
- if err, ok := err.(RunErr); ok && err.Err == nil {
- trace(CRedo, "%s", err.Name())
- return true
+ DepCache[tgt.rel] = dep
+ var alwayses []*Tgt
+ returnReady := false
+ if dep.always {
+ if dep.build == BuildUUID {
+ tracef(CDebug, "ood: %s%s always, but already build",
+ strings.Repeat(". ", level), tgt)
+ returnReady = true
+ } else {
+ tracef(CDebug, "ood: %s%s always", strings.Repeat(". ", level), tgt)
+ alwayses = append(alwayses, tgt)
+ returnReady = true
+ }
}
- trace(CErr, "%s", err)
- return false
+ for _, ifchange := range dep.ifchanges {
+ if ifchange.tgt.rel == tgt.rel {
+ continue
+ }
+ if !includeSrc && isSrc(ifchange.tgt) {
+ continue
+ }
+ if !returnReady {
+ if m, ok := deps[ifchange.tgt.rel]; ok {
+ m[tgt.rel] = tgt
+ } else {
+ deps[ifchange.tgt.rel] = map[string]*Tgt{tgt.rel: tgt}
+ }
+ alwayses = append(alwayses,
+ collectDeps(ifchange.tgt, level+1, deps, includeSrc)...)
+ }
+ }
+ return alwayses
}
-func ifchange(tgts []string) (bool, error) {
- jsInit()
- defer jsAcquire("ifchange exiting")
+func buildDependants(tgts []*Tgt) map[string]*Tgt {
defer Jobs.Wait()
- errs := make(chan error, len(tgts))
- jobs := 0
+ tracef(CDebug, "collecting deps")
+ seen := make(map[string]*Tgt)
+ deps := make(map[string]map[string]*Tgt)
+ for _, tgtInitial := range tgts {
+ for _, tgt := range collectDeps(tgtInitial, 0, deps, false) {
+ if tgt.rel != tgtInitial.rel {
+ seen[tgt.rel] = tgt
+ }
+ }
+ }
+ TgtCache = make(map[string]*Tgt)
+ IfchangeCache = nil
+ if len(seen) == 0 {
+ return seen
+ }
+
+ levelOrig := Level
+ defer func() { Level = levelOrig }()
+ Level = 1
+ tracef(CDebug, "building %d alwayses: %v", len(seen), seen)
+ errs := make(chan error, len(seen))
ok := true
+ okChecker := make(chan struct{})
+ go func() {
+ for err := range errs {
+ ok = isOkRun(err) && ok
+ }
+ close(okChecker)
+ }()
+ for _, tgt := range seen {
+ if err := runScript(tgt, errs, false, false); err != nil {
+ tracef(CErr, "always run error: %s, skipping dependants", err)
+ Jobs.Wait()
+ close(errs)
+ return nil
+ }
+ }
+ Jobs.Wait()
+ close(errs)
+ <-okChecker
+ if !ok {
+ tracef(CDebug, "alwayses failed, skipping dependants")
+ return nil
+ }
+
+ 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 := make(map[string]*Tgt)
+ for _, tgt := range queueSrc {
+ for _, dep := range deps[tgt.rel] {
+ queue[dep.rel] = dep
+ }
+ }
+
+ tracef(CDebug, "building %d dependant targets: %v", len(queue), queue)
+ errs = make(chan error, len(queue))
+ okChecker = make(chan struct{})
+ jobs := 0
+ queueSrc = make([]*Tgt, 0)
+ go func() {
+ for err := range errs {
+ ok = isOkRun(err) && ok
+ }
+ close(okChecker)
+ }()
+ for _, tgt := range queue {
+ ood, err := isOODWithTrace(tgt, 0, seen)
+ if err != nil {
+ tracef(CErr, "dependant error: %s, skipping dependants", err)
+ return nil
+ }
+ if !ood {
+ continue
+ }
+ if err := runScript(tgt, errs, false, false); err != nil {
+ tracef(CErr, "dependant error: %s, skipping dependants", err)
+ return nil
+ }
+ queueSrc = append(queueSrc, tgt)
+ seen[tgt.rel] = tgt
+ jobs++
+ }
+ Jobs.Wait()
+ close(errs)
+ <-okChecker
+ if !ok {
+ tracef(CDebug, "dependants failed, skipping them")
+ return nil
+ }
+ if jobs == 0 {
+ return seen
+ }
+ Level++
+ goto RebuildDeps
+}
+
+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)
+ }
+ }
+
+ jsInit()
+ if !IsTopRedo {
+ defer jsAcquire("ifchange exiting")
+ }
+ seen := buildDependants(tgts)
+ if seen == nil {
+ Jobs.Wait()
+ return false, nil
+ }
+ oodTgtsClear()
+ tracef(CDebug, "building %d targets: %v", len(tgts), tgts)
+ var ood bool
var err error
+ ok := true
+ okChecker := make(chan struct{})
+ errs := make(chan error, len(tgts))
+ go func() {
+ for err := range errs {
+ ok = isOkRun(err) && ok
+ }
+ close(okChecker)
+ }()
for _, tgt := range tgts {
- var ood bool
- if Force {
- ood = true
- } else {
- ood, err = isOOD(Cwd, tgt, 0)
+ if _, ok := seen[tgt.rel]; ok {
+ tracef(CDebug, "%s was already build as a dependant", tgt)
+ continue
+ }
+ ood = true
+ if !forced {
+ ood, err = isOODWithTrace(tgt, 0, seen)
if err != nil {
- return false, err
+ Jobs.Wait()
+ close(errs)
+ return false, ErrLine(err)
}
}
if !ood {
continue
}
- if isSrc(Cwd, tgt) {
- trace(CDebug, "%s is source, not redoing", tgt)
+ if isSrc(tgt) {
+ tracef(CDebug, "%s is source, not redoing", tgt)
continue
}
- if err = runScript(tgt, errs); err != nil {
- return false, err
- }
- if Force {
- // Sequentially run jobs
- err = <-errs
+ if err = runScript(tgt, errs, forced, traced); err != nil {
Jobs.Wait()
- if isOkRun(err) {
- continue
- }
- return false, nil
+ close(errs)
+ return false, ErrLine(err)
}
- jobs++
- }
- for i := 0; i < jobs; i++ {
- err = <-errs
- ok = ok && isOkRun(err)
}
+ Jobs.Wait()
+ close(errs)
+ <-okChecker
return ok, nil
}