2022-12-18

Tools: redo (part 1) Hello, world!

#software

Content

Part 0: Intro

Part 1: Hello, world!

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/

Part 1: Hello World

redo explained, very short version

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.

Hello, world!

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

C Files to play with

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!

default target: all

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.

Equivalent of make rules?

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?

Home