Redo: A Simple Way to (Re)build Targets from Sources
Redo allows you to rebuild files from source files when they've changed. It's simpler than other build systems such as Make or SCons and more generic than language-specific build systems such as Cabal or Apache Ant.
Redo gains its power and simplicity by leveraging other tools (in the Unix tradition). Build scripts for redo are simply shell scripts that follow a few conventions.
To build a target file, invoke redo with one or more target arguments.
redo target [target...]
There are no command-line options. Unlike Make, there is no default target for when you invoke
redo with no arguments (it will just return silently after doing no work).
What You Can Use Redo for
You can use
redo any time you want to automatically build some file(s) from some other file(s). For example:
Compiling Source Code
Compiling a .c File into a .o File
redo-ifchange $2.c redo-ifchange $(./cc -MM $2.c | tr -d '\n\\' | cut -d':' -f2) ./cc -c $2.c -o $3
Converting Markup into Documents
Generating an HTML Document from a Markdown Document
redo-ifchange $2.markdown pandoc $2.markdown
Advantages Over Make
- More flexibility thanks to .do scripts being full shell scripts.
- No need to learn Make's syntax or programming constructs.
- No need to worry about incompatibilities between different versions of Make.
- Better detection of actual changes through checksums instead of timestamps.
- Modularity without a speed penalty since redo is recursive by design.
- No supported method of building multiple targets with a single .do script.
- No built-in method to depend on environment variables or the contents of directories changing.
- The need to distribute a redo implementation if you're distributing a source archive.
Note that you can find a minimal implementation of redo written in bourne shell here. It's called "do" since it doesn't track changes to dependencies and always fully rebuilds the target(s), but it should be suitable for distributing with your source archive.
How to Use Redo in Your Project
Redo builds a target file by looking for a corresponding .do script. For example, you might want to build the file
index.markdown. When you invoke
redo index.html, redo will first look for a script named
index.html.do. If it fails to find that, it will then look for
default.html.do. If it finds a .do script, it will invoke it with 3 arguments:
$1: unused (
0) for backwards compatibility with older redo implementations
$2: the base name of the target (e.g.
indexfor the target
$3: the temporary file that you should output the resulting target file contents to
Your script is not required to use any of these arguments, although you will usually want to use
Scripts are executed by
sh which you should assume is a standard Bourne Shell.
Anything output to standard output will automatically be appended to the temporary output file (
$3 argument is provided for programs that don't output to standard output or don't provide an option to do so (such as compilers that need to rewind file pointers, etc.).
A temporary file is used (rather than outputting directly to the target file) to ensure that the target file only appears if it has been fully and correctly rebuilt (instead of partially rebuilt due to some interruption).
For example, if you wanted to convert
index.html using pandoc, in your
index.html.do file, instead of writing:
pandoc -o index.html index.markdown
you should write:
pandoc -o $3 index.markdown
or, you could take advantage of the fact that pandoc outputs to stdout by default and write:
and generalizing further with the
To specify that a target file depends on 1 or more source files, use the command
redo-ifchange inside of your .do script. For example, to specify that
index.html depends on the file
index.markdown, in the
index.html.do script you would write:
or more flexibly:
Redo will track the checksums of any dependencies specified this way and only build the target file if at least 1 of its dependencies has changed or the target file is missing.
Redo is able to track dependencies recursively. For example, if you were not creating
index.markdown directly but instead compiling it from some chapter files (such as
chapters/appendix.markdown), you could create a new
index.markdown.do file without making any changes to your
default.html.do file). Your new
index.markdown.do file could look something like:
redo-ifchange chapters/*.markdown cat chapters/*.markdown > $3
Note that since
redo-ifchange is simply a command that will be executed by the shell, we can use
* for filename globbing.
Redo will know when checking whether to rebuild
index.html that it should also check that the dependencies of
index.markdown are up-to-date as well.
Similarly, if you later wanted to build one of the chapter files from some other files, you just need to add an appropriate .do script (e.g.
Redo supports writing generic .do scripts based on file extensions. For example, if you had multiple Markdown files that you wanted to convert to HTML files, rather than write multiple .do scripts, you could instead take your existing
index.html.do and rename it to
default.html.do. Then, assuming that you'd written your .do script to take advantage of the
$2 argument, i.e.:
redo-ifchange $2.markdown pandoc $2.markdown
default.html.do will be run by redo to build any target ending in
.html (assuming that a more specific .do script doesn't exist).
I consider redo to be stable but not feature complete.
Redo is used to build itself. However, it is not a very complicated program.
Most of redis's build system (~200 lines of Make) was converted to .do scripts as a test of redo's suitability.
Redo has only been tested on FreeBSD and Linux.
Redo is written in Haskell. It was written "on-camera" by Chris Forno (jekor) as part of a tutorial video series on real-world Haskell development. You can find the videos on YouTube.
D. J. Bernstein conceived the idea behind redo and wrote some initial documentation at http://cr.yp.to/redo.html.
Alan Grosskurth wrote a thesis "Purely top-down software building" that includes an analysis and implementation of redo.
Mitchell Rosen, Göktuğ Kayaalp, and Rotten194 contributed suggestions on the tutorial videos during the process of writing the program. Many other viewers provided silent code review.