From 67566c9478fe04d37ff9f45a291a3933ef6af4ca Mon Sep 17 00:00:00 2001 From: Michael Gran Date: Sun, 14 Feb 2021 19:54:37 -0800 Subject: [PATCH] Clean up output --- CHEATSHEET.md | 149 ------------ NOTES.md | 510 +++++++++++++++++++++++++++++++++++++++++ README.md | 541 +++++++++----------------------------------- examples/simple.scm | 15 ++ potato/makevars.scm | 69 ++++-- potato/rules.scm | 198 +++++++++------- potato/text.scm | 2 +- 7 files changed, 804 insertions(+), 680 deletions(-) delete mode 100644 CHEATSHEET.md create mode 100644 NOTES.md create mode 100755 examples/simple.scm diff --git a/CHEATSHEET.md b/CHEATSHEET.md deleted file mode 100644 index 2dfa458..0000000 --- a/CHEATSHEET.md +++ /dev/null @@ -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 diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..3b9ebd6 --- /dev/null +++ b/NOTES.md @@ -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 diff --git a/README.md b/README.md index 3b9ebd6..902c5b6 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,13 @@ -# POTATO MAKE - -A build tool written in Guile Scheme. - -## Description +# CHEATSHEET FOR POTATO MAKE 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. +## Boilerplate -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. +Add this at the top of your build script. #!/usr/bin/env sh exec guile -s "$0" "$@" @@ -43,10 +16,16 @@ is named `makefile.scm`; however, you may choose any name. (use-modules (potato make)) (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 command-line arguments. The command-line arguments are the following, - makefile.scm [-hvqVeEbknB] [var=value...] [target_name...] + [-hvqVeEbknB] [var=value...] [target_name...] -h, --help displays help -v, --version @@ -60,16 +39,16 @@ command-line arguments. The command-line arguments are the following, and will override makevars set in the script -b, --builtins adds some default makevars and suffix rules - --ignore-errors + --ignore-errors [NOT IMPLEMENTED YET] 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 - -n, --no-execute + -n, --no-execute [NOT IMPLEMENTED YET] print rules, but only execute rules marked as 'always execute' -a, --ascii use ASCII-only output and no colors - -W, --warn + -W, --warn [NOT IMPLEMENTED YET] enable warning messages [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 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*. +## MAKEVARS -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. +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. - (initialize '("makefile.scm" "--verbosity=3" "CC=gcc" "all")) + ($ KEY) -> "VAL" -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. + ($ 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. - ;; ignore all command line arguments except the script name - (initialize '()) + (?= 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. -## 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. + (:= 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 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. +The *target rule* is for when the target, and the prerequisites, if any, +have filenames or phony names. -### 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...] - : name [prerequisites] [commands ...] - -`target-rule` (aka `:`) adds a target rule to the target rule -list. There are 3 components + Recipe as a string to be evaluated by the system -- 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.) + (: "foo.o" '("foo.c") + "cc -c foo.o") -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. + Recipe as a procedure -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. + (: "clean-foo" '() + (lambda () + (delete-file "foo.o"))) -There are a set of helper functions and variables that can be used to -construct recipes. + Recipe as a procedure that returns #f to indicate failure - string-compose element ... - ~ element ... - ignore-error-compose element ... - ~- element ... - silent-compose element ... - ~@ element ... - always-execute-compose element ... - ~+ element ... + (: "recent" '() + (lambda () + (if condition + #t + #f)))) -`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`. + Recipe as a procedure returning a string to be evaluated by the + system -For elements that are procedures, they are executed and their result -is used instead. + (: "foo.o" '("foo.c") + (lambda () + (format #f "cc ~A -c foo.c" some-flags)) -It is returned as a pair, where the `car` is the symbol `'default`. -That symbol is interpreted by the builder. + Recipe using recipe helper procedures, which create a string to + be evaluated by the system -`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. + (: "foo.c" '("foo.c") + (~ ($ CC) ($ CFLAGS) "-c" $<)) -`silent-compose` (aka `~@`) is like string-compose, but, it does not -print the resulting string to the output port, except with verbose output. + 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) -`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. + 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. - 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 -are newer than the target. +The *suffix rule* is a generic rule to convert one source file to a +target file, based on the filename extensions. - primary-prerequisite - $< - -`primary-prerequisite` (aka `$<`) returns the first prerequisite. + (-> ".c" ".o" + (~ ($ CC) ($ CFLAGS) "-c" $< "-o" $@)) - target-basename - $* - -`target-basename` (aka `$*`) returns the target with the suffix elided. - - prerequisites - $^ - -`prerequisites` (aka `$^`) return all the prerequisites. +## Recipe Helpers - %target-rule-list` + Concatenate elements with `~`. `~` inserts spaces between the + elements. -`%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. + 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 -Here are some example target rules that with recipes meant to be -executed by `system`. + Any procedures are applied lazily, when the rule is executed. - (: "foo.o" '("foo.c" "foo.h") - (~ "cc -o" $@ $<)) - - (: "clean" '() - "rm *.o" - "rm *~") - -Target rules may take advantage of makevars. + (~ "string" (lambda () "string") ($ KEY) $@ 100 ) - (: "foo.o" '("foo.c" "foo.h") - (~ ($ CC) ($ CFLAGS) "-o" $@ $<)) + Three versions of `~` with special effects + (~- ...) 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" '() - (lambda () - (delete-file "foo.o"))) + Recipes can contain the following automatic variables -### 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 + $@ 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 diff --git a/examples/simple.scm b/examples/simple.scm new file mode 100755 index 0000000..d2871bd --- /dev/null +++ b/examples/simple.scm @@ -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) diff --git a/potato/makevars.scm b/potato/makevars.scm index d71c149..b1fa60e 100644 --- a/potato/makevars.scm +++ b/potato/makevars.scm @@ -24,6 +24,8 @@ ;; 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. +(define %level-name '("unknown" "script" "command-line" "makeflags" "environment" "built-in")) + (define %ascii? #f) (define %makevars (make-hash-table)) (define %elevate-environment? #f) @@ -73,11 +75,12 @@ priority." (let* ((val&priority (hash-ref %makevars key)) (old-val (if (pair? val&priority) (cdr val&priority) #f)) (old-priority (if (pair? val&priority) (cdr val&priority) #f))) - (if (or (not old-val) - (override? old-priority new-priority)) - (if (procedure? new-val) - (hash-set! %makevars key (cons (delay new-val) new-priority)) - (hash-set! %makevars key (cons new-val new-priority))))) + (when (or (not old-val) + (override? old-priority new-priority)) + (if (procedure? new-val) + (hash-set! %makevars key (cons (delay new-val) new-priority)) + (hash-set! %makevars key (cons new-val new-priority))) + (when %verbose? (print-makevar key)))) *unspecified*) (define (makevars-add-keyvals keyvals) @@ -128,6 +131,45 @@ the value of MAKEFLAGS or SHELL." (makevars-set (car keyval) (cdr keyval) 5)) 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 (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) "Write out a list of the current makevars." (when (not (zero? (hash-count (const #t) %makevars))) @@ -199,9 +241,7 @@ the value of MAKEFLAGS or SHELL." (when (or environment? elevate-environment?) (makevars-add-environment) (makevars-add-makeflags)) - (makevars-add-keyvals keyvals) - (when %verbose? - (dump-makevars))) + (makevars-add-keyvals keyvals)) ;; API (define* (lazy-assign key #:optional (val "")) @@ -218,9 +258,7 @@ referenced. (set! key (key))) (unless (string? key) (set! key (format #f "~a" key))) - (makevars-set key (delay val)) - (when %verbose? - (format #t "~A=~A~%" key val))) + (makevars-set key (delay val))) (define-syntax ?= (lambda (stx) @@ -245,9 +283,7 @@ string to use as the key in the hash table entry. (set! val (val))) (unless (string? val) (set! val (format #f "~a" val))) - (makevars-set key val) - (when %verbose? - (format #t "~A=~A~%" key val))) + (makevars-set key val)) (define-syntax := (lambda (stx) @@ -283,7 +319,7 @@ space-separated token in the looked-up value." (priority (if (pair? val&priority) (cdr val&priority) #f))) (if (not val) (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 (if quoted? "\"\"" @@ -307,8 +343,7 @@ space-separated token in the looked-up value." (else (set! val (format #f "~a" val)))) (hash-set! %makevars key (cons val priority)) - (when %verbose? - (format #t "~A=~A~%" key val)) + (when %verbose? (print-makevar key)) (when (procedure? transformer) (set! val (string-append-with-spaces (map transformer diff --git a/potato/rules.scm b/potato/rules.scm index 64c21c8..9e0d869 100644 --- a/potato/rules.scm +++ b/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) "Register a new target rule" - (format #t "BLAMMO!! ~S~%" recipes) - - (when (>= %verbosity 0) + (when (>= %verbosity 3) (if (null? prerequisites) - (format #t "Defining 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~%~!" (lquo) name (rquo)) + (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. (when (null? recipes) @@ -226,8 +224,10 @@ installs it as the system driver. Returns the old system driver." "Register a suffix rule" ;; FIXME: Typecheck - (when (>= %verbosity 0) - (format #t "Defining suffix rule: ~A ~A ~A~%" source (right-arrow) target)) + (when (>= %verbosity 3) + (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 ;; (cons 'default string) (let ((recipes2 @@ -376,6 +376,11 @@ installs it as the system driver. Returns the old system driver." #t #f)) +(define (has-children? node) + (if (null? (node-get-children node)) + #f + #t)) + (define (get-parent node) (node-get-parent node)) @@ -398,13 +403,16 @@ installs it as the system driver. Returns the old system driver." #t #f))))) -(define (node-depth-string node) +(define (node-depth node) (let loop ((depth 0) (cur node)) (if (has-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 @@ -485,6 +493,7 @@ installs it as the system driver. Returns the old system driver." ;; and target rules (define (add-builtins) + #| (-> ".c" "" (~ ($ CC) ($ CFLAGS) ($ LDFLAGS) "-o" $@ $<)) (-> ".f" "" @@ -492,6 +501,7 @@ installs it as the system driver. Returns the old system driver." (-> ".sh" "" (~ "cp" $< $@) (~ "chmod a+x" $< $@)) + |# (-> ".c" ".o" (~ ($ CC) ($ CFLAGS) "-c" $<)) (-> ".f" ".o" @@ -576,8 +586,8 @@ runs them one-by-one, quitting on the first success." (when (>= %verbosity 3) (if (passed? node) - (format #t "PASS: ~a~%" (node-get-name node)) - (format #t "FAIL: ~a~%" (node-get-name node)))) + (format #t "PASS: ~a~%~!" (node-get-name node)) + (format #t "FAIL: ~a~%~!" (node-get-name node)))) (node-get-status node))) (define (run-recipes! node recipes) @@ -612,10 +622,10 @@ failure condition happens, mark the node as having failed." ((string? recipe) (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))) (= %verbosity 3)) - (format #t "~A~%" recipe)) + (format #t "~A~%~!" recipe)) (let ((retval (%system-proc recipe))) (if (zero? retval) (set-pass! node) @@ -628,10 +638,10 @@ failure condition happens, mark the node as having failed." ;; processed by system. ((string? retval) (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))) (= %verbosity 3)) - (format #t "~A~%" retval)) + (format #t "~A~%~!" retval)) (let ((retval2 (%system-proc retval))) (if (zero? retval2) (set-pass! node) @@ -721,6 +731,8 @@ file exists." (define (create-node name parent) "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))) (node-set-children! node '()) (node-set-rule-type! node 'default) @@ -787,8 +799,8 @@ file exists." ;; FIXME: First matching rule has highest priority? Or is last better? (node-set-rules! node (reverse (node-get-rules node))) (node-set-children! node (reverse (node-get-children node))) - ;;(format #t "matching suffix rules ~S~%" (node-get-rules node)) - ;;(format #t "matching children rules ~S~%" (node-get-children node)) + ;;(format #t "matching suffix rules ~S~%~!" (node-get-rules node)) + ;;(format #t "matching children rules ~S~%~!" (node-get-children node)) ;; And node is ready to go node)) @@ -798,112 +810,134 @@ file exists." This is where the magic happens." (let ((tree (create-node root #f))) (let ((node tree)) - (format #t "~ABegin building target ~a~A~a.~%" - (node-depth-string node) (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (format #t "~ABegin building target ~a~A~a.~%~!" + (node-depth-string node) (lquo) (node-get-name node) (rquo))) (while #t - (format #t "~AConsidering target ~a~A~a.~%" - (node-depth-string node) (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (format #t "~AConsidering target ~a~A~a.~%~!" + (node-depth-string node) (lquo) (node-get-name node) (rquo))) (if (undetermined? node) (begin - (format #t "~ATarget file ~a~A~a is undetermined.~%" - (node-depth-string node) (lquo) (node-get-name node) (rquo)) - (unless (node-get-mtime node) - (format #t "~AFile ~a~A~a does not exist.~%" - (node-depth-string node) (lquo) (node-get-name node) (rquo))) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a is undetermined.~%~!" + (node-depth-string node) (lquo) (node-get-name node) (rquo)) + (unless (node-get-mtime node) + (format #t "~AFile ~a~A~a does not exist.~%~!" + (node-depth-string node) (lquo) (node-get-name node) (rquo)))) (if (children-complete? node) (begin - (format #t "~AFinished prerequisites of target file ~a~A~a.~%" - (node-depth-string node) (lquo) (node-get-name node) (rquo)) + (when (and (>= %verbosity 3) (has-children? node)) + (format #t "~AFinished prerequisites of target file ~a~A~a.~%~!" + (node-depth-string node) (lquo) (node-get-name node) (rquo))) (if (children-passed? node) (begin - (format #t "~AThe prerequisites of target file ~a~A~a have passed.~%" - (node-depth-string node) (lquo) (node-get-name node) (rquo)) + (when (and (>= %verbosity 3) (has-children? node)) + (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) (begin (when (node-get-mtime node) - (format #t "~ATarget file ~a~A~a is up to date.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo))) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a is up to date.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo)))) (set-pass! node)) ;; else, not up to date (begin - (format #t "~ATarget file ~a~A~a is not up to date.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a is not up to date.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo))) (cond ((using-target-rule? node) - (format #t "~ATarget file ~a~A~a has a target rule.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a has a target rule.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo))) (run-target-rule! node)) ((using-suffix-rules? node) - (format #t "~ATarget file ~a~A~a has a suffix rule.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a has a suffix rule.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo))) (run-suffix-rules! node)) ((using-default-rule? node) - (format #t "~ATarget file ~a~A~a is using the default rule.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a is using the default rule.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo))) (run-default-rule! node)) (else (error "bad rules"))) (if (passed? node) - (format #t "~A[PASS] target file ~a~A~a.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo)) - (format #t "~A[FAIL] target file ~a~A~a.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo)))))) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a has passed.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo))) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a has failed.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo))))))) ;; else, children have failed (begin - (format #t "~AThe prerequisites of target file ~a~A~a have failed.~%" - (node-depth-string node) (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (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)))) ;; else, children aren't complete (begin - (format #t "~AThe prerequisites of target file ~a~A~a are incomplete.~%" - (node-depth-string node) (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (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))) - (format #t "~ANew node ~a~A~a ~a ~a~A~a.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo) - (right-arrow) - (lquo) (node-get-name next) (rquo)) + (when (>= %verbosity 3) + (format #t "~ADescending node ~a~A~a ~a ~a~A~a.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo) + (right-arrow) + (lquo) (node-get-name next) (rquo))) (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 (begin (if (passed? node) - (format #t "~ATarget file ~a~A~a is passed.~%" - (node-depth-string node) (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))) + (when (>= %verbosity 2) + (format #t "~A~a~A~a: ~Apass~A~%~!" + (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) (begin - (format #t "~ANew node ~a~A~a ~a ~a~A~a.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo) - (right-arrow) - (lquo) (node-get-name (node-get-parent node)) (rquo)) + (when (>= %verbosity 3) + (format #t "~AAscending node ~a~A~a ~a ~a~A~a.~%~!" + (node-depth-string node) + (lquo) (node-get-name node) (rquo) + (right-arrow) + (lquo) (node-get-name (node-get-parent node)) (rquo))) (set! node (get-parent node))) ;; else, there is no parent to this node (begin - (format #t "~ATarget file ~a~A~a has no parent.~%" - (node-depth-string node) - (lquo) (node-get-name node) (rquo)) + (when (>= %verbosity 3) + (format #t "~ATarget file ~a~A~a has no parent.~%~!" + (node-depth-string node) + (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) - (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) + (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))))))) ;; Return the command output of the root node (passed? tree))) diff --git a/potato/text.scm b/potato/text.scm index be70e94..5b5c323 100644 --- a/potato/text.scm +++ b/potato/text.scm @@ -5,7 +5,7 @@ left-arrow ellipses C0 - red + red green lquo rquo initialize-text))