]> Cypherpunks.ru repositories - goredo.git/commitdiff
DOT generation
authorSergey Matveev <stargrave@stargrave.org>
Sat, 21 Nov 2020 17:09:33 +0000 (20:09 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sat, 21 Nov 2020 17:09:33 +0000 (20:09 +0300)
.gitignore
README
cleanup.go
dep.go
dot.go [new file with mode: 0644]
main.go
ood.go
run.go
usage.go

index 2156cc9b2b547279c35e590b8f4e9e2ebadeacd6..40fa2fa961245c54036645bd5d6169394f7af986 100644 (file)
@@ -1,6 +1,8 @@
+/goredo
 /redo
 /redo-always
 /redo-cleanup
+/redo-dot
 /redo-ifchange
 /redo-ifcreate
 /redo-log
diff --git a/README b/README
index a918baefed4f23da7566afae23cbcf10caa93608..022ae2004815a88d490d6e6af0c3e777a6b20e2e 100644 (file)
--- a/README
+++ b/README
@@ -83,6 +83,11 @@ COMMANDS                                               *goredo-commands*
     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*
 
index a98510d1ec4d7763913310cd194484decce9ea68..bb9564cdc43285bd97472774693771bbd5feef04 100644 (file)
@@ -22,7 +22,6 @@ import (
        "io"
        "log"
        "os"
-       "path"
        "path/filepath"
        "strings"
 )
@@ -52,10 +51,7 @@ func redoDirClean(root, what string) error {
                }
                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) {
@@ -99,10 +95,7 @@ func cleanupWalker(root, what string) error {
                }
                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 {
diff --git a/dep.go b/dep.go
index 6900357b4964226e3730ae4e024543b4dbf27a0a..0f62d30243f0256e2a0be731de93f010be45052a 100644 (file)
--- a/dep.go
+++ b/dep.go
@@ -52,14 +52,14 @@ func ifcreate(fdDep *os.File, tgt string) error {
        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 {
@@ -71,7 +71,7 @@ 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},
        )
 }
@@ -109,7 +109,7 @@ func writeDep(fdDep *os.File, cwd, tgt string) error {
                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},
        }
@@ -121,8 +121,7 @@ func writeDep(fdDep *os.File, cwd, tgt string) error {
                }
                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 {
diff --git a/dot.go b/dot.go
new file mode 100644 (file)
index 0000000..d59bc40
--- /dev/null
+++ b/dot.go
@@ -0,0 +1,94 @@
+/*
+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
+}
diff --git a/main.go b/main.go
index 42d83beb0fddb14f15a3f5addb8181b5c362c7f1..fdd0aac75a97dcec4f7aa5c458fa64a2b5fa8052 100644 (file)
--- a/main.go
+++ b/main.go
@@ -77,6 +77,7 @@ func main() {
                        "redo",
                        "redo-always",
                        "redo-cleanup",
+                       "redo-dot",
                        "redo-ifchange",
                        "redo-ifcreate",
                        "redo-log",
@@ -213,6 +214,8 @@ CmdSwitch:
                                break
                        }
                }
+       case "redo-dot":
+               err = dotPrint(tgts)
        case "redo-stamp":
                if fdDep == nil {
                        log.Fatalln("no", EnvDepFd)
@@ -255,11 +258,7 @@ CmdSwitch:
                                }
                                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:
diff --git a/ood.go b/ood.go
index 4189ed459cae740df414d766ac3e6e8ceaedb675..15d0ae5abb6d15990dda880b627c14e096ab8ca7 100644 (file)
--- a/ood.go
+++ b/ood.go
@@ -31,6 +31,13 @@ import (
        "go.cypherpunks.ru/recfile"
 )
 
+const (
+       DepTypeIfcreate = "ifcreate"
+       DepTypeIfchange = "ifchange"
+       DepTypeAlways   = "always"
+       DepTypeStamp    = "stamp"
+)
+
 type TgtErr struct {
        Tgt string
        Err error
@@ -42,6 +49,14 @@ func (e TgtErr) Error() string {
        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)
@@ -75,15 +90,12 @@ func isBuiltNow(fdDep *os.File) (bool, *recfile.Reader, error) {
 }
 
 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)
@@ -107,7 +119,7 @@ func rebuildStamped(cwd, tgt, depPath string) (string, error) {
                        }
                        return "", err
                }
-               if m["Type"] == "stamp" {
+               if m["Type"] == DepTypeStamp {
                        stampTheir = m["Hash"]
                        break
                }
@@ -115,11 +127,10 @@ func rebuildStamped(cwd, tgt, depPath string) (string, error) {
        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 {
@@ -150,14 +161,14 @@ func isOOD(cwd, tgt string, level int) (bool, error) {
                        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:
diff --git a/run.go b/run.go
index fab67374eb24cb9bb14793cea1622d090098303a..c73d8b4517bbd29bcf0177ef72b1d7b77c6ed69a 100644 (file)
--- a/run.go
+++ b/run.go
@@ -162,9 +162,8 @@ func syncDir(dir string) error {
        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}
index d01d85d95c0461773bf84852bd2718aff283b4a2..d29e1cab4757edc5583fe6255d56aaaa6a3846e2 100644 (file)
--- a/usage.go
+++ b/usage.go
@@ -53,7 +53,7 @@ License GPLv3: GNU GPL version 3 <http://gnu.org/licenses/gpl.html>
 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.
 
@@ -64,6 +64,8 @@ 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