-- Leo's gemini proxy

-- Connecting to ew.srht.site:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

2022-12-18

Tools: redo (part 1) Hello, world!

tags: 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.


"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.



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 :: 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!

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.


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.

https://redo.readthedocs.io/en/latest/cookbook/hello/



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?





Home

-- Response ended

-- Page fetched on Thu May 2 20:29:22 2024