Clean up output
This commit is contained in:
parent
cbee22e2de
commit
67566c9478
149
CHEATSHEET.md
149
CHEATSHEET.md
@ -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
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
|
529
README.md
529
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
|
(: "foo.o" '("foo.c")
|
||||||
list. There are 3 components
|
"cc -c foo.o")
|
||||||
|
|
||||||
- NAME is a string that names the target. If this rule is being used
|
Recipe as a procedure
|
||||||
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.
|
(: "clean-foo" '()
|
||||||
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 ()
|
(lambda ()
|
||||||
(delete-file "foo.o")))
|
(delete-file "foo.o")))
|
||||||
|
|
||||||
### Suffix Rules
|
Recipe as a procedure that returns #f to indicate failure
|
||||||
|
|
||||||
Unlike target rules which are for one specific target and may have
|
(: "recent" '()
|
||||||
multiple prerequisites, suffix rules describe how to create a target
|
(lambda ()
|
||||||
from a single prerequisite with the assumption that they have the same
|
(if condition
|
||||||
basename and differ only in the filename suffixes. The are applied to
|
#t
|
||||||
implicit prerequisites to other rules, or to explicit prerequisites to
|
#f))))
|
||||||
other rules that have no target rules defined.
|
|
||||||
|
|
||||||
For example, one could have a suffix rule to convert a `*.c` file into
|
Recipe as a procedure returning a string to be evaluated by the
|
||||||
a `*.o` file. The syntax for suffix rules are similar to target rules
|
system
|
||||||
above.
|
|
||||||
|
|
||||||
suffix-rule source-suffix target-suffix [commands...]
|
(: "foo.o" '("foo.c")
|
||||||
-> source-suffix target-suffix [commands ...]
|
(lambda ()
|
||||||
|
(format #f "cc ~A -c foo.c" some-flags))
|
||||||
|
|
||||||
`suffix-rule` (aka `->` or `→`) adds a suffix rule to the suffix rule
|
Recipe using recipe helper procedures, which create a string to
|
||||||
list. There are 3 components
|
be evaluated by the system
|
||||||
|
|
||||||
- SOURCE-SUFFIX is a string that names the filename suffix of the file
|
(: "foo.c" '("foo.c")
|
||||||
used to create the target. Commonly, this string begins with a
|
(~ ($ CC) ($ CFLAGS) "-c" $<))
|
||||||
period.
|
|
||||||
|
|
||||||
SOURCE-SUFFIX can also be a conversion procedure that takes
|
Recipe as a boolean to indicate pass or failure without doing any
|
||||||
in a target name string and converts it into a source name string.
|
processing. For example, the rule below tells Potato Make that
|
||||||
|
the file "foo.c" exists without actually testing for it.
|
||||||
|
|
||||||
- TARGET-SUFFIX, is a string that is the filename suffix of the file
|
(: "foo.c" '() #t)
|
||||||
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
|
If there is no recipe at all, it is shorthand for the recipe #t,
|
||||||
potential target name string and returns `#t` or `#f` if the target
|
indicating a recipe that always passes. This is used
|
||||||
name string should be processed with this suffix rule.
|
in prerequisite-only target rules, such as below, which passes
|
||||||
|
so long as the prerequisites
|
||||||
|
pass. These two rules are the same.
|
||||||
|
|
||||||
- COMMANDS, if provided, are recipes that will be executed that are
|
(: "all" '("foo.exe"))
|
||||||
intended to cause the target to be created. The recipe can be
|
(: "all" '("foo.exe") #t)
|
||||||
either a string or a procedure.
|
|
||||||
|
|
||||||
If the COMMAND recipe is a string, it will be passed to the `system`
|
Lastly, if the recipe is #f, this target will always fail.
|
||||||
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
|
(: "fail" '() #f)
|
||||||
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
|
The *suffix rule* is a generic rule to convert one source file to a
|
||||||
|
target file, based on the filename extensions.
|
||||||
`%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"
|
(-> ".c" ".o"
|
||||||
(~ ($ CC) ($ CFLAGS) "-c" "-o" $@ $<))
|
(~ ($ CC) ($ CFLAGS) "-c" $< "-o" $@))
|
||||||
|
|
||||||
(-> ".sh" ""
|
## Recipe Helpers
|
||||||
(~ "cp" $< $@)
|
|
||||||
(~ "chmod a+x" $@))
|
|
||||||
|
|
||||||
|
Concatenate elements with `~`. `~` inserts spaces between the
|
||||||
|
elements.
|
||||||
|
|
||||||
## makevars
|
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
|
||||||
|
|
||||||
Makefile scripts may take advantage of a special variable type
|
Any procedures are applied lazily, when the rule is executed.
|
||||||
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.
|
(~ "string" (lambda () "string") ($ KEY) $@ 100 )
|
||||||
- The makevar values are either strings or procedures that take no
|
|
||||||
arguments that return strings.
|
|
||||||
|
|
||||||
There are five ways a makevar can be initialized.
|
Three versions of `~` with special effects
|
||||||
|
(~- ...) ignores any errors
|
||||||
|
(~@ ...) doesn't print recipe to console
|
||||||
|
(~+ ...) runs even when `--no-execute` was chosen
|
||||||
|
|
||||||
1. Set directly in the script using the `?=` or `:=` syntax.
|
## Automatic Variables
|
||||||
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
|
Recipes can contain the following automatic variables
|
||||||
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`
|
$@ the target
|
||||||
is set, category 1 variables *do not* override variables from categories
|
$* the target w/o a filename suffix
|
||||||
2, 3, and 4. They *do* override variables set in category 5.
|
$< the first prerequisite
|
||||||
|
$^ the prerequisites, as a single space-separated string
|
||||||
The library provides the following procedures for makevars
|
$$^ the prerequisites, as a scheme list of strings
|
||||||
|
$? the prerequisites that are files newer than the target file
|
||||||
lazy-assign key [val]
|
as a single space-separated string
|
||||||
|
$$? the prerequisites that are files newer than the target file
|
||||||
> `lazy-assign` sets a entry in the makevars hash table. KEY must be
|
as a scheme list of strings
|
||||||
> 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
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
|
||||||
|
166
potato/rules.scm
166
potato/rules.scm
@ -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)
|
||||||
|
(format #t "~ATarget file ~a~A~a is undetermined.~%~!"
|
||||||
(node-depth-string node) (lquo) (node-get-name node) (rquo))
|
(node-depth-string node) (lquo) (node-get-name node) (rquo))
|
||||||
(unless (node-get-mtime node)
|
(unless (node-get-mtime node)
|
||||||
(format #t "~AFile ~a~A~a does not exist.~%"
|
(format #t "~AFile ~a~A~a does not exist.~%~!"
|
||||||
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
|
(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)
|
||||||
|
(format #t "~ATarget file ~a~A~a is up to date.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo)))
|
(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)
|
||||||
|
(format #t "~ATarget file ~a~A~a is not up to date.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo))
|
(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)
|
||||||
|
(format #t "~ATarget file ~a~A~a has a target rule.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo))
|
(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)
|
||||||
|
(format #t "~ATarget file ~a~A~a has a suffix rule.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo))
|
(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)
|
||||||
|
(format #t "~ATarget file ~a~A~a is using the default rule.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo))
|
(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)
|
||||||
|
(format #t "~ATarget file ~a~A~a has passed.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo))
|
(lquo) (node-get-name node) (rquo)))
|
||||||
(format #t "~A[FAIL] target file ~a~A~a.~%"
|
(when (>= %verbosity 3)
|
||||||
|
(format #t "~ATarget file ~a~A~a has failed.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo))))))
|
(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)
|
||||||
|
(format #t "~ADescending node ~a~A~a ~a ~a~A~a.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo)
|
(lquo) (node-get-name node) (rquo)
|
||||||
(right-arrow)
|
(right-arrow)
|
||||||
(lquo) (node-get-name next) (rquo))
|
(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)
|
||||||
|
(format #t "~AAscending node ~a~A~a ~a ~a~A~a.~%~!"
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo)
|
(lquo) (node-get-name node) (rquo)
|
||||||
(right-arrow)
|
(right-arrow)
|
||||||
(lquo) (node-get-name (node-get-parent node)) (rquo))
|
(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))
|
|
||||||
(if (passed? node)
|
|
||||||
(format #t "~A[COMPLETE] [PASS] target file ~a~A~a.~%"
|
|
||||||
(node-depth-string node)
|
|
||||||
(lquo) (node-get-name node) (rquo))
|
|
||||||
(format #t "~A[COMPLETE] [FAIL] target file ~a~A~a.~%"
|
|
||||||
(node-depth-string node)
|
(node-depth-string node)
|
||||||
(lquo) (node-get-name node) (rquo)))
|
(lquo) (node-get-name node) (rquo)))
|
||||||
|
(if (passed? node)
|
||||||
|
(when (>= %verbosity 1)
|
||||||
|
(format #t "~A~a~A~a: ~Acomplete~A~%~!"
|
||||||
|
(node-depth-string node)
|
||||||
|
(lquo) (node-get-name node) (rquo)
|
||||||
|
(green) (default)))
|
||||||
|
(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))
|
||||||
|
Loading…
Reference in New Issue
Block a user