// goredo -- djb's redo implementation on pure Go // Copyright (C) 2020-2024 Sergey Matveev // // 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 // the Free Software Foundation, version 3 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package main 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 } 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 } } 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 buildDependants(tgts []*Tgt) map[string]*Tgt { defer Jobs.Wait() 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 { 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 { Jobs.Wait() close(errs) return false, ErrLine(err) } } if !ood { continue } if isSrc(tgt) { tracef(CDebug, "%s is source, not redoing", tgt) continue } if err = runScript(tgt, errs, forced, traced); err != nil { Jobs.Wait() close(errs) return false, ErrLine(err) } } Jobs.Wait() close(errs) <-okChecker return ok, nil }