X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=ifchange.go;h=5c294013f7bf270e7dc89a5c50db0586e3213492;hb=HEAD;hp=3cd93fb6c89d770bed1bfd330c0a4201ed81b944;hpb=6ef14f9de82e4a61532b84b149bde0c953d58a04;p=goredo.git diff --git a/ifchange.go b/ifchange.go index 3cd93fb..5c29401 100644 --- a/ifchange.go +++ b/ifchange.go @@ -1,83 +1,238 @@ -/* -goredo -- redo implementation on pure Go -Copyright (C) 2020 Sergey Matveev +// 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 . -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. +package main -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. +import ( + "strings" +) -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ +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 +} -package main +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 + } -import "sync" + 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 + } -var ( - Force bool = false - Jobs sync.WaitGroup -) + queueSrc := make([]*Tgt, 0, len(seen)) + for _, tgt := range seen { + queueSrc = append(queueSrc, tgt) + } -func isOkRun(err error) bool { - if err == nil { - return true +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 + } } - if err, ok := err.(RunErr); ok && err.Err == nil { - trace(CRedo, "%s", err.Name()) - return true + + 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++ } - trace(CErr, "%s", err) - return false + 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 []string) (bool, error) { +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() - defer jsAcquire("ifchange exiting") - defer Jobs.Wait() - errs := make(chan error, len(tgts)) - jobs := 0 - ok := true + 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, nil) + 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 }