Part 2: Automatic Recording of Dependencies on Header Files
Part 3: CFLAGS and friends, config.sh, compile.do
Part 4: CFLAGS and friends, env/VAR, default.run.do
Part 5: Auto-update BUILDDATE in version.h
Part 6: The yacc/bison problem: one call produces two artifacts
Part 7: Test: Generator for N source files
My code featured in this series can be found at
https://git.sr.ht/~ew/ew.redo/
I used several sources, the Pennarun documentation, the overview by de Boyne Pollard, the hello world example (below) from pozorvlak.
That should be enough to understand the code in the next section.
So, how about a "Hello, world!" example of this wonder thing? Ok, ok. This example is largely lifted from pozorvlak's post "A quick introduction to redo"
https://pozorvlak.livejournal.com/159621.html
Of course we need some pieces to play with. So of course, we write a hello world programm, but we deliberately split it over 3 files:
// hello.c #include <stdio.h> #include "resources.h" int main(char argc, char** argv) { printf("%s\n", message); }
// resources.c char* message = "Hello, world!";
// resources.h extern char* message;
This is all valid C and should not pose a problem to your C compiler. Just to make sure, that the C code is good, we can try this on the shell:
shell$ gcc -o hello hello.c resources.c shell$ ./hello Hello, world!
Since redo looks for a target called "all", unless instructed otherwise, we will create such a file. We use a (sub-)command of redo called "redo-ifchange".
# all.do redo-ifchange hello
In this case just one line is good enough. "redo-ifchange hello" will record a dependency: If you want to build all, then a prerequisite named "hello" is wanted. Target "all" is going to be rebuilt, if hello has been rebuilt before. In this case, there is nothing to do, there are no more lines after recording the dependency.
Ok, so we just added another level of indirection :) Try it again, Sam! Unsurprisingly, hello is described in a file called hello.do
# hello.do OBJS="hello.o resources.o" redo-ifchange $OBJS gcc -o $3 $OBJS
This file is slightly bigger. The ingredients for hello are listed in a variable --- we could do without that variable, but don't repeat yourself, right? The contents of this variable points to the compiled .o files, from which hello is going to be built.
The second line records as many dependencies as there are objects in the list, also known as prerequisites. We want to build hello, whenever any of the prerequisites have changed and were rebuilt, thus recording the dependencies with "redo-ifchange".
The third line is a nofrills call to gcc, just with that inexplicable "$3" as a filename. This is the equivalent of a call to the linker only, and could look different.
"$3" holds a temporary filename, which will collect the object. If everything went ok, then the original file is replaced with the temporary file. Just renaming the file is regarded an atomic change --- either it completes or it fails entirely, no half-baken states in between. This way redo completely avoids inconsistent states, when the build gets interrupted for any reason.
Now, how do we get the still missing .o files into existence? Well we could write more .do files, one for each desired .o file. But that gets out of hand quickly. A separate file is warranted, if we need to add some trickery to the compiler call for just this source file. But for now, we need some equivalent of a make rule.
For this common task, redo will consult default.o.do, where "o" is the suffix in question.
# default.o.do redo-ifchange $2.c gcc -o $3 -c $2.c
For whatever source file the incantations in default.o.do are considered, "$2" holds the name of the file sans the suffix, "$3" holds the name of a temporary output file, as before. So we record a dependency for the source file, and then call the compiler, to just compile (and not link) the source file.
Now we have all pieces to actually try this:
shell$ redo all redo . . hello.o (default.o.do) (0.049s) redo . . resources.o (default.o.do) (0.072s) redo . hello (0.188s) redo all (0.230s) shell$ ./hello Hello, world! shell$ ls -al total 68 drwxr-xr-x 3 ew ew 4096 Oct 17 20:56 . drwxr-xr-x 7 ew ew 4096 Oct 17 19:09 .. -rw-r--r-- 1 ew ew 48 Oct 17 20:36 all.do -rw-r--r-- 1 ew ew 38 Oct 17 17:44 default.o.do -rwxr-xr-x 1 ew ew 16032 Oct 17 20:56 hello -rw-r--r-- 1 ew ew 108 Oct 17 16:19 hello.c -rw-r--r-- 1 ew ew 150 Oct 17 17:43 hello.do -rw-r--r-- 1 ew ew 1304 Oct 17 20:56 hello.o drwxr-xr-x 2 ew ew 4096 Oct 17 20:56 .redo -rw-r--r-- 1 ew ew 33 Oct 17 16:19 resources.c -rw-r--r-- 1 ew ew 22 Oct 17 16:20 resources.h -rw-r--r-- 1 ew ew 1136 Oct 17 20:56 resources.o
All is well, we did our first build.
The output of redo can be enhanced with the options "-x" and "-xx" and "-v" in some versions. But all is there to understand what happened. Every line is the result of building one target. It lists the target, which .do file it used to build it, and how long it took. It also keeps track of dependency levels by additional indentation, all being the top level target. Even though you didn't see any compiler commands in the output, an executable "hello" was built, and we can call it.
Before we proceed into any direction to make this example more useful, I would like to point out some more (sub-)commands.
shell$ redo-sources all.do clean.do default.o.do hello.c hello.do resources.c
shell$ redo-targets all clean hello hello.o resources.o
shell$ redo-ood all clean
I cheated on you. There is one more target: clean. It is described in a file clean.do. So let's add this really quick:
# clean.do rm -f hello *~ *.o
$ redo-dot digraph d { rankdir=LR ranksep=2 splines=false // splines=ortho node[shape=rectangle] "all" -> "all.do" "hello" -> "hello.o" "hello" -> "resources.o" "resources.o" -> "resources.c" "resources.o" -> "resources.o.do" [style=dotted] "resources.o" -> "default.o.do" "all" -> "all" [style=dotted] "all" -> "hello" "hello" -> "hello.do" "hello.o" -> "hello.o.do" [style=dotted] "hello.o" -> "default.o.do" "hello.o" -> "hello.c" }
Ok, it would become a bit verbose, but all the dependencies show up as one line each. One can create a .png file or explore the .dot file with a python3 programm named "xdot". This is nice!
shell$ redo target [...] # to assure that **/.redo/*.rec are filled up shell$ redo-dot target [...] > whatever.dot shell$ dot -Tpng whatever.dot > whatever.png # possibly add -Gsplines=ortho shell$ xdot whatever.dot
The Pennarun docs suggest two more things to add, a ".gitignore" file, if our project is using git, and test.do, holding an outer test of sorts. This can (and probably should) call other tools to achieve the level of testing appropriate for this place in this project. The point is, that any .do file need not be a sh file, als long as it adheres to the shebang notation.
And finally make sure, you compare this example with the hello world in the Pennarun docs. Don't just trust me.
https://redo.readthedocs.io/en/latest/cookbook/hello/
So, having seen one thing play out, where do we want to go next?