walk
- A fast, general purpose, graph based build and task execution utility.
walk
--help
walk
[-v
] [target...]
walk(1) is a small utility that can be used to execute tasks, or build programs from source. It's similar to make(1) in many ways, but with some fundamental differences that make it vastly simpler, and arguably more powerful.
At the core of walk(1) is a Directed Acyclic Graph (DAG). DAG's are a magical data structure that allow you to easily express dependency trees. You'll find DAG's everywhere; in git, languages, infrastructure tools, init systems and more. walk(1) provides a generic primitive to express a DAG as a set of targets (files) that depend on each other.
walk(1) can be used to build just about anything, from C/C++ programs to frontend applications (css, sass, coffeescript, etc) and infrastructure (CloudFormation, Terraform). Basically, anything that can express it's dependencies can be built.
-v
Show stdout from the Walkfile
when executing the exec phase.
-j
=numberControls the number of targets that are executed in parallel. By default,
targets are executed with the maximum level of parallelism that the graph
allows. To limit the number of targets that are executed in parallel, set
this to a value greater than 1
. To execute targets serially, set this to
1
.
-p
=formatPrints the underlying DAG to stdout, using the provided format. Available
formats are dot
and plain
.
--noprefix
By default, the stdout/stderr output from the Walkfile
is prefixed with the name
of the target, followed by a tab character. This flag disables the
prefixing. This can help with performance, or issues where you encounter
"too many open files", since prefixing necessitates more file descriptors.
Targets can be used to represent a task, or a file that needs to be built. They
are synonymous with targets in make(1). In general, targets are relative paths
to files that need to be built, like src/hello.o
. When a target does not
relate to an actual file on disk, it's synonymous with .PHONY
targets in
make(1).
walk(1) delegates to an executable file called Walkfile within the same directory as the target, to determine what dependencies the target has, and how to execute it.
The Walkfile
determines how a target is executed, and what other targets it
depends on.
When walk(1) begins execution of a target, it attempts to find an executable
file called Walkfile
in the same directory as the target, and then executes
it with the following positional arguments:
$1
The phase (deps
or exec
).
$2
The name of the target to build (e.g. hello.o
).
It's up to the Walkfile
to determine what dependencies the target has, and
how to execute it.
walk(1) has two phases:
Plan
In this phase, walk(1) executes the Walkfile
with deps
as the first
argument. The Walkfile
is expected to print a newline delimited list of
files that the target depends on, relative to the target. Internally,
walk(1) builds a graph of all of the targets and their dependencies.
Exec
In this phase, walk(1) executes the Walkfile
with exec
as the first
argument. The Walkfile
is expected to build the given target, but don't
need to if it's, for example, a task (like test
, clean
, etc).
walk(1) is heavily inspired by make(1) and redo. There are a number of reasons why walk(1) may be better in certain scenarios:
Simplicity
Conditional Execution
Recursiveness
walk
from any directory, and always get
the same result. Recursiveness comes for free.For the following examples, we'll assume that we want to build a program called
hello from
hello.c
and hello.h
. This can be expressed as a DAG, like the following:
all
|
hello
|
hello.o
/ \
hello.c hello.h
When walk
is invoked without any arguments, it defaults to a target called all
:
$ walk
Here's what happens within walk(1) when we execute this:
walk(1) resolves all of the dependencies, and builds a graph:
$ Walkfile deps all
hello
$ Walkfile deps hello
hello.o
$ Walkfile deps hello.o
hello.c
hello.h
$ Walkfile deps hello.c
$ Walkfile deps hello.h
walk(1) executes all of the targets, starting with dependencies:
$ Walkfile exec hello.c
$ Walkfile exec hello.h
$ Walkfile exec hello.o
$ Walkfile exec hello
$ Walkfile exec all
You can provide one or more targets as arguments to specify where to start
execution from. For example, if wanted to build just hello.o
and any of it's
dependencies:
$ walk hello.o
When targets are executed, they're always executed relative to the directory of
the target. This means that we can execute walk
from any directory, and
always get the same behavior. All of the following are identical:
$ walk hello.o
$ cd .. && walk 111-compile/hello.o
$ cd .. && walk test/111-compile/hello.o
See more at https://github.com/ejholmes/walk/tree/master/test.
When walk(1) receives SIGINT or SIGTERM, it will forward these signals down to any targets that are currently executing. With that in mind, it's a good idea to ensure that any potentially long running targets handle these signals to terminate gracefully.
You can find a list of bugs at https://github.com/ejholmes/walk/issues. Please report any issues there.
Walk is Copyright (C) 2017 Eric Holmes