Table of Contents
Make executes its commands always via the Bourne shell /bin/sh. Stirmake can either use the shell or omit its use, at the Stirfile author's preference.
The same syntax that is used in make too executes the command after tabulator via shell:
@toplevel @strict @phonyrule: 'all': 'hello' 'hello': 'hello.c' cc -o $@ $<
In this case, the command is sh -c "cc -o $@ $<". To omit shell and invoke the compiler directly, you can use the @-tab syntax, which takes a list of command-line arguments:
@toplevel @strict @phonyrule: 'all': 'hello' 'hello': 'hello.c' @ ["cc", "-o", $@, $<]
Also sometimes you may want to include more than one command in a single line. It is possible with the @@-tab syntax:
@toplevel @strict @phonyrule: 'all': 'hello' 'libhello.a': 'hello.o' @@ [["rm", "-f", $@], ["ar", "rvs", $@, @@suffilter($^, ".o")]]
In this case, after the tabulator you have an array of arrays of strings, not a simple array of strings.
Shell expansion may not always do what you want. Firstly, spaces in file names cause the file name to be split to multiple command-line arguments. The shell also assigns special meanings to many characters like `, \ and $. Stirmake does not have any attempt at shell escaping to support for example dollar signs in file names. Stirmake's way to allow dollar signs in file names is to not use shell, but instead invoke the compiler directly with no shell inbetween.
Of course, with the @-tab syntax it's possible to specify the shell manually:
@toplevel @strict @phonyrule: 'all': 'hello' 'hello': 'hello.c' @ ["sh", "-c", "cc -o " . $@ . " " . $<]
Note that the -c option takes only one argument, so everything has to be concatenated to a string with space as argument delimiter.
Stirmake has database which tells what commands have been executed to create the targets, and full timestamp and file size information for the targets. This default mode is used whenever -e is not specified as the command line argument. The uppercase -E on the other hand explicitly turns on this default mode. If -e is used, the database contains only build commands but not file timestamps and file sizes. The version of database is in this case @v1@.
An example of the database when -E is used:
@v2@ "." "prog": "NI" "E" "NM" "cc" "-Wall" "-O3" "-o" "prog" "hello.o" "." "hello.o": "NI" "E" "NM" "cc" "-Wall" "-O3" "-c" "-o" "hello.o" "hello.c" "." "hello.d": "NI" "E" "NM" "cc" "-Wall" "-O3" "-MM" "-o" "hello.d" "hello.c" "hello.d" = 17 1759334220 851355669 "hello.c" = 27 1759334155 483267579 "hello.o" = 1456 1759334220 879355707 "prog" = 16464 1759334220 895355729
The @v2@ specifier at beginning tells the version (v1: just commands, v2: also file sizes and timestamps). The first three files are mentioned along with the commands that were used to build the files. The first "NI", "E", "NM" are command settings (no-ignore / ignore, echo / no-echo, no-make / is-make) and the rest is the command that was used.
The last four files are mentioned along with their sizes in bytes and the modification timestamp of the files. If the file size or timestamp differs, it can be concluded that rebuild is necessary.
Let us assume that you have the following Makefile:
hello: hello.c cc -o hello hello.c
You want to take a backup of hello.c before you start modifying it. Then you modify it and run "make". Then for some reason you want to restore the old unmodified version. Then you run "make", and find that "make" won't build anything:
cp hello.c hello.c.bak $EDITOR hello.c make mv hello.c.bak hello.c make
In this case, the cp command creates a new timestamp for the copy, but it's before the timestamp of edited hello.c and before the timestamp of the binary hello which make created. However, mv is different from cp. Whereas cp creates a new timestamp, mv does a rename and doesn't touch the timestamp. Therefore, after the mv command, hello.c timestamp and possibly file size did change, but it didn't become newer than the timestamp of the hello binary. Therefore, with standard make, hello won't be rebuilt. This anomaly can be called move madness.
Stirmake is immune to move madness, since stirmake has a file size, command and timestamp database. In this example of move madness, at least the timestamp of hello.c did change although it didn't become newer than the timestamp of hello binary. Also it's possible the file size changed. In this case, stirmake has enough information to conclude that the file has in fact changed, and does a rebuild.
Stirmake supports three command options: @ignore, @noecho and @ismake.
Normally, all commands that stirmake executes are echoed to standard output, unless the -s (silent) option is given as argument. However, individual command echoing can be disabled with @noecho. This is useful if the list of commands has an "echo" command, which would then be echoed twice: first the full command name, then the argument to "echo". How to do this:
@toplevel @strict @phonyrule: 'all': @ @noecho ["echo", "foo"]
Also, normally, a command that fails stops the build process. All commands must return "true" exit status, 0, not "false" (which would be nonzero). To allow command returning false to not stop the build, use @ignore:
@toplevel @strict @phonyrule: 'all': @ @ignore ["false"] @ @noecho ["echo", "false was ignored"]
Stirmake needs to know if the sub-process it executes is GNU make. The reason being that GNU make requires a jobserver, which stirmake has to offer via environment variables. Normally, stirmake compares the program name with a built-in list of known GNU make names:
However, if GNU make is invoked with a command that is not present in this list, it is possible to mark the command as GNU make with @ismake:
@toplevel @strict @phonyrule: 'all': @ @ismake ["/opt/bin/gnumake", "-C", "subdir"]
Stirmake has a feature @glob to evaluate shell glob patterns, using the glob() interface without actually invoking the shell. However, its usage is somewhat limited as a list of commands has to have all commands evaluated before the commands can be executed, because stirmake has command database and needs to compare the current list of commands against the list as of previous invocation.
Consider for example that you have a zip file and you want to unzip it but remove all text files:
@toplevel @strict @phonyrule: 'unzip': 'zip.zip' @ ["unzip", "zip.zip"] @ ["rm", @@glob("*.txt")]
Now the question is, how does this work? Does it execute @glob before or after unzip? The unfortunate news is that stirmake has to have full knowledge of the build commands before they can be executed, because if the list is equal to the previous list of commands and if none of the files have been changed, it is pointless to invoke the commands. So, the @glob has to be executed before unzip is called. Therefore, the list of text files does not have any text files that were created by unzipping the archive.
This problem can be avoided by using the shell:
@toplevel @strict @phonyrule: 'unzip': 'zip.zip' @ ["unzip", "zip.zip"] @ ["sh", "-c", "rm *.txt"]
Now since the glob evaluation result isn't part of the command, this works fine since stirmake doesn't have to evaluate *.txt but rather the shell evaluates it. What *.txt evaluates to, isn't part of the build command database.
Where glob is useful is for example automatic creation of rules from .c to .d and .o:
@toplevel @strict $CC="cc" $CFLAGS=["-Wall", "-O3"] $SRC=@glob("*.c") $DEP=@sufsuball($SRC, ".c", ".d") $OBJ=@sufsuball($SRC, ".c", ".o") 'prog': $(OBJ) @ [$CC, @$CFLAGS, "-o", $@, @@suffilter($^, ".o")] @patrule: $(OBJ): '%.o': '%.c' '%.d' @ [$CC, @$CFLAGS, "-c", "-o", $@, $<] @patrule: $(DEP): '%.d': '%.c' @ [$CC, @$CFLAGS, "-MM", "-o", $@, $<] @cdepincludes @autophony @autotarget @ignore $DEP
In this example, the Stirfile does not need to be modified whenever a new .c file is created to the directory. It is up to the reader to decide whether this feature in Stirfile is good, or whether Stirfile should have an explicit list of what to compile.