Clean up output

This commit is contained in:
Michael Gran 2021-02-14 19:54:37 -08:00
parent cbee22e2de
commit 67566c9478
7 changed files with 804 additions and 680 deletions

@ -1,149 +0,0 @@
# CHEATSHEET FOR POTATO MAKE
## Boilerplate
Add this at the top of your build script.
#!/usr/bin/env sh
exec guile -s "$0" "$@"
!#
(use-modules (potato make))
(initialize)
Add this at the bottom of your build script
(execute)
The rules go in between `initialize` and `build`
## MAKEVARS
A hash table called `%makevars` has string keys. These procedures
are syntax that add quotation marks around `key`, so you call them without the quotes on
`key`. The returned value of `$` is a string, or an empty string on failure.
($ KEY) -> "VAL"
($ key [transformer])
Look up `key` in the `%makevars` hash table and return the
result as a string. If `key` is not found, return an empty
string. If a string-to-string transformer procedure is
provided, apply it to each space-separated token in the
result.
(?= key val)
Assign `val` to `key` in the `%makevars` hash table. If `val`
is a procedure, assign its output to `key` the first time that
`key` is referenced.
(:= key val)
Assign `val` to `key` in the `%makevars` hash table. If `val`
is a procedure, evaluate it and assign its output to `key`
immediately.
## Rules
The *target rule* is for when the target, and the prerequisites, if any,
have filenames or phony names.
(: target-name '(prereq-name-1 prereq-name-2 ...)
recipe-1
recipe-2
...)
`target-name` is a string which is either a filename to be
created or an phony name like "all" or "clean".
Recipe as a string to be evaluated by the system
(: "foo.o" '("foo.c")
"cc -c foo.o")
Recipe as a procedure
(: "clean-foo" '()
(lambda ()
(delete-file "foo.o")))
Recipe as a procedure that returns #f to indicate failure
(: "recent" '()
(lambda ()
(if condition
#t
#f))))
Recipe as a procedure returning a string to be evaluated by the
system
(: "foo.o" '("foo.c")
(lambda ()
(format #f "cc ~A -c foo.c" some-flags))
Recipe using recipe helper procedures, which create a string to
be evaluated by the system
(: "foo.c" '("foo.c")
(~ ($ CC) ($ CFLAGS) "-c" $<))
Recipe as a boolean to indicate pass or failure without doing any
processing. For example, the rule below tells Potato Make that
the file "foo.c" exists without actually testing for it.
(: "foo.c" '() #t)
If there is no recipe at all, it is shorthand for the recipe #t,
indicating a recipe that always passes. This is used
in prerequisite-only target rules, such as below, which passes
so long as the prerequisites
pass. These two rules are the same.
(: "all" '("foo.exe"))
(: "all" '("foo.exe") #t)
Lastly, if the recipe is #f, this target will always fail.
(: "fail" '() #f)
The *suffix rule* is a generic rule to convert one source file to a
target file, based on the filename extensions.
(-> ".c" ".o"
(~ ($ CC) ($ CFLAGS) "-c" $< "-o" $@))
## Recipe Helpers
Concatenate elements with `~`. `~` inserts spaces between the
elements.
Elements can be
- strings
- procedures that return strings
- `%makevar` hash-table references
- automatic variables
- anything whose string representation as created by
(format #f "~A" ...) make sense
Any procedures are applied lazily, when the rule is executed.
(~ "string" (lambda () "string") ($ KEY) $@ 100 )
Three versions of `~` with special effects
(~- ...) ignores any errors
(~@ ...) doesn't print recipe to console
(~+ ...) runs even when `--no-execute` was chosen
## Automatic Variables
Recipes can contain the following automatic variables
$@ the target
$* the target w/o a filename suffix
$< the first prerequisite
$^ the prerequisites, as a single space-separated string
$$^ the prerequisites, as a scheme list of strings
$? the prerequisites that are files newer than the target file
as a single space-separated string
$$? the prerequisites that are files newer than the target file
as a scheme list of strings

510
NOTES.md Normal file

@ -0,0 +1,510 @@
# POTATO MAKE
A build tool written in Guile Scheme.
## Description
Potato Make is a scheme library that aims to simplify the task of
maintaining, updating, and regenerating programs. It is inspired by
the `make` utility in POSIX. With this library, you can write a
build script in Guile Scheme.
Like POSIX `make`, these scripts update files that are derived from
other files. The makefile script typically will describe how a file is
built from shell commands, and it will describe the relationships
between components to be built, so that they are built in order. In a
typical script, the script will check the prerequisites to a target,
and if the prerequisites are newer than the target, it will rebuild
the target.
There are two types of rules that a makefile script can contain.
1. Target rules, which describe how a specific named file is to be
built from prerequisites using a set of shell commands.
2. Suffix rules, which generically describe how to convert files with
one filename suffix into files with another filename suffix.
The makefile script will make use of a custom variable type which can
be set either in the script, by environment variables, or in command
line arguments. Let's call them *makevars*, to reduce confusion with
standard scheme variables.
## Setting up the Scheme Script
To write a build script with this library, one needs to add the
following boilerplate code at the top of an executable scheme script.
Throughout this documentation we will presume that this scheme script
is named `makefile.scm`; however, you may choose any name.
#!/usr/bin/env sh
exec guile -s "$0" "$@"
!#
(use-modules (potato make))
(initialize)
This boilerplate loads the library functions and it parses the
command-line arguments. The command-line arguments are the following,
makefile.scm [-hvqVeEbknB] [var=value...] [target_name...]
-h, --help
displays help
-v, --version
displays the version number of this script
-V [0,1,2,3], --verbosity=[0,1,2,3]
choose the verbosity of the output
-e, --environment
environment variables are converted to makevars
-E, --elevate-environment
environment variables are converted to makevars
and will override makevars set in the script
-b, --builtins
adds some default makevars and suffix rules
--ignore-errors
keep building even if a command fails
-k, --continue-on-error
keep building some targets even if a command fails
-n, --no-execute
print rules, but only execute rules marked as
'always execute'
-a, --ascii
use ASCII-only output and no colors
-W, --warn
enable warning messages
[var=value...]
set the value of makevars
[target_name...]
Set one or more targets to be executed. If no target
is specified, the first target found will be executed.
Note that in POSIX `make`, it, by default, adds in environment
variables and built-in rules. With this library, these require
command-line arguments to be enabled to pick up environment variables
and built-in rules. This is to make this tool more appropriate for
generating *reproducible builds*.
If you don't want `initialize` to parse the command line, you may call
it with specific command line arguments, like the example below. The
first string is the name of the script, and then any combination of
flags, macro assignments and targets may follow.
(initialize '("makefile.scm" "--verbosity=3" "CC=gcc" "all"))
If you call initialize with an empty list as below, it will guess the
script name from the command-line arguements, but, will ignore all
other flags and options.
;; ignore all command line arguments except the script name
(initialize '())
## Environment Variables
Certain environment variables affect the execution of the makefile
script.
`LANG` - affects the current locale
`MAKEFLAGS` - This will be parsed similar to command-line arguments.
If it contains the single, space-separated letters 'e', 'f', 'i',
'k', 'n', 'p', 'r', or 's', those options will be enabled as if
set on the command line. If it contains strings of the form
VAR=VALUE, it will set those makevars.
`SHELL` - The shell environment variable is always ignored.
All other environment variables, including those with null values,
shall initialize makevars.
## Signals
`SIGHUP`, `SIGTERM`, `SIGINT`, and `SIGQUIT` shall interrupt any
processing.
## Rules
The purpose of a makefile script is to run rules, which describe how
programs act on prerequisites to create targets. There are two types
of rules: target rules and suffix rules.
### Target Rules
Target rules are defined and manipulated with the following commands.
target-rule name [prerequisites] [commands...]
: name [prerequisites] [commands ...]
`target-rule` (aka `:`) adds a target rule to the target rule
list. There are 3 components
- NAME is a string that names the target. If this rule is being used
to create a file, NAME is the name of the file to be output.
NAME can also be a predicate procedure that maps string->boolean.
But if NAME is a procedure, this rule cannot be used at the
top-level target of a build.
- PREREQUISITES, if provided, is a list of strings or procedures of
zero arguments that evaluate to strings. Each entry is the
name of a target that needs to be exist before this target is
attempted. It may be an empty list, indicating that there are no
prerequisites.
- COMMANDS, if provided, are recipes that will be executed that are
intended to cause the target to be created. The recipe can be
either a string or a procedure.
If the COMMAND recipe is a string, it will be passed to the `system`
procedure for execution by the shell. If any call to system returns a
non-zero return value, processing will end. (This behavior is modified
by the `--ignore-errors` and `--continue-on-error` command-line
arguments.)
If the COMMAND recipe is a procedure, it will be executed. If it
returns `#f` or a non-zero integer, failure is assumed. If the
COMMAND recipe returns a string, the resulting string is passed to
`system` and is process as above.
If the COMMAND recipe is a pair, and the CAR of the pair is one of
`'ignore-error`, `'silent`, or `'always-execute`, it will have the
extra effect of ignoring errors, not printing the command line, or
always executing even when the `--no-execution` option is enabled.
The CDR must be a string or procedure as above.
There are a set of helper functions and variables that can be used to
construct recipes.
string-compose element ...
~ element ...
ignore-error-compose element ...
~- element ...
silent-compose element ...
~@ element ...
always-execute-compose element ...
~+ element ...
`string-compose` (aka `~`) takes as arguments one or more elements. It
converts the elements to strings and concatenates the strings,
appending spaces between them. The conversion to strings happens as if
by `display`.
For elements that are procedures, they are executed and their result
is used instead.
It is returned as a pair, where the `car` is the symbol `'default`.
That symbol is interpreted by the builder.
`ignore-error-compose` (aka `~-`) is like string-compose but returns a
pair with the first argument of `'ignore-error`. When passed as a
recipe, it causes the recipe not to end execution, even if an error is
signaled.
`silent-compose` (aka `~@`) is like string-compose, but, it does not
print the resulting string to the output port, except with verbose output.
`always-execute-compose` (aka `~+`) is like compose, but, it forces
the line to always be executed, even if the `--no-execution` option
was chosen.
target-name
$@
`target-name` (aka `$@`) is a global variable. If called from within the
context of a recipe, it contains as a string the name of the target.
target-name is not thread safe.
newer-prerequisites
$?
`newer-prerequisites` (aka `$?`) returns the list of prerequisites that
are newer than the target.
primary-prerequisite
$<
`primary-prerequisite` (aka `$<`) returns the first prerequisite.
target-basename
$*
`target-basename` (aka `$*`) returns the target with the suffix elided.
prerequisites
$^
`prerequisites` (aka `$^`) return all the prerequisites.
%target-rule-list`
`%target-rule-list` is list of targets rules encountered in the build
script in the order in which they were listed, converted into an
internal format.
Here are some example target rules that with recipes meant to be
executed by `system`.
(: "foo.o" '("foo.c" "foo.h")
(~ "cc -o" $@ $<))
(: "clean" '()
"rm *.o"
"rm *~")
Target rules may take advantage of makevars.
(: "foo.o" '("foo.c" "foo.h")
(~ ($ CC) ($ CFLAGS) "-o" $@ $<))
Target rules may also have recipes that execute scheme code
(: "clean" '()
(lambda ()
(delete-file "foo.o")))
### Suffix Rules
Unlike target rules which are for one specific target and may have
multiple prerequisites, suffix rules describe how to create a target
from a single prerequisite with the assumption that they have the same
basename and differ only in the filename suffixes. The are applied to
implicit prerequisites to other rules, or to explicit prerequisites to
other rules that have no target rules defined.
For example, one could have a suffix rule to convert a `*.c` file into
a `*.o` file. The syntax for suffix rules are similar to target rules
above.
suffix-rule source-suffix target-suffix [commands...]
-> source-suffix target-suffix [commands ...]
`suffix-rule` (aka `->` or `→`) adds a suffix rule to the suffix rule
list. There are 3 components
- SOURCE-SUFFIX is a string that names the filename suffix of the file
used to create the target. Commonly, this string begins with a
period.
SOURCE-SUFFIX can also be a conversion procedure that takes
in a target name string and converts it into a source name string.
- TARGET-SUFFIX, is a string that is the filename suffix of the file
to be created. The TARGET-SUFFIX could be an empty string,
indicating that the target is just the basename with no suffix.
TARGET-SUFFIX can also be a predicate procedure that takes in a
potential target name string and returns `#t` or `#f` if the target
name string should be processed with this suffix rule.
- COMMANDS, if provided, are recipes that will be executed that are
intended to cause the target to be created. The recipe can be
either a string or a procedure.
If the COMMAND recipe is a string, it will be passed to the `system`
procedure for execution by the shell. If any call to system returns a
non-zero return value, ending processing.
If the COMMAND recipe is a procedure, it will be executed. If it
returns #f or a non-zero integer, failure is assumed. If the COMMAND
recipe returns a string, the resulting string is passed to `system`
and is process as above.
%suffix-rule-list
`%suffix-rule-list` is list of suffix rules encountered in the build
script in the order in which they were listed, converted into an
internal format.
Example suffix rules are
(-> ".c" ".o"
(~ ($ CC) ($ CFLAGS) "-c" "-o" $@ $<))
(-> ".sh" ""
(~ "cp" $< $@)
(~ "chmod a+x" $@))
## makevars
Makefile scripts may take advantage of a special variable type
called a makevar. In scheme terms, makevars are entries in a
`%makevars` hash table that have special accessor syntax.
- The makevar names -- the keys -- are strings.
- The makevar values are either strings or procedures that take no
arguments that return strings.
There are five ways a makevar can be initialized.
1. Set directly in the script using the `?=` or `:=` syntax.
2. Set in command-line arguments
3. Extracted from the `MAKEFLAGS` environment variable
4. Generated from the environment variables
5. Or be one of the predefined variables built into this library
There is a priority to makevars. The variables from category five
above are set first, then four, then three, etc. Each lower category
may overwrite variables set in the higher category.
This priority is modified by the `-e` command-line argument. If `-e`
is set, category 1 variables *do not* override variables from categories
2, 3, and 4. They *do* override variables set in category 5.
The library provides the following procedures for makevars
lazy-assign key [val]
> `lazy-assign` sets a entry in the makevars hash table. KEY must be
> a string or a thunk that evaluates to a string. Likewise VAL must
> be a string or a thunk that evaluates to a string.
> If KEY is a thunk, it is immediately evaluated to a string to use as
> the key in the hash table entry.
> If VAL is a thunk, it is stored as a *promise* to be evaluated
> later. The promise will be evaluated the first time this key is
> referenced.
> If VAL is not given, the empty string will be used.
?= key [val]
> This is a syntax version of lazy-assign where KEY should be a string
> without quotes, e.g.
(?= foo "bar") ==> (lazy-assign "foo" "bar")
assign key [val]
> `assign` is the same as `lazy-assign` above, except that if VAL is a
> thunk it is immediately evaluated to a string and that string is
> used as the value in the hash table entry.
:= key [val]
> This is a syntax version of `assign` where KEY should be a string
> without quotes, e.g.
(:= foo "bar") ==> (assign "foo" "bar")
reference key [transformer]
> `reference` looks up KEY in the `%makevar` hash table. If it is
> found, VALUE is returned as a string.
> *IMPORTANT!* If it is not found, an empty string is returned. This
> is because it is a common practice in makefiles to use makevars that
> may or may not be defined by environment variables. With verbose output,
> a warning will be printed when a key cannot be found.
> If the value was stored using `lazy-assign` and is a *promise*, this
> procedure is *forced* to return a string. Also, the value in the
> hash table is updated to this string.
> The optional `transfomer` should be a function the takes a string
> and returns a string. It will be applied to every space-separated
> word in the value.
$ key
> This is a syntax version of `reference`, where KEY should be a
> string without quotes, e.g.
($ key) ==> (reference "key")
reference-func key
> `reference-func` returns a procedure of zero arguments that will,
> when called, look up a key as described in `reference` above.
$$ key
> This is a syntax version of reference-func, where KEY should be a
> string without quotes, e.g.
($$ key) ==> (reference-func "key")
%makevars
> This is the hash table. You are not meant to access it directly,
> but, with the functions above. If you do use it directly, the VALUE
> is a cons where the CAR is string or promise and the CDR is private
> data.
## The build algorithm
The initial target is given on the command line. If no target was
given on the command line, the first entry in the target list is used.
For each top-level target, create a n-ary tree of prerequisites. If a
target doesn't have an explicit rule, but has a suffix that appears in
one or more suffix rules, it searches for possible prerequisites that
would fulfill a suffix rule. Continue until the tree is populated.
Then for each node, try to compute timestamps for each target, if they
exist.
Mark as 'skip' each node that is a real file that is older than the
parent file.
In a depth-first search, build each node unless the node target is
older than the parent.
If a build recipe fails...
If '--ignore-errors', mark current node as 'skip', then keep going.
If '--continue-on-error', mark all siblings as 'skip', and mark the direct ancestors 'skip', keep
going.
Else, quit.
If we're not quit, once reaching the end, start with the next
top-level target (which only happens is multiple targets are given in
the command line).
## Built-in rules and makevars
If the `--builtins` option is given, there are some builtin suffix rules
and *makevars* that are present by default. These include the following.
You can add more builtins by updating the potato/builtins.scm file.
MAKE=make
AR=ar
ARFLAGS=-rv
YACC=yacc
YFLAGS=
LEX=lex
LFLAGS=
LDFLAGS=
CC=gcc
CFLAGS=-g -O2
FC=gfortran
FFLAGS=-g -O2
(-> ".c" ".o"
(~ ($ CC) ($ CFLAGS) "-c" $<)))
(-> ".f90" ".o"
(~ ($ FC) ($ FFLAGS) "-c" $<))
(-> ".y" ".o"
(~ ($ YACC) ($ YFLAGS) $<)
(~ ($ CC) ($ CFLAGS) "-c y.tab.c")
"rm -f y.tab.c"
(~ "mv y.tab.o" $@))
(-> ".l" ".o"
(~ ($ LEX) ($ LFLAGS) $<)
(~ ($ CC) ($ CFLAGS) "-c lex.yy.c")
"rm -f lex.yy.c"
(~ "mv lex.yy.o" $@))
(-> ".y" ".c"
(~ ($ YACC) ($ YFLAGS) $<)
(~ "mv y.tab.c" $@))
(-> ".l" ".c"
(~ ($ LEX) ($ LFLAGS) $<)
(~ "mv lex.yy.c" $@))
## Debug commands
These commands modify how rules are interpreted or executed
FIXME

541
README.md

@ -1,40 +1,13 @@
# POTATO MAKE # CHEATSHEET FOR POTATO MAKE
A build tool written in Guile Scheme.
## Description
Potato Make is a scheme library that aims to simplify the task of Potato Make is a scheme library that aims to simplify the task of
maintaining, updating, and regenerating programs. It is inspired by maintaining, updating, and regenerating programs. It is inspired by
the `make` utility in POSIX. With this library, you can write a the `make` utility in POSIX. With this library, you can write a
build script in Guile Scheme. build script in Guile Scheme.
Like POSIX `make`, these scripts update files that are derived from ## Boilerplate
other files. The makefile script typically will describe how a file is
built from shell commands, and it will describe the relationships
between components to be built, so that they are built in order. In a
typical script, the script will check the prerequisites to a target,
and if the prerequisites are newer than the target, it will rebuild
the target.
There are two types of rules that a makefile script can contain. Add this at the top of your build script.
1. Target rules, which describe how a specific named file is to be
built from prerequisites using a set of shell commands.
2. Suffix rules, which generically describe how to convert files with
one filename suffix into files with another filename suffix.
The makefile script will make use of a custom variable type which can
be set either in the script, by environment variables, or in command
line arguments. Let's call them *makevars*, to reduce confusion with
standard scheme variables.
## Setting up the Scheme Script
To write a build script with this library, one needs to add the
following boilerplate code at the top of an executable scheme script.
Throughout this documentation we will presume that this scheme script
is named `makefile.scm`; however, you may choose any name.
#!/usr/bin/env sh #!/usr/bin/env sh
exec guile -s "$0" "$@" exec guile -s "$0" "$@"
@ -43,10 +16,16 @@ is named `makefile.scm`; however, you may choose any name.
(use-modules (potato make)) (use-modules (potato make))
(initialize) (initialize)
Add this at the bottom of your build script
(execute)
The rules go in between `initialize` and `build`
This boilerplate loads the library functions and it parses the This boilerplate loads the library functions and it parses the
command-line arguments. The command-line arguments are the following, command-line arguments. The command-line arguments are the following,
makefile.scm [-hvqVeEbknB] [var=value...] [target_name...] <your-script-name> [-hvqVeEbknB] [var=value...] [target_name...]
-h, --help -h, --help
displays help displays help
-v, --version -v, --version
@ -60,16 +39,16 @@ command-line arguments. The command-line arguments are the following,
and will override makevars set in the script and will override makevars set in the script
-b, --builtins -b, --builtins
adds some default makevars and suffix rules adds some default makevars and suffix rules
--ignore-errors --ignore-errors [NOT IMPLEMENTED YET]
keep building even if a command fails keep building even if a command fails
-k, --continue-on-error -k, --continue-on-error [NOT IMPLEMENTED YET]
keep building some targets even if a command fails keep building some targets even if a command fails
-n, --no-execute -n, --no-execute [NOT IMPLEMENTED YET]
print rules, but only execute rules marked as print rules, but only execute rules marked as
'always execute' 'always execute'
-a, --ascii -a, --ascii
use ASCII-only output and no colors use ASCII-only output and no colors
-W, --warn -W, --warn [NOT IMPLEMENTED YET]
enable warning messages enable warning messages
[var=value...] [var=value...]
@ -78,433 +57,133 @@ command-line arguments. The command-line arguments are the following,
Set one or more targets to be executed. If no target Set one or more targets to be executed. If no target
is specified, the first target found will be executed. is specified, the first target found will be executed.
Note that in POSIX `make`, it, by default, adds in environment ## MAKEVARS
variables and built-in rules. With this library, these require
command-line arguments to be enabled to pick up environment variables
and built-in rules. This is to make this tool more appropriate for
generating *reproducible builds*.
If you don't want `initialize` to parse the command line, you may call A hash table called `%makevars` has string keys. These procedures
it with specific command line arguments, like the example below. The are syntax that add quotation marks around `key`, so you call them without the quotes on
first string is the name of the script, and then any combination of `key`. The returned value of `$` is a string, or an empty string on failure.
flags, macro assignments and targets may follow.
(initialize '("makefile.scm" "--verbosity=3" "CC=gcc" "all")) ($ KEY) -> "VAL"
If you call initialize with an empty list as below, it will guess the ($ key [transformer])
script name from the command-line arguements, but, will ignore all Look up `key` in the `%makevars` hash table and return the
other flags and options. result as a string. If `key` is not found, return an empty
string. If a string-to-string transformer procedure is
provided, apply it to each space-separated token in the
result.
;; ignore all command line arguments except the script name (?= key val)
(initialize '()) Assign `val` to `key` in the `%makevars` hash table. If `val`
is a procedure, assign its output to `key` the first time that
`key` is referenced.
## Environment Variables (:= key val)
Assign `val` to `key` in the `%makevars` hash table. If `val`
Certain environment variables affect the execution of the makefile is a procedure, evaluate it and assign its output to `key`
script. immediately.
`LANG` - affects the current locale
`MAKEFLAGS` - This will be parsed similar to command-line arguments.
If it contains the single, space-separated letters 'e', 'f', 'i',
'k', 'n', 'p', 'r', or 's', those options will be enabled as if
set on the command line. If it contains strings of the form
VAR=VALUE, it will set those makevars.
`SHELL` - The shell environment variable is always ignored.
All other environment variables, including those with null values,
shall initialize makevars.
## Signals
`SIGHUP`, `SIGTERM`, `SIGINT`, and `SIGQUIT` shall interrupt any
processing.
## Rules ## Rules
The purpose of a makefile script is to run rules, which describe how The *target rule* is for when the target, and the prerequisites, if any,
programs act on prerequisites to create targets. There are two types have filenames or phony names.
of rules: target rules and suffix rules.
### Target Rules (: target-name '(prereq-name-1 prereq-name-2 ...)
recipe-1
recipe-2
...)
Target rules are defined and manipulated with the following commands. `target-name` is a string which is either a filename to be
created or an phony name like "all" or "clean".
target-rule name [prerequisites] [commands...] Recipe as a string to be evaluated by the system
: name [prerequisites] [commands ...]
`target-rule` (aka `:`) adds a target rule to the target rule
list. There are 3 components
- NAME is a string that names the target. If this rule is being used (: "foo.o" '("foo.c")
to create a file, NAME is the name of the file to be output. "cc -c foo.o")
NAME can also be a predicate procedure that maps string->boolean.
But if NAME is a procedure, this rule cannot be used at the
top-level target of a build.
- PREREQUISITES, if provided, is a list of strings or procedures of
zero arguments that evaluate to strings. Each entry is the
name of a target that needs to be exist before this target is
attempted. It may be an empty list, indicating that there are no
prerequisites.
- COMMANDS, if provided, are recipes that will be executed that are
intended to cause the target to be created. The recipe can be
either a string or a procedure.
If the COMMAND recipe is a string, it will be passed to the `system`
procedure for execution by the shell. If any call to system returns a
non-zero return value, processing will end. (This behavior is modified
by the `--ignore-errors` and `--continue-on-error` command-line
arguments.)
If the COMMAND recipe is a procedure, it will be executed. If it Recipe as a procedure
returns `#f` or a non-zero integer, failure is assumed. If the
COMMAND recipe returns a string, the resulting string is passed to
`system` and is process as above.
If the COMMAND recipe is a pair, and the CAR of the pair is one of (: "clean-foo" '()
`'ignore-error`, `'silent`, or `'always-execute`, it will have the (lambda ()
extra effect of ignoring errors, not printing the command line, or (delete-file "foo.o")))
always executing even when the `--no-execution` option is enabled.
The CDR must be a string or procedure as above.
There are a set of helper functions and variables that can be used to Recipe as a procedure that returns #f to indicate failure
construct recipes.
string-compose element ... (: "recent" '()
~ element ... (lambda ()
ignore-error-compose element ... (if condition
~- element ... #t
silent-compose element ... #f))))
~@ element ...
always-execute-compose element ...
~+ element ...
`string-compose` (aka `~`) takes as arguments one or more elements. It Recipe as a procedure returning a string to be evaluated by the
converts the elements to strings and concatenates the strings, system
appending spaces between them. The conversion to strings happens as if
by `display`.
For elements that are procedures, they are executed and their result (: "foo.o" '("foo.c")
is used instead. (lambda ()
(format #f "cc ~A -c foo.c" some-flags))
It is returned as a pair, where the `car` is the symbol `'default`. Recipe using recipe helper procedures, which create a string to
That symbol is interpreted by the builder. be evaluated by the system
`ignore-error-compose` (aka `~-`) is like string-compose but returns a (: "foo.c" '("foo.c")
pair with the first argument of `'ignore-error`. When passed as a (~ ($ CC) ($ CFLAGS) "-c" $<))
recipe, it causes the recipe not to end execution, even if an error is
signaled.
`silent-compose` (aka `~@`) is like string-compose, but, it does not Recipe as a boolean to indicate pass or failure without doing any
print the resulting string to the output port, except with verbose output. processing. For example, the rule below tells Potato Make that
the file "foo.c" exists without actually testing for it.
(: "foo.c" '() #t)
`always-execute-compose` (aka `~+`) is like compose, but, it forces If there is no recipe at all, it is shorthand for the recipe #t,
the line to always be executed, even if the `--no-execution` option indicating a recipe that always passes. This is used
was chosen. in prerequisite-only target rules, such as below, which passes
so long as the prerequisites
target-name pass. These two rules are the same.
$@
`target-name` (aka `$@`) is a global variable. If called from within the
context of a recipe, it contains as a string the name of the target.
target-name is not thread safe.
newer-prerequisites (: "all" '("foo.exe"))
$? (: "all" '("foo.exe") #t)
Lastly, if the recipe is #f, this target will always fail.
(: "fail" '() #f)
`newer-prerequisites` (aka `$?`) returns the list of prerequisites that The *suffix rule* is a generic rule to convert one source file to a
are newer than the target. target file, based on the filename extensions.
primary-prerequisite (-> ".c" ".o"
$< (~ ($ CC) ($ CFLAGS) "-c" $< "-o" $@))
`primary-prerequisite` (aka `$<`) returns the first prerequisite.
target-basename ## Recipe Helpers
$*
`target-basename` (aka `$*`) returns the target with the suffix elided.
prerequisites
$^
`prerequisites` (aka `$^`) return all the prerequisites.
%target-rule-list` Concatenate elements with `~`. `~` inserts spaces between the
elements.
`%target-rule-list` is list of targets rules encountered in the build Elements can be
script in the order in which they were listed, converted into an - strings
internal format. - procedures that return strings
- `%makevar` hash-table references
- automatic variables
- anything whose string representation as created by
(format #f "~A" ...) make sense
Here are some example target rules that with recipes meant to be Any procedures are applied lazily, when the rule is executed.
executed by `system`.
(: "foo.o" '("foo.c" "foo.h") (~ "string" (lambda () "string") ($ KEY) $@ 100 )
(~ "cc -o" $@ $<))
(: "clean" '()
"rm *.o"
"rm *~")
Target rules may take advantage of makevars.
(: "foo.o" '("foo.c" "foo.h") Three versions of `~` with special effects
(~ ($ CC) ($ CFLAGS) "-o" $@ $<)) (~- ...) ignores any errors
(~@ ...) doesn't print recipe to console
(~+ ...) runs even when `--no-execute` was chosen
Target rules may also have recipes that execute scheme code ## Automatic Variables
(: "clean" '() Recipes can contain the following automatic variables
(lambda ()
(delete-file "foo.o")))
### Suffix Rules $@ the target
$* the target w/o a filename suffix
Unlike target rules which are for one specific target and may have $< the first prerequisite
multiple prerequisites, suffix rules describe how to create a target $^ the prerequisites, as a single space-separated string
from a single prerequisite with the assumption that they have the same $$^ the prerequisites, as a scheme list of strings
basename and differ only in the filename suffixes. The are applied to $? the prerequisites that are files newer than the target file
implicit prerequisites to other rules, or to explicit prerequisites to as a single space-separated string
other rules that have no target rules defined. $$? the prerequisites that are files newer than the target file
as a scheme list of strings
For example, one could have a suffix rule to convert a `*.c` file into
a `*.o` file. The syntax for suffix rules are similar to target rules
above.
suffix-rule source-suffix target-suffix [commands...]
-> source-suffix target-suffix [commands ...]
`suffix-rule` (aka `->` or `→`) adds a suffix rule to the suffix rule
list. There are 3 components
- SOURCE-SUFFIX is a string that names the filename suffix of the file
used to create the target. Commonly, this string begins with a
period.
SOURCE-SUFFIX can also be a conversion procedure that takes
in a target name string and converts it into a source name string.
- TARGET-SUFFIX, is a string that is the filename suffix of the file
to be created. The TARGET-SUFFIX could be an empty string,
indicating that the target is just the basename with no suffix.
TARGET-SUFFIX can also be a predicate procedure that takes in a
potential target name string and returns `#t` or `#f` if the target
name string should be processed with this suffix rule.
- COMMANDS, if provided, are recipes that will be executed that are
intended to cause the target to be created. The recipe can be
either a string or a procedure.
If the COMMAND recipe is a string, it will be passed to the `system`
procedure for execution by the shell. If any call to system returns a
non-zero return value, ending processing.
If the COMMAND recipe is a procedure, it will be executed. If it
returns #f or a non-zero integer, failure is assumed. If the COMMAND
recipe returns a string, the resulting string is passed to `system`
and is process as above.
%suffix-rule-list
`%suffix-rule-list` is list of suffix rules encountered in the build
script in the order in which they were listed, converted into an
internal format.
Example suffix rules are
(-> ".c" ".o"
(~ ($ CC) ($ CFLAGS) "-c" "-o" $@ $<))
(-> ".sh" ""
(~ "cp" $< $@)
(~ "chmod a+x" $@))
## makevars
Makefile scripts may take advantage of a special variable type
called a makevar. In scheme terms, makevars are entries in a
`%makevars` hash table that have special accessor syntax.
- The makevar names -- the keys -- are strings.
- The makevar values are either strings or procedures that take no
arguments that return strings.
There are five ways a makevar can be initialized.
1. Set directly in the script using the `?=` or `:=` syntax.
2. Set in command-line arguments
3. Extracted from the `MAKEFLAGS` environment variable
4. Generated from the environment variables
5. Or be one of the predefined variables built into this library
There is a priority to makevars. The variables from category five
above are set first, then four, then three, etc. Each lower category
may overwrite variables set in the higher category.
This priority is modified by the `-e` command-line argument. If `-e`
is set, category 1 variables *do not* override variables from categories
2, 3, and 4. They *do* override variables set in category 5.
The library provides the following procedures for makevars
lazy-assign key [val]
> `lazy-assign` sets a entry in the makevars hash table. KEY must be
> a string or a thunk that evaluates to a string. Likewise VAL must
> be a string or a thunk that evaluates to a string.
> If KEY is a thunk, it is immediately evaluated to a string to use as
> the key in the hash table entry.
> If VAL is a thunk, it is stored as a *promise* to be evaluated
> later. The promise will be evaluated the first time this key is
> referenced.
> If VAL is not given, the empty string will be used.
?= key [val]
> This is a syntax version of lazy-assign where KEY should be a string
> without quotes, e.g.
(?= foo "bar") ==> (lazy-assign "foo" "bar")
assign key [val]
> `assign` is the same as `lazy-assign` above, except that if VAL is a
> thunk it is immediately evaluated to a string and that string is
> used as the value in the hash table entry.
:= key [val]
> This is a syntax version of `assign` where KEY should be a string
> without quotes, e.g.
(:= foo "bar") ==> (assign "foo" "bar")
reference key [transformer]
> `reference` looks up KEY in the `%makevar` hash table. If it is
> found, VALUE is returned as a string.
> *IMPORTANT!* If it is not found, an empty string is returned. This
> is because it is a common practice in makefiles to use makevars that
> may or may not be defined by environment variables. With verbose output,
> a warning will be printed when a key cannot be found.
> If the value was stored using `lazy-assign` and is a *promise*, this
> procedure is *forced* to return a string. Also, the value in the
> hash table is updated to this string.
> The optional `transfomer` should be a function the takes a string
> and returns a string. It will be applied to every space-separated
> word in the value.
$ key
> This is a syntax version of `reference`, where KEY should be a
> string without quotes, e.g.
($ key) ==> (reference "key")
reference-func key
> `reference-func` returns a procedure of zero arguments that will,
> when called, look up a key as described in `reference` above.
$$ key
> This is a syntax version of reference-func, where KEY should be a
> string without quotes, e.g.
($$ key) ==> (reference-func "key")
%makevars
> This is the hash table. You are not meant to access it directly,
> but, with the functions above. If you do use it directly, the VALUE
> is a cons where the CAR is string or promise and the CDR is private
> data.
## The build algorithm
The initial target is given on the command line. If no target was
given on the command line, the first entry in the target list is used.
For each top-level target, create a n-ary tree of prerequisites. If a
target doesn't have an explicit rule, but has a suffix that appears in
one or more suffix rules, it searches for possible prerequisites that
would fulfill a suffix rule. Continue until the tree is populated.
Then for each node, try to compute timestamps for each target, if they
exist.
Mark as 'skip' each node that is a real file that is older than the
parent file.
In a depth-first search, build each node unless the node target is
older than the parent.
If a build recipe fails...
If '--ignore-errors', mark current node as 'skip', then keep going.
If '--continue-on-error', mark all siblings as 'skip', and mark the direct ancestors 'skip', keep
going.
Else, quit.
If we're not quit, once reaching the end, start with the next
top-level target (which only happens is multiple targets are given in
the command line).
## Built-in rules and makevars
If the `--builtins` option is given, there are some builtin suffix rules
and *makevars* that are present by default. These include the following.
You can add more builtins by updating the potato/builtins.scm file.
MAKE=make
AR=ar
ARFLAGS=-rv
YACC=yacc
YFLAGS=
LEX=lex
LFLAGS=
LDFLAGS=
CC=gcc
CFLAGS=-g -O2
FC=gfortran
FFLAGS=-g -O2
(-> ".c" ".o"
(~ ($ CC) ($ CFLAGS) "-c" $<)))
(-> ".f90" ".o"
(~ ($ FC) ($ FFLAGS) "-c" $<))
(-> ".y" ".o"
(~ ($ YACC) ($ YFLAGS) $<)
(~ ($ CC) ($ CFLAGS) "-c y.tab.c")
"rm -f y.tab.c"
(~ "mv y.tab.o" $@))
(-> ".l" ".o"
(~ ($ LEX) ($ LFLAGS) $<)
(~ ($ CC) ($ CFLAGS) "-c lex.yy.c")
"rm -f lex.yy.c"
(~ "mv lex.yy.o" $@))
(-> ".y" ".c"
(~ ($ YACC) ($ YFLAGS) $<)
(~ "mv y.tab.c" $@))
(-> ".l" ".c"
(~ ($ LEX) ($ LFLAGS) $<)
(~ "mv lex.yy.c" $@))
## Debug commands
These commands modify how rules are interpreted or executed
FIXME

15
examples/simple.scm Executable file

@ -0,0 +1,15 @@
#!/usr/bin/env sh
exec guile -s "$0" "$@"
!#
(use-modules (potato make))
(initialize)
(:= CC "gcc")
(:= CFLAGS "-g -O2")
(: "all" '("foo"))
(: "foo" '("foo.o" "bar.o")
(~ ($ CC) "-o" $@ $^))
(-> ".c" ".o"
(~ ($ CC) "-c" $<))
(execute)

@ -24,6 +24,8 @@
;; The lower priority level always win, unless the '-e' flag was set ;; The lower priority level always win, unless the '-e' flag was set
;; If the '-e' flag is set level 1 doesn't override level 3 and 4. ;; If the '-e' flag is set level 1 doesn't override level 3 and 4.
(define %level-name '("unknown" "script" "command-line" "makeflags" "environment" "built-in"))
(define %ascii? #f) (define %ascii? #f)
(define %makevars (make-hash-table)) (define %makevars (make-hash-table))
(define %elevate-environment? #f) (define %elevate-environment? #f)
@ -73,11 +75,12 @@ priority."
(let* ((val&priority (hash-ref %makevars key)) (let* ((val&priority (hash-ref %makevars key))
(old-val (if (pair? val&priority) (cdr val&priority) #f)) (old-val (if (pair? val&priority) (cdr val&priority) #f))
(old-priority (if (pair? val&priority) (cdr val&priority) #f))) (old-priority (if (pair? val&priority) (cdr val&priority) #f)))
(if (or (not old-val) (when (or (not old-val)
(override? old-priority new-priority)) (override? old-priority new-priority))
(if (procedure? new-val) (if (procedure? new-val)
(hash-set! %makevars key (cons (delay new-val) new-priority)) (hash-set! %makevars key (cons (delay new-val) new-priority))
(hash-set! %makevars key (cons new-val new-priority))))) (hash-set! %makevars key (cons new-val new-priority)))
(when %verbose? (print-makevar key))))
*unspecified*) *unspecified*)
(define (makevars-add-keyvals keyvals) (define (makevars-add-keyvals keyvals)
@ -128,6 +131,45 @@ the value of MAKEFLAGS or SHELL."
(makevars-set (car keyval) (cdr keyval) 5)) (makevars-set (car keyval) (cdr keyval) 5))
builtin-makevars)) builtin-makevars))
(define (print-makevar key)
(let ((val (hash-ref %makevars key)))
(let ((keyval-string
(if (zero? (string-length (car val)))
(string-copy key)
(string-append key " " (right-arrow) " " (car val)))))
;; Replace any control characters in VAL, like newline or tab
(set! keyval-string
(string-fold
(lambda (c str)
(string-append str
(if (char<? c #\space)
(C0 c)
(string c))))
""
keyval-string))
;; Truncate
(if (> (string-length keyval-string) 60)
(if %ascii?
(set! keyval-string
(string-append (substring keyval-string 0 57) "..."))
(set! keyval-string
(string-append (substring keyval-string 0 59) "…"))))
(let* ((space (make-string (- 64 (string-length keyval-string))
#\space))
(priority (cdr val))
(source-string (list-ref '("unknown"
"script"
"command line"
"MAKEFLAGS"
"environment"
"built-in")
priority)))
(display "Var: ")
(display keyval-string)
(display space)
(display source-string)
(newline)))))
(define (dump-makevars) (define (dump-makevars)
"Write out a list of the current makevars." "Write out a list of the current makevars."
(when (not (zero? (hash-count (const #t) %makevars))) (when (not (zero? (hash-count (const #t) %makevars)))
@ -199,9 +241,7 @@ the value of MAKEFLAGS or SHELL."
(when (or environment? elevate-environment?) (when (or environment? elevate-environment?)
(makevars-add-environment) (makevars-add-environment)
(makevars-add-makeflags)) (makevars-add-makeflags))
(makevars-add-keyvals keyvals) (makevars-add-keyvals keyvals))
(when %verbose?
(dump-makevars)))
;; API ;; API
(define* (lazy-assign key #:optional (val "")) (define* (lazy-assign key #:optional (val ""))
@ -218,9 +258,7 @@ referenced.
(set! key (key))) (set! key (key)))
(unless (string? key) (unless (string? key)
(set! key (format #f "~a" key))) (set! key (format #f "~a" key)))
(makevars-set key (delay val)) (makevars-set key (delay val)))
(when %verbose?
(format #t "~A=~A~%" key val)))
(define-syntax ?= (define-syntax ?=
(lambda (stx) (lambda (stx)
@ -245,9 +283,7 @@ string to use as the key in the hash table entry.
(set! val (val))) (set! val (val)))
(unless (string? val) (unless (string? val)
(set! val (format #f "~a" val))) (set! val (format #f "~a" val)))
(makevars-set key val) (makevars-set key val))
(when %verbose?
(format #t "~A=~A~%" key val)))
(define-syntax := (define-syntax :=
(lambda (stx) (lambda (stx)
@ -283,7 +319,7 @@ space-separated token in the looked-up value."
(priority (if (pair? val&priority) (cdr val&priority) #f))) (priority (if (pair? val&priority) (cdr val&priority) #f)))
(if (not val) (if (not val)
(if %strict (if %strict
(error (format #t "There is no makevar for key ~a~%" key)) (error (format #t "There is no makevar for key ~a~%~!" key))
;; else ;; else
(if quoted? (if quoted?
"\"\"" "\"\""
@ -307,8 +343,7 @@ space-separated token in the looked-up value."
(else (else
(set! val (format #f "~a" val)))) (set! val (format #f "~a" val))))
(hash-set! %makevars key (cons val priority)) (hash-set! %makevars key (cons val priority))
(when %verbose? (when %verbose? (print-makevar key))
(format #t "~A=~A~%" key val))
(when (procedure? transformer) (when (procedure? transformer)
(set! val (string-append-with-spaces (set! val (string-append-with-spaces
(map transformer (map transformer

@ -165,12 +165,10 @@ installs it as the system driver. Returns the old system driver."
(define* (target-rule name #:optional (prerequisites '()) #:rest recipes) (define* (target-rule name #:optional (prerequisites '()) #:rest recipes)
"Register a new target rule" "Register a new target rule"
(format #t "BLAMMO!! ~S~%" recipes) (when (>= %verbosity 3)
(when (>= %verbosity 0)
(if (null? prerequisites) (if (null? prerequisites)
(format #t "Defining target rule: ~a~A~a~%" (lquo) name (rquo)) (format #t "Target rule: ~a~A~a~%~!" (lquo) name (rquo))
(format #t "Defining target rule: ~a~A~a ~A ~A~%" (lquo) name (rquo) (left-arrow) prerequisites))) (format #t "Target rule: ~a~A~a ~A ~A~%~!" (lquo) name (rquo) (left-arrow) prerequisites)))
;; Empty recipes is shorthand for a recipe that always passes. ;; Empty recipes is shorthand for a recipe that always passes.
(when (null? recipes) (when (null? recipes)
@ -226,8 +224,10 @@ installs it as the system driver. Returns the old system driver."
"Register a suffix rule" "Register a suffix rule"
;; FIXME: Typecheck ;; FIXME: Typecheck
(when (>= %verbosity 0) (when (>= %verbosity 3)
(format #t "Defining suffix rule: ~A ~A ~A~%" source (right-arrow) target)) (format #t "Suffix rule: ~a~A~a ~A ~a~A~a~%~!"
(lquo) source (rquo) (right-arrow) (lquo) target (rquo)))
;; If any recipes are raw strings, we need to make them into ;; If any recipes are raw strings, we need to make them into
;; (cons 'default string) ;; (cons 'default string)
(let ((recipes2 (let ((recipes2
@ -376,6 +376,11 @@ installs it as the system driver. Returns the old system driver."
#t #t
#f)) #f))
(define (has-children? node)
(if (null? (node-get-children node))
#f
#t))
(define (get-parent node) (define (get-parent node)
(node-get-parent node)) (node-get-parent node))
@ -398,13 +403,16 @@ installs it as the system driver. Returns the old system driver."
#t #t
#f))))) #f)))))
(define (node-depth-string node) (define (node-depth node)
(let loop ((depth 0) (let loop ((depth 0)
(cur node)) (cur node))
(if (has-parent? cur) (if (has-parent? cur)
(loop (1+ depth) (get-parent cur)) (loop (1+ depth) (get-parent cur))
;; ;;
(make-string (* 2 depth) #\space)))) depth)))
(define (node-depth-string node)
(make-string (* 2 (node-depth node)) #\space))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; AUTOMATIC VARIABLES ;; AUTOMATIC VARIABLES
@ -485,6 +493,7 @@ installs it as the system driver. Returns the old system driver."
;; and target rules ;; and target rules
(define (add-builtins) (define (add-builtins)
#|
(-> ".c" "" (-> ".c" ""
(~ ($ CC) ($ CFLAGS) ($ LDFLAGS) "-o" $@ $<)) (~ ($ CC) ($ CFLAGS) ($ LDFLAGS) "-o" $@ $<))
(-> ".f" "" (-> ".f" ""
@ -492,6 +501,7 @@ installs it as the system driver. Returns the old system driver."
(-> ".sh" "" (-> ".sh" ""
(~ "cp" $< $@) (~ "cp" $< $@)
(~ "chmod a+x" $< $@)) (~ "chmod a+x" $< $@))
|#
(-> ".c" ".o" (-> ".c" ".o"
(~ ($ CC) ($ CFLAGS) "-c" $<)) (~ ($ CC) ($ CFLAGS) "-c" $<))
(-> ".f" ".o" (-> ".f" ".o"
@ -576,8 +586,8 @@ runs them one-by-one, quitting on the first success."
(when (>= %verbosity 3) (when (>= %verbosity 3)
(if (passed? node) (if (passed? node)
(format #t "PASS: ~a~%" (node-get-name node)) (format #t "PASS: ~a~%~!" (node-get-name node))
(format #t "FAIL: ~a~%" (node-get-name node)))) (format #t "FAIL: ~a~%~!" (node-get-name node))))
(node-get-status node))) (node-get-status node)))
(define (run-recipes! node recipes) (define (run-recipes! node recipes)
@ -612,10 +622,10 @@ failure condition happens, mark the node as having failed."
((string? recipe) ((string? recipe)
(when (= %verbosity 1) (when (= %verbosity 1)
(format #t "~a~%" (node-get-name node))) (format #t "~a~%~!" (node-get-name node)))
(when (or (and (= %verbosity 2) (not (eq? 'silent opt))) (when (or (and (= %verbosity 2) (not (eq? 'silent opt)))
(= %verbosity 3)) (= %verbosity 3))
(format #t "~A~%" recipe)) (format #t "~A~%~!" recipe))
(let ((retval (%system-proc recipe))) (let ((retval (%system-proc recipe)))
(if (zero? retval) (if (zero? retval)
(set-pass! node) (set-pass! node)
@ -628,10 +638,10 @@ failure condition happens, mark the node as having failed."
;; processed by system. ;; processed by system.
((string? retval) ((string? retval)
(when (= %verbosity 1) (when (= %verbosity 1)
(format #t "~a~%" (node-get-name node))) (format #t "~a~%~!" (node-get-name node)))
(when (or (and (= %verbosity 2) (not (eq? 'silent opt))) (when (or (and (= %verbosity 2) (not (eq? 'silent opt)))
(= %verbosity 3)) (= %verbosity 3))
(format #t "~A~%" retval)) (format #t "~A~%~!" retval))
(let ((retval2 (%system-proc retval))) (let ((retval2 (%system-proc retval)))
(if (zero? retval2) (if (zero? retval2)
(set-pass! node) (set-pass! node)
@ -721,6 +731,8 @@ file exists."
(define (create-node name parent) (define (create-node name parent)
"Constructs a tree of nodes, with name as the root node." "Constructs a tree of nodes, with name as the root node."
(when (and (node? parent) (> (node-depth parent) 30))
(error "Stack overflow"))
(let ((node (make-node name parent 'undetermined))) (let ((node (make-node name parent 'undetermined)))
(node-set-children! node '()) (node-set-children! node '())
(node-set-rule-type! node 'default) (node-set-rule-type! node 'default)
@ -787,8 +799,8 @@ file exists."
;; FIXME: First matching rule has highest priority? Or is last better? ;; FIXME: First matching rule has highest priority? Or is last better?
(node-set-rules! node (reverse (node-get-rules node))) (node-set-rules! node (reverse (node-get-rules node)))
(node-set-children! node (reverse (node-get-children node))) (node-set-children! node (reverse (node-get-children node)))
;;(format #t "matching suffix rules ~S~%" (node-get-rules node)) ;;(format #t "matching suffix rules ~S~%~!" (node-get-rules node))
;;(format #t "matching children rules ~S~%" (node-get-children node)) ;;(format #t "matching children rules ~S~%~!" (node-get-children node))
;; And node is ready to go ;; And node is ready to go
node)) node))
@ -798,112 +810,134 @@ file exists."
This is where the magic happens." This is where the magic happens."
(let ((tree (create-node root #f))) (let ((tree (create-node root #f)))
(let ((node tree)) (let ((node tree))
(format #t "~ABegin building target ~a~A~a.~%" (when (>= %verbosity 3)
(node-depth-string node) (lquo) (node-get-name node) (rquo)) (format #t "~ABegin building target ~a~A~a.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
(while #t (while #t
(format #t "~AConsidering target ~a~A~a.~%" (when (>= %verbosity 3)
(node-depth-string node) (lquo) (node-get-name node) (rquo)) (format #t "~AConsidering target ~a~A~a.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
(if (undetermined? node) (if (undetermined? node)
(begin (begin
(format #t "~ATarget file ~a~A~a is undetermined.~%" (when (>= %verbosity 3)
(node-depth-string node) (lquo) (node-get-name node) (rquo)) (format #t "~ATarget file ~a~A~a is undetermined.~%~!"
(unless (node-get-mtime node) (node-depth-string node) (lquo) (node-get-name node) (rquo))
(format #t "~AFile ~a~A~a does not exist.~%" (unless (node-get-mtime node)
(node-depth-string node) (lquo) (node-get-name node) (rquo))) (format #t "~AFile ~a~A~a does not exist.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo))))
(if (children-complete? node) (if (children-complete? node)
(begin (begin
(format #t "~AFinished prerequisites of target file ~a~A~a.~%" (when (and (>= %verbosity 3) (has-children? node))
(node-depth-string node) (lquo) (node-get-name node) (rquo)) (format #t "~AFinished prerequisites of target file ~a~A~a.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
(if (children-passed? node) (if (children-passed? node)
(begin (begin
(format #t "~AThe prerequisites of target file ~a~A~a have passed.~%" (when (and (>= %verbosity 3) (has-children? node))
(node-depth-string node) (lquo) (node-get-name node) (rquo)) (format #t "~AThe prerequisites of target file ~a~A~a have passed.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
(if (up-to-date? node) (if (up-to-date? node)
(begin (begin
(when (node-get-mtime node) (when (node-get-mtime node)
(format #t "~ATarget file ~a~A~a is up to date.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~ATarget file ~a~A~a is up to date.~%~!"
(lquo) (node-get-name node) (rquo))) (node-depth-string node)
(lquo) (node-get-name node) (rquo))))
(set-pass! node)) (set-pass! node))
;; else, not up to date ;; else, not up to date
(begin (begin
(format #t "~ATarget file ~a~A~a is not up to date.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~ATarget file ~a~A~a is not up to date.~%~!"
(lquo) (node-get-name node) (rquo)) (node-depth-string node)
(lquo) (node-get-name node) (rquo)))
(cond (cond
((using-target-rule? node) ((using-target-rule? node)
(format #t "~ATarget file ~a~A~a has a target rule.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~ATarget file ~a~A~a has a target rule.~%~!"
(lquo) (node-get-name node) (rquo)) (node-depth-string node)
(lquo) (node-get-name node) (rquo)))
(run-target-rule! node)) (run-target-rule! node))
((using-suffix-rules? node) ((using-suffix-rules? node)
(format #t "~ATarget file ~a~A~a has a suffix rule.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~ATarget file ~a~A~a has a suffix rule.~%~!"
(lquo) (node-get-name node) (rquo)) (node-depth-string node)
(lquo) (node-get-name node) (rquo)))
(run-suffix-rules! node)) (run-suffix-rules! node))
((using-default-rule? node) ((using-default-rule? node)
(format #t "~ATarget file ~a~A~a is using the default rule.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~ATarget file ~a~A~a is using the default rule.~%~!"
(lquo) (node-get-name node) (rquo)) (node-depth-string node)
(lquo) (node-get-name node) (rquo)))
(run-default-rule! node)) (run-default-rule! node))
(else (else
(error "bad rules"))) (error "bad rules")))
(if (passed? node) (if (passed? node)
(format #t "~A[PASS] target file ~a~A~a.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~ATarget file ~a~A~a has passed.~%~!"
(lquo) (node-get-name node) (rquo)) (node-depth-string node)
(format #t "~A[FAIL] target file ~a~A~a.~%" (lquo) (node-get-name node) (rquo)))
(node-depth-string node) (when (>= %verbosity 3)
(lquo) (node-get-name node) (rquo)))))) (format #t "~ATarget file ~a~A~a has failed.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))))))
;; else, children have failed ;; else, children have failed
(begin (begin
(format #t "~AThe prerequisites of target file ~a~A~a have failed.~%" (when (>= %verbosity 3)
(node-depth-string node) (lquo) (node-get-name node) (rquo)) (format #t "~AThe prerequisites of target file ~a~A~a have failed.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
(set-fail! node)))) (set-fail! node))))
;; else, children aren't complete ;; else, children aren't complete
(begin (begin
(format #t "~AThe prerequisites of target file ~a~A~a are incomplete.~%" (when (>= %verbosity 3)
(node-depth-string node) (lquo) (node-get-name node) (rquo)) (format #t "~AThe prerequisites of target file ~a~A~a are incomplete.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
(let ((next (get-next-child node))) (let ((next (get-next-child node)))
(format #t "~ANew node ~a~A~a ~a ~a~A~a.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~ADescending node ~a~A~a ~a ~a~A~a.~%~!"
(lquo) (node-get-name node) (rquo) (node-depth-string node)
(right-arrow) (lquo) (node-get-name node) (rquo)
(lquo) (node-get-name next) (rquo)) (right-arrow)
(lquo) (node-get-name next) (rquo)))
(set! node (get-next-child node)) (set! node (get-next-child node))
(format #t "~ATarget is now ~a~A~a.~%~!" ))))
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))
)))
;; else, this node is determined ;; else, this node is determined
(begin (begin
(if (passed? node) (if (passed? node)
(format #t "~ATarget file ~a~A~a is passed.~%" (when (>= %verbosity 2)
(node-depth-string node) (lquo) (node-get-name node) (rquo)) (format #t "~A~a~A~a: ~Apass~A~%~!"
(format #t "~ATarget file ~a~A~a has failed.~%" (node-depth-string node) (lquo) (node-get-name node) (rquo)
(node-depth-string node) (lquo) (node-get-name node) (rquo))) (green) (default)))
(when (>= %verbosity 2)
(format #t "~A~a~A~a: ~Afail~A~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)
(red) (default))))
(if (has-parent? node) (if (has-parent? node)
(begin (begin
(format #t "~ANew node ~a~A~a ~a ~a~A~a.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~AAscending node ~a~A~a ~a ~a~A~a.~%~!"
(lquo) (node-get-name node) (rquo) (node-depth-string node)
(right-arrow) (lquo) (node-get-name node) (rquo)
(lquo) (node-get-name (node-get-parent node)) (rquo)) (right-arrow)
(lquo) (node-get-name (node-get-parent node)) (rquo)))
(set! node (get-parent node))) (set! node (get-parent node)))
;; else, there is no parent to this node ;; else, there is no parent to this node
(begin (begin
(format #t "~ATarget file ~a~A~a has no parent.~%" (when (>= %verbosity 3)
(node-depth-string node) (format #t "~ATarget file ~a~A~a has no parent.~%~!"
(lquo) (node-get-name node) (rquo)) (node-depth-string node)
(lquo) (node-get-name node) (rquo)))
(if (passed? node) (if (passed? node)
(format #t "~A[COMPLETE] [PASS] target file ~a~A~a.~%" (when (>= %verbosity 1)
(node-depth-string node) (format #t "~A~a~A~a: ~Acomplete~A~%~!"
(lquo) (node-get-name node) (rquo)) (node-depth-string node)
(format #t "~A[COMPLETE] [FAIL] target file ~a~A~a.~%" (lquo) (node-get-name node) (rquo)
(node-depth-string node) (green) (default)))
(lquo) (node-get-name node) (rquo))) (when (>= %verbosity 1)
(format #t "~A~a~A~a: ~Acomplete~A~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)
(red) (default))))
(break))))))) (break)))))))
;; Return the command output of the root node ;; Return the command output of the root node
(passed? tree))) (passed? tree)))

@ -5,7 +5,7 @@
left-arrow left-arrow
ellipses ellipses
C0 C0
red red green
lquo lquo
rquo rquo
initialize-text)) initialize-text))