redo operates upon a set of files, divided into two kinds, that are connected into a dependency graph:
Source files are always leaf nodes on the dependency graph. They depend from no other files. Attempts to "re-do" (i.e. build) them are either no-ops or errors.
Target files are either leaf or interior nodes on the dependency graph. They depend from zero or more other files, which may in their turn be either source files or further target files. It is target files that are "re-done" (i.e. built).
redo is an incremental build system. Given a pristine set of source files, it re‐builds all of the target files dependent from those source files. But also: given a set of already built target files, and the set of source files from which they were built, plus some changes made to those source files subsequently, it runs only those parts of the build procedure that are necessary to re‐build the target files that depend from the changed parts of the source files.
redo stores a dependency database in a directory named .redo that is found in the top level directory of the overall thing being (re‐)built.
This database records, for each file, whether it is a source file or a target file.
For each target file it additionally records the source/target files that it depends from, with information that is used to determine at re‐build time whether those dependencies have changed.
For symbolic links and regular files, this information includes a hash of the link target or file contents.
.do programs
Re‐building is controlled by the execution of .do programs.
These are executable programs that contain the instructions both for rebuilding one specific or a class of target files, and for recording the dependency information in the dependency database as the (re‐)build goes on.
They can be written in any language as long as that language is capable of running three external commands: redo-ifchange, redo-ifcreate, and redo-ifdelete.
Conventionally, they are simply executable sh scripts, that being the highest common factor when it comes to build system portability over a wide range of operating systems.
There is no reason, however, that they could not be TCL shell scripts, or Perl scripts, or REXX scripts, or something else; if that were consistently available for directly executable programs on the operating system being used.
redo does not have a control file of its own.
Its entire operation involves tracing the dependency graph, finding what to re‐build, finding what .do program to run in order to re‐build that, running that program, and updating the dependency graph.
There is no separate layer of variables, control flow, and conditional execution on top of that of the .do programs (i.e. sh script variables, control flow, and conditional execution) themselves.
The system is recursive.
The top of the system is one command: redo.
This has no "currently building" target context in the dependency database.
It assumes that all of the target files that it is given require re‐building; and so skips querying the dependency database and proceeds directly to finding and running the .do programs.
The .do program has a "currently building" target context, communicated via environment variables and open file descriptors to every redo-ifchange, redo-ifcreate, and redo-ifdelete that the .do program invokes.
These programs thus know what part of the dependency database they are to update.
redo-ifdelete and redo-ifcreate do not recurse; but redo-ifchange does.
When a .do program executes redo-ifchange, the given dependencies are themselves processed.
redo-ifchange uses the contents of the dependency database to determine whether each dependency itself needs to be re‐built.
If it does, redo-ifchange searches for and runs the relevant .do program.
This in turn may result in further redo-ifchange calls, and further nested .do programs being run, for second level dependencies.
And so forth.