+/goredo
/redo
/redo-always
/redo-cleanup
+/redo-dot
/redo-ifchange
/redo-ifcreate
/redo-log
default.do
../default.b.o.do
../default.o.do
+* redo-dot -- dependency DOT graph generator. For example to visualize
+ your dependencies with GraphViz: >
+ $ redo target [...] # to assure that .redo is filled up
+ $ redo-dot target [...] > whatever.dot
+ $ dot -Tpng whatever.dot > whatever.png # possibly add -Gsplines=ortho
<
STATE *goredo-state*
"io"
"log"
"os"
- "path"
"path/filepath"
"strings"
)
}
var pth string
for _, fi := range fis {
- pth, err = filepath.Rel(Cwd, path.Join(root, fi.Name()))
- if err != nil {
- panic(err)
- }
+ pth = cwdMustRel(root, fi.Name())
switch what {
case CleanupLog:
if strings.HasSuffix(fi.Name(), LogSuffix) {
}
var pth string
for _, fi := range fis {
- pth, err = filepath.Rel(Cwd, path.Join(root, fi.Name()))
- if err != nil {
- panic(err)
- }
+ pth = cwdMustRel(root, fi.Name())
if fi.IsDir() {
if fi.Name() == RedoDir {
if what == CleanupFull {
trace(CDebug, "ifcreate: %s <- %s", fdDep.Name(), tgt)
return recfileWrite(
fdDep,
- recfile.Field{Name: "Type", Value: "ifcreate"},
+ recfile.Field{Name: "Type", Value: DepTypeIfcreate},
recfile.Field{Name: "Target", Value: tgt},
)
}
func always(fdDep *os.File) error {
trace(CDebug, "always: %s", fdDep.Name())
- return recfileWrite(fdDep, recfile.Field{Name: "Type", Value: "always"})
+ return recfileWrite(fdDep, recfile.Field{Name: "Type", Value: DepTypeAlways})
}
func stamp(fdDep, src *os.File) error {
trace(CDebug, "stamp: %s <- %s", fdDep.Name(), hsh)
return recfileWrite(
fdDep,
- recfile.Field{Name: "Type", Value: "stamp"},
+ recfile.Field{Name: "Type", Value: DepTypeStamp},
recfile.Field{Name: "Hash", Value: hsh},
)
}
return err
}
fields := []recfile.Field{
- recfile.Field{Name: "Type", Value: "ifchange"},
+ recfile.Field{Name: "Type", Value: DepTypeIfchange},
recfile.Field{Name: "Target", Value: tgt},
recfile.Field{Name: "Ctime", Value: ts},
}
}
fields = append(fields, recfile.Field{Name: "Hash", Value: hsh})
}
- err = recfileWrite(fdDep, fields...)
- return err
+ return recfileWrite(fdDep, fields...)
}
func writeDeps(fdDep *os.File, tgts []string) error {
--- /dev/null
+/*
+goredo -- redo implementation on pure Go
+Copyright (C) 2020 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
+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 <http://www.gnu.org/licenses/>.
+*/
+
+// Dependency DOT graph generation
+
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path"
+
+ "go.cypherpunks.ru/recfile"
+)
+
+type DotNodes struct {
+ from string
+ to string
+}
+
+func dotWalker(data map[DotNodes]bool, tgtOrig string) (map[DotNodes]bool, error) {
+ cwd, tgt := cwdAndTgt(tgtOrig)
+ depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
+ fdDep, err := os.Open(depPath)
+ if err != nil {
+ return nil, err
+ }
+ defer fdDep.Close()
+ var dep string
+ r := recfile.NewReader(fdDep)
+ for {
+ m, err := r.NextMap()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return nil, err
+ }
+ switch m["Type"] {
+ case DepTypeIfcreate:
+ data[DotNodes{tgtOrig, cwdMustRel(cwd, m["Target"])}] = true
+ case DepTypeIfchange:
+ dep = m["Target"]
+ data[DotNodes{tgtOrig, cwdMustRel(cwd, dep)}] = false
+ if isSrc(cwd, dep) || dep == tgt {
+ continue
+ }
+ data, err = dotWalker(data, cwdMustRel(cwd, dep))
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ return data, nil
+}
+
+func dotPrint(tgts []string) error {
+ data := map[DotNodes]bool{}
+ var err error
+ for _, tgt := range tgts {
+ data, err = dotWalker(data, tgt)
+ if err != nil {
+ return err
+ }
+ }
+ fmt.Println(`digraph d {
+ rankdir=LR
+ ranksep=2
+ splines=false // splines=ortho
+ node[shape=rectangle]`)
+ for nodes, nonexistent := range data {
+ fmt.Printf("\n\t\"%s\" -> \"%s\"", nodes.from, nodes.to)
+ if nonexistent {
+ fmt.Print(" [style=dotted]")
+ }
+ }
+ fmt.Println("\n}")
+ return nil
+}
"redo",
"redo-always",
"redo-cleanup",
+ "redo-dot",
"redo-ifchange",
"redo-ifcreate",
"redo-log",
break
}
}
+ case "redo-dot":
+ err = dotPrint(tgts)
case "redo-stamp":
if fdDep == nil {
log.Fatalln("no", EnvDepFd)
}
break CmdSwitch
}
- rel, err := filepath.Rel(Cwd, path.Join(cwd, m["Target"]))
- if err != nil {
- panic(err)
- }
- fmt.Println(rel)
+ fmt.Println(cwdMustRel(cwd, m["Target"]))
}
ok = doFile != ""
default:
"go.cypherpunks.ru/recfile"
)
+const (
+ DepTypeIfcreate = "ifcreate"
+ DepTypeIfchange = "ifchange"
+ DepTypeAlways = "always"
+ DepTypeStamp = "stamp"
+)
+
type TgtErr struct {
Tgt string
Err error
return fmt.Sprintf("%s: %s", e.Tgt, e.Err)
}
+func cwdMustRel(paths ...string) string {
+ rel, err := filepath.Rel(Cwd, path.Join(paths...))
+ if err != nil {
+ panic(err)
+ }
+ return rel
+}
+
func cwdAndTgt(tgt string) (string, string) {
cwd, tgt := path.Split(tgt)
cwd, err := filepath.Abs(cwd)
}
func rebuildStamped(cwd, tgt, depPath string) (string, error) {
- relTgt, err := filepath.Rel(Cwd, path.Join(cwd, tgt))
- if err != nil {
- panic(err)
- }
+ relTgt := cwdMustRel(cwd, tgt)
errs := make(chan error, 1)
- if err = runScript(relTgt, errs); err != nil {
+ if err := runScript(relTgt, errs); err != nil {
return "", err
}
- if err = <-errs; !isOkRun(err) {
+ if err := <-errs; !isOkRun(err) {
return "", errors.New("build failed")
}
fdDep, err := os.Open(depPath)
}
return "", err
}
- if m["Type"] == "stamp" {
+ if m["Type"] == DepTypeStamp {
stampTheir = m["Hash"]
break
}
return stampTheir, nil
}
-func isOOD(cwd, tgt string, level int) (bool, error) {
- tgtOrig := tgt
+func isOOD(cwd, tgtOrig string, level int) (bool, error) {
indent := strings.Repeat(". ", level)
trace(CDebug, "ood: %s%s checking", indent, tgtOrig)
- cwd, tgt = cwdAndTgt(path.Join(cwd, tgt))
+ cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig))
depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
fdDep, err := os.Open(depPath)
if err != nil {
return true, TgtErr{tgtOrig, err}
}
switch m["Type"] {
- case "always":
+ case DepTypeAlways:
trace(CDebug, "ood: %s%s -> always", indent, tgtOrig)
ood = true
- case "ifcreate":
+ case DepTypeIfcreate:
ifcreates = append(ifcreates, m)
- case "ifchange":
+ case DepTypeIfchange:
ifchanges = append(ifchanges, m)
- case "stamp":
+ case DepTypeStamp:
stampOur = m["Hash"]
trace(CDebug, "ood: %s%s -> stamped: %s", indent, tgtOrig, stampOur)
default:
return err
}
-func runScript(tgt string, errs chan error) error {
- tgtOrig := tgt
- cwd, tgt := cwdAndTgt(tgt)
+func runScript(tgtOrig string, errs chan error) error {
+ cwd, tgt := cwdAndTgt(tgtOrig)
redoDir := path.Join(cwd, RedoDir)
if err := mkdirs(redoDir); err != nil {
return TgtErr{tgtOrig, err}
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
-redo, redo-{always,cleanup,ifchange,ifcreate,log,stamp,whichdo} must be
+redo, redo-{always,cleanup,dot,ifchange,ifcreate,log,stamp,whichdo} must be
linked to goredo executable. It determines the command by its own name.
You can create them by running: goredo -symlinks.
* redo-cleanup {full,log,tmp} [...]
remove either all goredo's related temporary files, or kept stderr
logs, or everything (including .redo directories) related
+* redo-dot target [...]
+ write dependency DOT graph to stdout
* redo-ifchange target [...]
build specified targets in parallel, if they are changed. Record them
as dependencies for current target