/* goredo -- djb's redo implementation on pure Go Copyright (C) 2020-2021 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 ( "os" "path" "strings" ) func collectDeps( cwd, tgtOrig string, level int, deps map[string]map[string]struct{}, includeSrc bool, ) []string { cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig)) depPath := path.Join(cwd, RedoDir, tgt+DepSuffix) fdDep, err := os.Open(depPath) if err != nil { return nil } depInfo, err := depRead(fdDep) fdDep.Close() if err != nil { return nil } var alwayses []string 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, ) returnReady = true } else { tracef(CDebug, "ood: %s%s always", strings.Repeat(". ", level), tgtOrig) alwayses = append(alwayses, tgtRel) returnReady = true } } for _, m := range depInfo.ifchanges { dep := m["Target"] if dep == "" { return alwayses } if dep == tgt { continue } if !includeSrc && isSrc(cwd, dep) { continue } if !returnReady { depRel := cwdMustRel(cwd, dep) if m, ok := deps[depRel]; ok { m[tgtRel] = struct{}{} } else { m = map[string]struct{}{} m[tgtRel] = struct{}{} deps[depRel] = m } alwayses = append( alwayses, collectDeps(cwd, dep, level+1, deps, includeSrc)..., ) } } return alwayses } func buildDependants(tgts []string) map[string]struct{} { defer Jobs.Wait() tracef(CDebug, "collecting deps") seen := map[string]struct{}{} deps := map[string]map[string]struct{}{} for _, tgtInitial := range tgts { for _, tgt := range collectDeps(Cwd, tgtInitial, 0, deps, false) { if tgt != tgtInitial { seen[tgt] = struct{}{} } } } if len(seen) == 0 { return nil } levelOrig := Level defer func() { Level = levelOrig }() Level = 1 tracef(CDebug, "building %d alwayses: %v", len(seen), seen) errs := make(chan error, len(seen)) for tgt := range seen { if err := runScript(tgt, errs, false); err != nil { tracef(CErr, "always run error: %s, skipping dependants", err) return nil } } ok := true for i := 0; i < len(seen); i++ { ok = isOkRun(<-errs) && ok } Jobs.Wait() close(errs) if !ok { tracef(CDebug, "alwayses failed, skipping dependants") return nil } queueSrc := make([]string, 0, len(seen)) for tgt := range seen { queueSrc = append(queueSrc, tgt) } if len(queueSrc) == 0 { return seen } RebuildDeps: tracef(CDebug, "checking %d dependant targets: %v", len(queueSrc), queueSrc) queue := []string{} for _, tgt := range queueSrc { for dep := range deps[tgt] { queue = append(queue, dep) } } tracef(CDebug, "building %d dependant targets: %v", len(queue), queue) errs = make(chan error, len(queue)) jobs := 0 queueSrc = []string{} for _, tgt := range queue { ood, err := isOODWithTrace(Cwd, 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); err != nil { tracef(CErr, "dependant error: %s, skipping dependants", err) return nil } queueSrc = append(queueSrc, tgt) seen[tgt] = struct{}{} jobs++ } for i := 0; i < jobs; i++ { ok = isOkRun(<-errs) && ok } if !ok { tracef(CDebug, "dependants failed, skipping them") return nil } Jobs.Wait() close(errs) if jobs == 0 { return seen } Level++ goto RebuildDeps } func ifchange(tgts []string, forced, traced bool) (bool, error) { jsInit() if !IsTopRedo { defer jsAcquire("ifchange exiting") } defer Jobs.Wait() seen := buildDependants(tgts) oodTgtsClear() tracef(CDebug, "building %d targets: %v", len(tgts), tgts) jobs := 0 errs := make(chan error, len(tgts)) var ood bool var err error for _, tgt := range tgts { if _, ok := seen[tgt]; ok { tracef(CDebug, "%s was already build as a dependant", tgt) continue } ood = true if !forced { ood, err = isOODWithTrace(Cwd, tgt, 0, seen) if err != nil { return false, err } } if !ood { continue } if isSrc(Cwd, tgt) { tracef(CDebug, "%s is source, not redoing", tgt) continue } if err = runScript(tgt, errs, traced); err != nil { return false, err } jobs++ } ok := true for ; jobs > 0; jobs-- { ok = isOkRun(<-errs) && ok } return ok, nil }