X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=ifchange.go;h=f89da48fc7d1dbe9b733c1e679fd8a4044760aa7;hb=fa0728d6bc790672982ba006961077371d2b552d;hp=972da4dcd80f9c5f9735c4d696624a102ab8e792;hpb=bc7701e7a4f95cee680e0736ec3e68a8b0b5c09f;p=goredo.git diff --git a/ifchange.go b/ifchange.go index 972da4d..f89da48 100644 --- a/ifchange.go +++ b/ifchange.go @@ -1,6 +1,6 @@ /* -goredo -- redo implementation on pure Go -Copyright (C) 2020 Sergey Matveev +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 @@ -17,32 +17,178 @@ along with this program. If not, see . package main -func isOkRun(err error) bool { - if err == nil { - return true +import ( + "os" + "path" + "strings" +) + +func collectDeps( + cwd, tgtOrig string, + level int, + deps map[string]map[string]struct{}, +) []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 } - if err, ok := err.(RunErr); ok && err.Err == nil { - trace(CRedo, "%s", err.Name()) - return true + var alwayses []string + returnReady := false + tgtRel := cwdMustRel(cwd, tgt) + if depInfo.always { + if depInfo.build == BuildUUID { + trace( + CDebug, "ood: %s%s always, but already build", + strings.Repeat(". ", level), tgtOrig, + ) + returnReady = true + } else { + trace(CDebug, "ood: %s%s always", strings.Repeat(". ", level), tgtOrig) + alwayses = append(alwayses, tgtRel) + returnReady = true + } } - trace(CErr, "%s", err) - return false + for _, m := range depInfo.ifchanges { + dep := m["Target"] + if dep == "" { + return alwayses + } + if dep == tgt || isSrc(cwd, dep) { + continue + } + if !returnReady { + depRel := cwdMustRel(cwd, dep) + if m, ok := deps[depRel]; ok { + m[tgtRel] = struct{}{} + } else { + m = make(map[string]struct{}, 0) + m[tgtRel] = struct{}{} + deps[depRel] = m + } + alwayses = append(alwayses, collectDeps(cwd, dep, level+1, deps)...) + } + } + return alwayses } -func ifchange(tgts []string) (bool, error) { +func buildDependants(tgts []string) map[string]struct{} { + defer Jobs.Wait() + trace(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) { + if tgt != tgtInitial { + seen[tgt] = struct{}{} + } + } + } + if len(seen) == 0 { + return nil + } + + levelOrig := Level + defer func() { + Level = levelOrig + }() + Level = 1 + trace(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 { + trace(CErr, "always run error: %s, skipping dependants", err) + return nil + } + } + ok := true + for i := 0; i < len(seen); i++ { + ok = ok && isOkRun(<-errs) + } + Jobs.Wait() + close(errs) + if !ok { + trace(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: + trace(CDebug, "checking %d dependant targets: %v", len(queueSrc), queueSrc) + queue := []string{} + for _, tgt := range queueSrc { + for dep := range deps[tgt] { + queue = append(queue, dep) + } + } + trace(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 := isOOD(Cwd, tgt, 0, seen) + if err != nil { + trace(CErr, "dependant error: %s, skipping dependants", err) + return nil + } + if !ood { + continue + } + if err := runScript(tgt, errs, false); err != nil { + trace(CErr, "dependant error: %s, skipping dependants", err) + return nil + } + queueSrc = append(queueSrc, tgt) + seen[tgt] = struct{}{} + jobs++ + } + for i := 0; i < jobs; i++ { + ok = ok && isOkRun(<-errs) + } + if !ok { + trace(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() defer jsAcquire("ifchange exiting") defer Jobs.Wait() - errs := make(chan error, len(tgts)) + seen := buildDependants(tgts) + trace(CDebug, "building %d targets: %v", len(tgts), tgts) jobs := 0 - ok := true + errs := make(chan error, len(tgts)) + var ood bool var err error for _, tgt := range tgts { - var ood bool - if Force { - ood = true - } else { - ood, err = isOOD(Cwd, tgt, 0) + if _, ok := seen[tgt]; ok { + trace(CDebug, "%s was already build as a dependant", tgt) + continue + } + ood = true + if !forced { + ood, err = isOOD(Cwd, tgt, 0, seen) if err != nil { return false, err } @@ -54,23 +200,14 @@ func ifchange(tgts []string) (bool, error) { trace(CDebug, "%s is source, not redoing", tgt) continue } - if err = runScript(tgt, errs); err != nil { + if err = runScript(tgt, errs, traced); err != nil { return false, err } - if Force { - // Sequentially run jobs - err = <-errs - Jobs.Wait() - if isOkRun(err) { - continue - } - return false, nil - } jobs++ } - for i := 0; i < jobs; i++ { - err = <-errs - ok = ok && isOkRun(err) + ok := true + for ; jobs > 0; jobs-- { + ok = ok && isOkRun(<-errs) } return ok, nil }