-- Leo's gemini proxy
-- Connecting to ew.srht.site:1965...
-- Connected
-- Sending request
-- Meta line: 20 text/gemini
My code featured in this series can be found at
I used several sources, the Pennarun documentation, the overview by de Boyne Pollard, the hello world example (below) from pozorvlak.
"redo" called without arguments checks for a file "all.do" in the current directory or above. This is a convention like "make all".
"redo target" checks for target.do.
.do files are shell script snippets, the shebang line "#!/bin/sh" is not neccessary, as far as I can tell.
.do files do these two things and more: they record dependencies, and they contain the commands to build "target".
A redo project is always recursive by definition, redo will hunt upwards from the current targets directory for more information.
Dependencies and state are recorded in some way in one or more .redo directories.
A "target" generated by redo via snippet target.do is collected in a file with a temporary name. If there were no errors, then the file is renamed to it's target name. This avoids dealing with half baken files, where the generation step failed midway.
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"
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 :: almost classic example, however, the exact message is hidden in another file and made known via a header file.
// hello.c #include <stdio.h> #include "resources.h" int main(char argc, char** argv) { printf("%s\n", message); }
resources.c :: will actually hold said exact message.
// resources.c char* message = "Hello, world!";
resources.h :: will spread the news, that the variable message is actually defined somewhere.
// 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.
redo-sources --- will list all items known to be sources. Yes, the .do files are sources themselves, and dependencies on them are created.
shell$ redo-sources all.do clean.do default.o.do hello.c hello.do resources.c
redo-targets --- will list all items known to be targets. These can be produced somehow.
shell$ redo-targets all clean hello hello.o resources.o
redo-ood --- will list all targets, which are considered "out of date". Determining this is an art in itself, but at this moment, we just notice, that there are two items in this list:
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 --- will list the dependency graph in .dot format just like so.
$ 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.
So, having seen one thing play out, where do we want to go next?
Diligent readers have spotted, that there is no recorded dependency on resources.h or stdio.h, for that matter.
And everyone wants to see compilerflags in variables like the venerable CFLAGS, right? And of course, if their value is changed, the portions of the project actually using this value, shall be rebuild, right?
-- Response ended
-- Page fetched on Thu May 2 20:29:22 2024