walk
is a fast, general purpose, graph based build and task execution utility.
Heavily inspired by make and redo.
- Fast parallel execution.
- Graph based dependency management.
- Maximum composability with existing UNIX tooling.
- Describe targets and their dependencies as simple executables.
- Universal execution; execute
walk
from any directory.
Using Go 1.7+:
$ go get -u github.com/ejholmes/walk
Or grab the latest release from https://github.com/ejholmes/walk/releases.
walk
is built on top of a very simple concept; when you want to build a target, walk
executes a file called Walkfile
to determine:
- What other targets the given target depends on.
- How to build the target.
For example, if you wanted to build a program called prog
from main.c and parse.c, you might write a Walkfile
like this:
#!/bin/bash
# The first argument is the "phase", which will either be `deps` or `exec`. In
# the `deps` phase, the Walkfile should print the name of the targets that this
# target depends on.
phase=$1
# The second argument is the name of the target, like `prog`, `parse.o`, etc.
target=$2
case $target in
prog)
case $phase in
# Prog depends on the object files we'll build from source. We simply
# print each dependency on a single line.
deps)
echo main.o
echo parse.o
;;
exec) exec gcc -Wall -o $target $($0 deps $target) ;;
esac ;;
# A generic recipe for building a .o file from a corresponding .c file.
*.o)
case $phase in
deps) echo ${target//.o/.c} ;;
exec) exec gcc -Wall -o $target -c $($0 deps $target) ;;
esac ;;
# When invoking walk(1) without any arguments, it defaults to a target called
# `all`.
all)
case $phase in
deps) echo prog ;;
esac ;;
# In general, it's good practice to include a fallback rule like this, in
# case someone tries to build a target that we don't know how to build (or
# someone makes a typo).
*.c|*.h) ;; # static files
*) >&2 echo "No rule for target \"$target\"" && exit 1 ;;
esac
When you execute walk all
, the following happens internally:
-
walk
resolves all of the dependencies, and builds a graph:$ Walkfile deps all prog $ Walkfile deps prog parse.o main.o $ Walkfile deps parse.o parse.c $ Walkfile deps main.o main.c $ Walkfile deps parse.c $ Walkfile deps main.c
-
walk
executes all of the targets, starting with dependencies:$ Walkfile exec parse.c $ Walkfile exec main.c $ Walkfile exec main.o $ Walkfile exec parse.o $ Walkfile exec prog $ Walkfile exec all
Ultimately, all of our targets end up getting invoked, and prog
is built:
$ walk
ok main.c
ok parse.c
ok parse.o
ok main.o
ok prog
ok all
We can print the dependency graph to verify that our dependency chain is what we expect:
$ walk -p dot
digraph {
"(root)" -> "all"
"all" -> "prog"
"prog" -> "main.o"
"prog" -> "parse.o"
"parse.o" -> "parse.c"
"main.o" -> "main.c"
}
And that's it. Wait, that's it? That's it. walk
is quite simply, just syntactic sugar over executing a binary as a graph.
See also man walk
.