This commit is contained in:
Michael Gran 2021-02-12 16:34:18 -08:00
parent a42fef03b9
commit 0a30f1d23a
9 changed files with 957 additions and 380 deletions

@ -13,7 +13,7 @@ Add this at the top of your build script.
Add this at the bottom of your build script Add this at the bottom of your build script
(build) (execute)
The rules go in between `initialize` and `build` The rules go in between `initialize` and `build`

@ -41,8 +41,7 @@ is named `makefile.scm`; however, you may choose any name.
!# !#
(use-modules (potato make)) (use-modules (potato make))
(setup (command-line)) (initialize)
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,
@ -72,6 +71,8 @@ command-line arguments. The command-line arguments are the following,
'always execute' 'always execute'
-a, --ascii -a, --ascii
use ASCII-only output and no colors use ASCII-only output and no colors
-W, --warn
enable warning messages
[var=value...] [var=value...]
set the value of makevars set the value of makevars
@ -85,6 +86,20 @@ command-line arguments to be enabled to pick up environment variables
and built-in rules. This is to make this tool more appropriate for and built-in rules. This is to make this tool more appropriate for
generating *reproducible builds*. 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" "--verbose" "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 ## Environment Variables
Certain environment variables affect the execution of the makefile Certain environment variables affect the execution of the makefile
@ -375,8 +390,12 @@ The library provides the following procedures for makevars
reference key [transformer] reference key [transformer]
> `reference` looks up KEY in the `%makevar` hash table. If it is > `reference` looks up KEY in the `%makevar` hash table. If it is
> found, VALUE is returned as a string. If it is not found, `#f` is > found, VALUE is returned as a string.
> returned.
> *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. In `--verbose`
> mode, a warning will be printed when a key cannot be found.
> If the value was stored using `lazy-assign` and is a *promise*, this > 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 > procedure is *forced* to return a string. Also, the value in the

@ -2,3 +2,9 @@
U+220E END OF PROOF U+220E END OF PROOF
U+227A PRECEDES U+227A PRECEDES
U+227B SUCCEEDS U+227B SUCCEEDS
a.out
→ foo.o [PASS]
-> bar.o [PASS]
a.out ≺

@ -5,6 +5,7 @@
bad-proc-output bad-proc-output
invalid-macro invalid-macro
not-a-regular-file not-a-regular-file
not-a-procedure
no-read-access-to-file no-read-access-to-file
)) ))
@ -58,6 +59,16 @@
(define (not-a-regular-file origin irritant) (define (not-a-regular-file origin irritant)
(raise-exception (make-not-a-regular-file origin irritant))) (raise-exception (make-not-a-regular-file origin irritant)))
(define (make-not-a-procedure origin irritants)
(make-exception
(make-programming-error)
(make-exception-with-origin origin)
(make-exception-with-message "Not a procedure")
(make-exception-with-irritants irritants)))
(define (not-a-procedure origin irritant)
(raise-exception (make-not-a-procedure origin irritant)))
(define (make-no-read-access-to-file origin irritants) (define (make-no-read-access-to-file origin irritants)
(make-exception (make-exception
(make-programming-error) (make-programming-error)

@ -9,12 +9,11 @@
#:use-module (potato rules) #:use-module (potato rules)
#:use-module (potato text) #:use-module (potato text)
#:export (initialize #:export (initialize
execute execute)
)
#:re-export (%suffix-rules #:re-export (%suffix-rules
lazy-assign ?= lazy-assign ?=
assign := assign :=
reference $ reference $ Q
reference-func $$ reference-func $$
target-rule : target-rule :
suffix-rule -> suffix-rule ->
@ -23,10 +22,15 @@
newer-prerequisites $? newer-prerequisites $?
prerequisites $^ prerequisites $^
primary-prerequisite $< primary-prerequisite $<
compose ~ string-compose ~
silent-compose ~@
always-execute-compose ~+
ignore-error-compose ~-
install-alternate-system-driver
)) ))
(define %version "1.0") (define %version "1.0")
(define %debug-argv0 #f)
;; #:re-export ( ;; #:re-export (
;; lazy-assign ?= ;; lazy-assign ?=
@ -58,7 +62,7 @@
;; If the -t option was specified, make shall write to standard ;; If the -t option was specified, make shall write to standard
;; output a message for each file that was touched. ;; output a message for each file that was touched.
(define %opt-quiet #f) (define %opt-terse #f)
(define %opt-verbose #f) (define %opt-verbose #f)
(define %opt-ignore-errors #f) (define %opt-ignore-errors #f)
(define %opt-continue-on-error #f) (define %opt-continue-on-error #f)
@ -68,7 +72,7 @@
(define (critical spec . args) (define (critical spec . args)
(apply format (append (list #t spec) args))) (apply format (append (list #t spec) args)))
(define (print spec . args) (define (print spec . args)
(unless %opt-quiet (unless %opt-terse
(apply format (append (list #t spec) args)))) (apply format (append (list #t spec) args))))
(define (debug spec . args) (define (debug spec . args)
(when %opt-verbose (when %opt-verbose
@ -77,7 +81,7 @@
(define option-spec (define option-spec
'((help (single-char #\h) (value #f)) '((help (single-char #\h) (value #f))
(version (single-char #\v) (value #f)) (version (single-char #\v) (value #f))
(quiet (single-char #\q) (value #f)) (terse (single-char #\q) (value #f))
(verbose (single-char #\V) (value #f)) (verbose (single-char #\V) (value #f))
(environment (single-char #\e) (value #f)) (environment (single-char #\e) (value #f))
(elevate-environment (single-char #\E) (value #f)) (elevate-environment (single-char #\E) (value #f))
@ -86,14 +90,15 @@
(continue-on-error (single-char #\k) (value #f)) (continue-on-error (single-char #\k) (value #f))
(no-execution (single-char #\n) (value #f)) (no-execution (single-char #\n) (value #f))
(ascii (single-char #\A) (value #f)) (ascii (single-char #\A) (value #f))
(strict (single-char #\S) (value #f))
)) ))
(define (display-help-and-exit argv0) (define (display-help-and-exit argv0)
(format #t "~A [-hvqVeEbn] [KEY=VALUE ...] [targets ...]~%" argv0) (format #t "~A [-hvqVeEbn] [KEY=VALUE ...] [targets ...]~%" argv0)
(format #t " -h, --help print help and exit~%") (format #t " -h, --help print help and exit~%")
(format #t " -v, --version print version and exit~%") (format #t " -v, --version print version and exit~%")
(format #t " -q, --quiet print minimal output~%") (format #t " -q, --terse use terse output~%")
(format #t " -V, --verbose print maximum output~%") (format #t " -V, --verbose use verbose output~%")
(format #t " -e, --environment use environment variables~%") (format #t " -e, --environment use environment variables~%")
(format #t " -E, --elevate-environment~%") (format #t " -E, --elevate-environment~%")
(format #t " use environment variables and let~%") (format #t " use environment variables and let~%")
@ -101,13 +106,15 @@
(format #t " -b, --builtins~%") (format #t " -b, --builtins~%")
(format #t " include some common variables and suffix rules~%") (format #t " include some common variables and suffix rules~%")
(format #t " --ignore-errors~%") (format #t " --ignore-errors~%")
(format #t " keep building even if commands fail~%") (format #t " ignore all errors~%")
(format #t " -k, --continue-on-error~%") (format #t " -k, --continue-on-error~%")
(format #t " keep building even if commands fail~%") (format #t " after an error, keep building other targets~%")
(format #t " -n, --no-execution~%") (format #t " -n, --no-execution~%")
(format #t " only execute rules marked as 'always execute'~%") (format #t " only execute rules marked as 'always execute'~%")
(format #t " -a, --ascii~%") (format #t " -a, --ascii~%")
(format #t " ASCII only output and no colors~%") (format #t " ASCII only output and no colors~%")
(format #t " -S, --strict~%")
(format #t " causes some behaviours to throw errors~%")
(exit 0)) (exit 0))
(define (display-version-and-exit argv0) (define (display-version-and-exit argv0)
@ -120,7 +127,7 @@
of pairs of KEY VAL" of pairs of KEY VAL"
(filter-map (filter-map
(lambda (str) (lambda (str)
(let ((tok (string-split str #\x))) (let ((tok (string-split str #\=)))
(cond (cond
((= 1 (length tok)) ((= 1 (length tok))
#f) #f)
@ -141,15 +148,20 @@ return them in a list."
lst)) lst))
(define* (initialize #:optional (define* (initialize #:optional
(arguments '())) (arguments #f))
"Set up the options, rules, and makevars. If ARGUMENTS "Set up the options, rules, and makevars. If ARGUMENTS
is not set, it will use options, makevars, and targets as is not set, it will use options, makevars, and targets as
specified by the command line. If it is set, it is specified by the command line. If it is set, it is
expected to be a list of strings that are command-line expected to be a list of strings that are command-line
arguments." arguments."
;; If left unset, assume user want all the command line arguments.
(when (not arguments)
(set! arguments (command-line)))
;; If the user has set it to '(), expecting a null environment, add
;; back in a filename, which is required.
(when (null? arguments) (when (null? arguments)
(set! arguments (program-arguments))) (set! arguments (list (car (program-arguments)))))
;; We start of with the --help and --version command-line arguments. ;; We start of with the --help and --version command-line arguments.
(let ((options (getopt-long arguments option-spec)) (let ((options (getopt-long arguments option-spec))
@ -159,7 +171,8 @@ arguments."
(%opt-no-errors #f) (%opt-no-errors #f)
(%opt-continue-on-error #f) (%opt-continue-on-error #f)
(%opt-no-execution #f) (%opt-no-execution #f)
(%opt-ascii #f)) (%opt-ascii #f)
(%opt-strict #f))
(when (option-ref options 'help #f) (when (option-ref options 'help #f)
(display-help-and-exit (car arguments))) (display-help-and-exit (car arguments)))
(when (option-ref options 'version #f) (when (option-ref options 'version #f)
@ -176,12 +189,12 @@ arguments."
(let ((mf (getenv "MAKEFLAGS"))) (let ((mf (getenv "MAKEFLAGS")))
(when mf (when mf
(let ((tokens (string-tokenize mf))) (let ((tokens (string-tokenize mf)))
(when (member "quiet" tokens) (when (member "terse" tokens)
(set! %opt-quiet #t) (set! %opt-terse #t)
(set! %opt-verbose #f)) (set! %opt-verbose #f))
(when (member "verbose" tokens) (when (member "verbose" tokens)
(set! %opt-verbose #t) (set! %opt-verbose #t)
(set! %opt-quiet #f)) (set! %opt-terse #f))
(when (member "builtins" tokens) (when (member "builtins" tokens)
(set! %opt-builtins #t)) (set! %opt-builtins #t))
(when (member "ascii" tokens) (when (member "ascii" tokens)
@ -190,28 +203,32 @@ arguments."
(set! %opt-ignore-errors #t)) (set! %opt-ignore-errors #t))
(when (member "continue-on-error" tokens) (when (member "continue-on-error" tokens)
(set! %opt-continue-on-error #t)) (set! %opt-continue-on-error #t))
(when (member "strict" tokens)
(set! %opt-strict #t))
(when (member "no-execution" tokens) (when (member "no-execution" tokens)
(set! %opt-no-execution #t)))))) (set! %opt-no-execution #t))))))
;; Now the bulk of the command-line options. ;; Now the bulk of the command-line options.
(when (option-ref options 'quiet #f) (when (option-ref options 'terse #f)
(set! %opt-quiet #t) (set! %opt-terse #t)
(set! %opt-verbose #f)) (set! %opt-verbose #f))
(when (option-ref options 'verbose #f) (when (option-ref options 'verbose #f)
(set! %opt-verbose #t) (set! %opt-verbose #t)
(set! %opt-quiet #f)) (set! %opt-terse #f))
(set! %opt-builtins (when (option-ref options 'builtins #f)
(option-ref options 'builtins #f)) (set! %opt-builtins #t))
(set! %opt-elevate-environment (when (option-ref options 'elevate-environment #f)
(option-ref options 'elevate-environment #f)) (set! %opt-elevate-environment #t))
(set! %opt-ignore-errors (when (option-ref options 'ignore-errors #f)
(option-ref options 'ignore-errors #f)) (set! %opt-ignore-errors #t))
(set! %opt-continue-on-error (when (option-ref options 'continue-on-error #f)
(option-ref options 'continue-on-error #f)) (set! %opt-continue-on-error #t))
(set! %opt-no-execution (when (option-ref options 'no-execution #f)
(option-ref options 'no-execution #f)) (set! %opt-no-execution #t))
(set! %opt-ascii (when (option-ref options 'ascii #f)
(option-ref options 'ascii #f)) (set! %opt-ascii #t))
(when (option-ref options 'strict #f)
(set! %opt-strict #t))
;; Now that all the options are set, we can set up ;; Now that all the options are set, we can set up
;; the build environment. ;; the build environment.
@ -221,14 +238,20 @@ arguments."
%opt-environment %opt-environment
%opt-elevate-environment %opt-elevate-environment
%opt-builtins %opt-builtins
%opt-strict
%opt-verbose %opt-verbose
%opt-ascii) %opt-ascii)
#;(initialize-rules %opt-no-builtins
%opt-verbose)
;; The remaining command-line words are the build targets that ;; The remaining command-line words are the build targets that
;; we're going to tackle. ;; we're going to tackle.
(set! %targets (parse-targets extra)) (set! %targets (parse-targets extra))
(initialize-rules %targets
%opt-builtins
%opt-ignore-errors
%opt-continue-on-error
%opt-no-execution
%opt-terse
%opt-verbose
%opt-ascii)
(set! %initialized #t) (set! %initialized #t)
%targets %targets
))) )))
@ -262,7 +285,7 @@ targets listed on the parsed command-line are used."
(if (not (build target)) (if (not (build target))
;; %opt-ignore-errors ;; %opt-ignore-errors
;; %opt-continue-on-error ;; %opt-continue-on-error
;; %opt-quiet ;; %opt-terse
;; %opt-verbose)) ;; %opt-verbose))
(begin (begin
(print "The recipe for “~A” has failed.~%" target)) (print "The recipe for “~A” has failed.~%" target))

@ -9,7 +9,7 @@
%elevate-environment? %elevate-environment?
lazy-assign ?= lazy-assign ?=
assign := assign :=
reference $ reference $ Q
reference-func $$ reference-func $$
dump-makevars dump-makevars
)) ))
@ -27,6 +27,7 @@
(define %ascii? #f) (define %ascii? #f)
(define %makevars #f) (define %makevars #f)
(define %elevate-environment? #f) (define %elevate-environment? #f)
(define %strict #f)
(define %verbose? #t) (define %verbose? #t)
(define (debug spec . args) (define (debug spec . args)
(when %verbose? (when %verbose?
@ -55,7 +56,7 @@ later equals signs."
"The logic of whether which makemacro priority levels can override "The logic of whether which makemacro priority levels can override
others." others."
(if %elevate-environment? (if %elevate-environment?
(if (and (or (= old-priority) (= old-priority 3) (= old-priority 4)) (if (and (or (= old-priority 2) (= old-priority 3) (= old-priority 4))
(= new-priority 1)) (= new-priority 1))
#f #f
;; else ;; else
@ -185,15 +186,17 @@ the value of MAKEFLAGS or SHELL."
environment? environment?
elevate-environment? elevate-environment?
builtins? builtins?
strict?
verbose? verbose?
ascii?) ascii?)
(set! %elevate-environment? elevate-environment?) (set! %elevate-environment? elevate-environment?)
(set! %makevars (make-hash-table)) (set! %makevars (make-hash-table))
(set! %strict strict?)
(set! %verbose? verbose?) (set! %verbose? verbose?)
(set! %ascii? ascii?) (set! %ascii? ascii?)
(when builtins? (when builtins?
(makevars-add-builtins)) (makevars-add-builtins))
(when 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)
@ -211,19 +214,13 @@ the key in the hash table entry.
later. The promise will be evaluated the first time this key is later. The promise will be evaluated the first time this key is
referenced. referenced.
If VAL is not given, the empty string will be used." If VAL is not given, the empty string will be used."
(when (and (not (string? key)) (when (procedure? key)
(not (procedure? key))) (set! key (key)))
(bad-key-type "lazy-assign" (list key))) (unless (string? key)
(when (and (not (string? val)) (set! key (format #f "~a" key)))
(not (procedure? val))) (makevars-set key (delay val))
(bad-value-type "lazy-assign" (list val))) (when %verbose?
(let ((KEY (if (string? key) key (key))) (format #t "~A=~A~%" key val)))
(VAL (if (string? val) val (delay val))))
(unless (string? KEY)
(bad-proc-output "lazy-assign" key))
(makevars-set KEY VAL)
(when (and %verbose? (string? VAL))
(format #t "~A=~A~%" KEY VAL))))
(define-syntax ?= (define-syntax ?=
(lambda (stx) (lambda (stx)
@ -240,21 +237,17 @@ VAL.
If KEY and/or VAL is a thunk, it is immediately evaluated to a If KEY and/or VAL is a thunk, it is immediately evaluated to a
string to use as the key in the hash table entry. string to use as the key in the hash table entry.
If VAL is not given, the empty string will be used." If VAL is not given, the empty string will be used."
(when (and (not (string? key)) (when (procedure? key)
(not (procedure? key))) (set! key (key)))
(bad-key-type "assign" (list key))) (unless (string? key)
(when (and (not (string? val)) (set! key (format #f "~a" key)))
(not (procedure? val))) (when (procedure? val)
(bad-value-type "assign" (list val))) (set! val (val)))
(let ((KEY (if (string? key) key (key))) (unless (string? val)
(VAL (if (string? val) val (val)))) (set! val (format #f "~a" val)))
(unless (string? KEY) (makevars-set key val)
(bad-proc-output "assign" KEY)) (when %verbose?
(unless (string? VAL) (format #t "~A=~A~%" key val)))
(bad-proc-output "assign" VAL))
(makevars-set KEY VAL)
(when %verbose?
(format #t "~A=~A~%" KEY VAL))))
(define-syntax := (define-syntax :=
(lambda (stx) (lambda (stx)
@ -264,8 +257,7 @@ string to use as the key in the hash table entry.
((_ key) ((_ key)
#'(assign (symbol->string (syntax->datum #'key))))))) #'(assign (symbol->string (syntax->datum #'key)))))))
(define* (reference key quoted? #:optional (transformer #f))
(define* (reference key #:optional (transformer #f))
"Looks up KEY in the %makevars hash table. KEY may be a string "Looks up KEY in the %makevars hash table. KEY may be a string
or a procedure that evaluates to a string. or a procedure that evaluates to a string.
If the value of the key If the value of the key
@ -284,33 +276,62 @@ space-separated token in the looked-up value."
(set! key (key)) (set! key (key))
(unless (string? key) (unless (string? key)
(bad-proc-output "reference" key))) (bad-proc-output "reference" key)))
(when (not (string? key))
(set! key (format #t "~a" key)))
(let* ((val&priority (hash-ref %makevars key)) (let* ((val&priority (hash-ref %makevars key))
(val (if (pair? val&priority) (car val&priority) #f)) (val (if (pair? val&priority) (car val&priority) #f))
(priority (if (pair? val&priority) (cdr val&priority) #f))) (priority (if (pair? val&priority) (cdr val&priority) #f)))
(if (not val) (if (not val)
#f (if %strict
(error (format #t "There is no makevar for key ~a~%" key))
;; else
(if quoted?
"\"\""
""))
;; else ;; else
(begin (begin
(when (promise? val) (cond
((promise? val)
(set! val (force val)) (set! val (force val))
(unless (string? val) (cond
(bad-proc-output "reference" val)) ((string? val)
(hash-set! %makevars key (cons val priority)) ;; noop
(when %verbose? #t)
(format #t "~A=~A~%" key val))) ((procedure? val)
(set! val (val)))
(else
(set! val (format #f "~a" val)))))
((string? val)
;; noop
#f)
(else
(set! val (format #f "~a" val))))
(hash-set! %makevars key (cons val priority))
(when %verbose?
(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
(string-tokenize val))))) (string-tokenize val)))))
val)))) (if quoted?
(string-append "\"" val "\"")
val)))))
(define-syntax $ (define-syntax $
(lambda (stx) (lambda (stx)
(syntax-case stx () (syntax-case stx ()
((_ key val) ((_ key transformer)
#'(reference (symbol->string (syntax->datum #'key)) transformer)) #'(reference (symbol->string (syntax->datum #'key)) #f transformer))
((_ key) ((_ key)
#'(reference (symbol->string (syntax->datum #'key))))))) #'(reference (symbol->string (syntax->datum #'key)) #f)))))
(define-syntax Q
(lambda (stx)
(syntax-case stx ()
((_ key transformer)
#'(reference (symbol->string (syntax->datum #'key)) #t transformer))
((_ key)
#'(reference (symbol->string (syntax->datum #'key)) #t)))))
(define (reference-func key) (define (reference-func key)
"Looks up KEY in the %makevars hash table. KEY shall be a string "Looks up KEY in the %makevars hash table. KEY shall be a string
@ -347,3 +368,4 @@ that string."
(syntax-case stx () (syntax-case stx ()
((_ key) ((_ key)
#'(reference-func (symbol->string (syntax->datum #'key))))))) #'(reference-func (symbol->string (syntax->datum #'key)))))))

@ -1,9 +1,11 @@
(define-module (potato rules) (define-module (potato rules)
#:use-module (ice-9 pretty-print) #:use-module (ice-9 pretty-print)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9) #:use-module (srfi srfi-9)
#:use-module (potato exceptions) #:use-module (potato exceptions)
#:use-module (potato builtins) #:use-module (potato builtins)
#:use-module (potato makevars) #:use-module (potato makevars)
#:use-module (potato text)
#:export(<target-rule> #:export(<target-rule>
<suffix-rule> <suffix-rule>
<node> <node>
@ -11,6 +13,7 @@
%suffix-rules %suffix-rules
initialize-rules initialize-rules
first-target-rule-name first-target-rule-name
install-alternate-system-driver
target-rule : target-rule :
suffix-rule -> suffix-rule ->
target-name $@ target-name $@
@ -25,6 +28,18 @@
ignore-error-compose ~- ignore-error-compose ~-
)) ))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GLOBALS
(define %ignore-errors? #f)
(define %continue-on-error? #f)
(define %no-execution? #f)
(define %terse? #f)
(define %verbose? #f)
(define %ascii? #f)
(define %top-level-targets '())
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPER FUNCTIONS ;; HELPER FUNCTIONS
@ -52,10 +67,11 @@ it is evaluated."
(effective-arg #f)) (effective-arg #f))
(cond (cond
((procedure? arg) ((procedure? arg)
(format #t "BLAMMO ~s ~s ~%" arg (arg))
(set! effective-arg (arg)) (set! effective-arg (arg))
#;(unless (string? effective-arg) (unless (string? effective-arg)
(bad-proc-output "~" arg)) (bad-proc-output "~" arg))
) )
((string? arg) ((string? arg)
(set! effective-arg arg)) (set! effective-arg arg))
@ -94,7 +110,7 @@ it is evaluated."
(define (always-execute-compose . args) (define (always-execute-compose . args)
(cons 'always-execute (apply base-compose args))) (cons 'always-execute (apply base-compose args)))
(define ~@ always-execute-compose) (define ~+ always-execute-compose)
(define (regular-file? filename) (define (regular-file? filename)
(let ((st (stat filename #f))) (let ((st (stat filename #f)))
@ -105,6 +121,15 @@ it is evaluated."
(+ (* 1000000000 (stat:mtime st)) (+ (* 1000000000 (stat:mtime st))
(stat:mtimensec st)))) (stat:mtimensec st))))
(define %system-proc system)
(define (install-alternate-system-driver proc)
"Give a procure to use rather than the standard
'system' procedure."
(unless (procedure? proc)
(not-a-procedure "install-alternate-system-driver" proc))
(set! %system-proc proc))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TARGET STRUCT AND METHODS ;; TARGET STRUCT AND METHODS
@ -128,10 +153,25 @@ it is evaluated."
(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"
;; FIXME: Typecheck
(let ((rule (make-target-rule name prerequisites recipes 1))) (when %verbose?
;; Add to %target-rules (format #t "Defining target rule: ~A ~A ~A~%" prerequisites (right-arrow) name))
(set! %target-rules (cons rule %target-rules)))) ;; If any recipes are raw strings, we need to make them into
;; (cons 'default string)
(let ((recipes2
(map (lambda (recipe)
(cond
((pair? recipe)
recipe)
((string? recipe)
(cons 'default recipe))
(else
(error "bad recipe for target rule"))))
recipes)))
(let ((rule (make-target-rule name prerequisites recipes2 1)))
;; Add to %target-rules
(set! %target-rules (cons rule %target-rules)))))
;; Alias ;; Alias
(define : target-rule) (define : target-rule)
@ -166,9 +206,25 @@ it is evaluated."
(define (suffix-rule source target . recipes) (define (suffix-rule source target . recipes)
"Register a suffix rule" "Register a suffix rule"
;; FIXME: Typecheck ;; FIXME: Typecheck
(let ((rule (make-suffix-rule source target recipes 1))) (when %verbose?
(set! %suffix-rules (cons rule %suffix-rules)))) (format #t "Defining suffix rule: ~A ~A ~A~%" source (right-arrow) target))
;; If any recipes are raw strings, we need to make them into
;; (cons 'default string)
(let ((recipes2
(map (lambda (recipe)
(cond
((pair? recipe)
recipe)
((string? recipe)
(cons 'default recipe))
(else
(error "bad recipe for target rule"))))
recipes)))
(let ((rule (make-suffix-rule source target recipes 1)))
(set! %suffix-rules (cons rule %suffix-rules)))))
;; Alias ;; Alias
(define -> suffix-rule) (define -> suffix-rule)
@ -183,14 +239,14 @@ it is evaluated."
;; just a label. ;; just a label.
(name node-get-name node-set-name!) (name node-get-name node-set-name!)
;; A <node> which is the parent of this node, or #f. ;; A <node> which is the parent of this node, or #f.
(parent note-get-parent note-set-parent!) (parent node-get-parent node-set-parent!)
;; If 'name' is a regular file, mtime holds its last modification ;; If 'name' is a regular file, mtime holds its last modification
;; time in nanoseconds since the epoch. If 'name' does not exist, ;; time in nanoseconds since the epoch. If 'name' does not exist,
;; _mtime is #f. ;; _mtime is #f.
(mtime node-get-mtime node-set-mtime!) (mtime node-get-mtime node-set-mtime!)
;; One of 'pass, 'fail, or 'undetermined ;; One of 'pass, 'fail, or 'undetermined
(status node-get-status node-set-status!) (status node-get-status node-set-status!)
;; A list of rules to evaluate to try to c ;; A list of rules
(rules node-get-rules node-set-rules!) (rules node-get-rules node-set-rules!)
(children node-get-children node-set-children!) (children node-get-children node-set-children!)
;; Determines how many children must pass for the parent ;; Determines how many children must pass for the parent
@ -198,60 +254,51 @@ it is evaluated."
(logic node-get-logic node-set-logic!) (logic node-get-logic node-set-logic!)
) )
(define (has-rules? node)
(not (null? (node-get-rules node))))
;;;;;;;;; (define (set-fail! node)
;; Automatic variables (node-set-status! node 'fail))
;; target-name $@
;; target-basename $*
;; newer-prerequisites $?
;; prerequisites $^
;; primary-prerequisite $<
(define (set-pass! node)
(node-set-status! node 'pass))
;; This is set in the builder to make automatic variables work. (define (failed? node)
(define %node-cur #f) (eqv? (node-get-status node) 'fail))
(define target-name (define (passed? node)
(lambda () (eqv? (node-get-status node) 'pass))
(if %node-cur
(node-get-name %node-cur)
#f)))
(define $@ target-name)
(define target-basename
(lambda ()
(if %node-cur
(basename (node-get-name %node-cur)))))
(define $* target-basename)
(define primary-prerequisite
(lambda ()
(if %node-cur
(let ((prereq (node-get-children %node-cur)))
(if (null? prereq)
""
(car prereq))))))
(define $< primary-prerequisite)
(define newer-prerequisites
(lambda ()
(error "FIXME")))
(define $? newer-prerequisites)
(define prerequisites
(lambda ()
(error "FIXME")))
(define $^ prerequisites)
(define (leaf-node? node)
(null? (node-get-children node)))
(define (undetermined? node) (define (undetermined? node)
(eq? (node-get-status node) 'undetermined)) (eq? (node-get-status node) 'undetermined))
(define (any-child-has-passed? node)
(let ((children (node-get-children node)))
(if (null? children)
#f
(any passed? children))))
(define (every-child-has-passed? node)
(let ((children (node-get-children node)))
(if (null? children)
#f
(every passed? children))))
(define (any-child-has-failed? node)
(let ((children (node-get-children node)))
(if (null? children)
#f
(any failed? children))))
(define (every-child-has-failed? node)
(let ((children (node-get-children node)))
(if (null? children)
#f
(every failed? children))))
(define (children-complete? node) (define (children-complete? node)
(cond (cond
((leaf-node? node) ((leaf-node? node)
@ -276,32 +323,26 @@ it is evaluated."
(else (else
#f))) #f)))
(define (has-recipe? node)
(not (null? (node-get-recipe node))))
(define (set-fail! node)
(node-set-status! node 'fail))
(define (set-pass! node)
(node-set-status! node 'fail))
(define (get-next-child node) (define (get-next-child node)
"Return the first child node that is not yet PASS or FAIL" "Return the first child node that is not yet PASS or FAIL"
(let ((children (node-get-children node))) (let ((children (node-get-children node)))
(any (lambda (child) (if (null? children)
(if (eqv? (node-get-status child) 'undetermined) #f
child
#f)) (any (lambda (child)
children))) (if (eqv? (node-get-status child) 'undetermined)
child
(define (failed? node) #f))
(eqv? (node-get-status node) 'fail)) children))))
(define (has-parent? node) (define (has-parent? node)
(if (node-get-parent node) (if (node-get-parent node)
#t #t
#f)) #f))
(define (get-parent node)
(node-get-parent node))
(define (up-to-date? node) (define (up-to-date? node)
"Checks if node is up to date: "Checks if node is up to date:
- it has an mtime - it has an mtime
@ -317,247 +358,369 @@ it is evaluated."
#t #t
#f))) #f)))
(define (get-parent node) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(node-get-parent node)) ;; AUTOMATIC VARIABLES
#|
(define (run-recipe! node quiet verbose)
"Runs the recipes associated with this node, one by one. Recipes
are either strings, procedures that return strings, or generic
procedures. If a failure condition happens, mark the node as having
failed."
(let ((recipes (node-get-recipes node)))
(when (null? recipes)
(error "no recipe"))
(let loop ((opt/recipe (car recipes))
(rest (cdr recipes)))
(let ((opt ((car recipe/opt))
(recipe (cdr recipe)))
;; Recipes are either
;; - strings to pass to system
;; - procedures that return a string which is passed
;; to system
;; - procedures (that don't return a string) that are executed
;; that pass unless they return #f
; OPT is one of 'default, 'ignore, 'silent
(cond
((string=? recipe)
(unless (or quiet (eq? opt 'silent))
(format #t "[SYSTEM] ~A~$" recipe)
(let ((retval (system recipe)))
(unless (or quiet (eq? opt 'silent))
(format #t " --> ~S~%" (status:exit-val retval)))
(when (and (not (eqv? opt 'ignore))
(or (eqv? #f (status:exit-val retval))
(not (zero? (status:exit-val retval)))))
(node-set-status! node 'fail))))
((procedure? recipe)
(unless (or quiet (eq? opt 'silent))
(if (procedure-name recipe)
(format #t "[PROC] ~A~%" (procedure-name recipe))
(format #t "[PROC] ~%")))
(let ((retval (recipe)))
(unless (or quiet (eq? opt 'silent))
(format #t " --> ~S~%" (status:exit-val retval2)))
(cond
((eqv? retval #f)
(unless (eqv? opt 'ignore)
(node-set-status node 'fail)))
((string=? retval)
(unless (or quiet (eq? opt 'silent))
(format #t "[SYSTEM] ~A~$" retval)
(let ((retval2 (system retval)))
(unless (or quiet (eq? opt 'silent))
(format #t " --> ~S~%" (status:exit-val retval2)))
(when (and (not (eqv? opt 'ignore))
(or (eqv? #f (status:exit-val retval))
(not (zero? (status:exit-val retval)))))
(node-set-status! node 'fail))))))))
(cond
((eqv? (node-get-status node) 'fail)
;; quit
)
((null? rest)
(node-set-status! node) 'pass)
(else
((loop (car rest)
(cdr rest))))))))))
(when (eq? 'pass (node-get-status node))
(let ((name (node-get-name node)))
(when (and (file-exists? name)
(regular-file? name))
(node-set-mtime! node (compute-mtime name))))))
(define (run-default-recipe! node) (define target-name 'unspecified)
"The default recipe passes if the file exists" (define target-basename 'unspecified)
(let ((name (node-get-name node))) (define prerequisites '())
(if (and (file-exists? name) (define primary-prerequisite 'unspecified)
(regular-file? name)) (define newer-prerequisites '())
(begin
(node-set-status! node 'pass)
(node-set-mtime! node (compute-mtime name)))
;; else
(node-set-status! node 'fail))))
;; Start at root (define $@ (lambda () target-name))
(define $* (lambda () target-basename))
(define $< (lambda () primary-prerequisite))
(define $$? (lambda () newer-prerequisites))
(define $? (lambda () (apply string-compose newer-prerequisites)))
(define $$^ (lambda () prerequisites))
(define $^ (lambda () (apply string-compose prerequisites)))
;; If cur is UNDETERMINED, find a leaf that is UNDETERMINED. (define (target-rule-prep-automatic-variables node rule)
;; Descent to first leaf. (set! target-name (node-get-name node))
;; If self's mtime is earlier than parent's mtime, mark self as PASS. (set! target-basename (basename target-name))
;; Elif self has rules, run rules and mark self PASS/FAIL. (set! prerequisites (target-rule-get-prerequisites rule))
;; Else self has no mtime or rules, so mark self FAIL. (set! primary-prerequisite (if (null? prerequisites) #f (car prerequisites)))
;; Go to parent. (set! newer-prerequisites
;; If this node doesn't have a real file attached, then all
;; prerequistes are "newer".
(if (not (node-get-mtime node))
prerequisites
;; Prerequisites that have no mtime or a higher mtime are
;; "newer".
(filter-map
(lambda (name)
(cond
((and (file-exists? name)
(regular-file? name)
(>= (node-get-mtime node) (compute-mtime name)))
name)
((not (file-exists? name))
name)
(else
#f)))
prerequisites))))
;; IF PASS or FAIL, go to parent (define (suffix-rule-prep-automatic-variables node rule)
(set! target-name (node-get-name node))
;; IF UNDETERMINED do these... (set! target-basename (basename target-name))
(set! primary-prerequisite (string-append target-basename (suffix-rule-get-source rule)))
;; Are we done with the children? (set! prerequisites (list primary-prerequisite))
;; If AND rule and one child is FAIL, stop (set! newer-prerequisites
;; If OR rule and one child is PASS, stop ;; If this node doesn't have a real file attached, then the
;; If no children left, stop ;; prerequisite is newer.
;; Else keep going (if (not (node-get-mtime node))
(list primary-prerequisite)
;; Did the children pass? ;; Prerequisites that have no mtime or a higher mtime are
;; IF AND rule and all children are PASS, true ;; "newer".
;; IF OR rule an one child is PASS, true (cond
;; Otherwise, false ((and (file-exists? primary-prerequisite)
(regular-file? primary-prerequisite)
;; If the children FAIL, cur is FAIL (> (node-get-mtime node) (compute-mtime primary-prerequisite)))
;; If the children PASS, run rules and mark self PASS/FAIL (list primary-prerequisite))
;; Go to parent (else
'())))))
;; 3 failures
;; - If anything fails, stop immediately
;; - If anything fails, searching through tree
;; - Ignore errors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LET'S GO! ;; MIXED METHODS
;; requiring more than one of node, automatic variables, suffix rules
;; and target rules
(define %debug? #f) (define (add-builtins)
(-> ".c" ""
(~ ($ CC) ($ CFLAGS) ($ LDFLAGS) "-o" $@ $<))
(-> ".f" ""
(~ ($ FC) ($ FFLAGS) ($ LDFLAGS) "-o" $@ %<))
(-> ".sh" ""
(~ "cp" $< $@)
(~ "chmod a+x" $< $@))
(-> ".c" ".o"
(~ ($ CC) ($ CFLAGS) "-c" $<))
(-> ".f" ".o"
(~ ($ FC) ($ FFLAGS) ",c" $<))
(-> ".y" ".o"
(~ ($ YACC) ($ YFLAGS) $<))
(-> ".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) ($ LDFLAGS) $<)
(~ "mv lex.yy.c" $@))
(-> ".scm" ".go"
(~ ($ GUILD) "compile" ($ GFLAGS) $<)))
(define (initialize-rules no-builtins? debug?) (define (run-rules! node)
(set! %target-rules '()) "Runs the rules associated with this node. If this node has *and*
(set! %suffix-rules '()) logic, it runs the recipes one by one, quitting on the first failure.
(set! %debug? debug?) If this rule has *or* logic, it runs them quitting on the first
(unless no-builtins? success."
;; Set up initial suffix rules (let ((rules (node-get-rules node)))
(suffix-rule ".c" ".o" (when (null? rules)
(string-compose (error "no rules"))
(reference "CC")
(reference "CFLAGS")
"-c"
primary-prerequisite)))
)
(define (create-node name parent) (let ((logic (node-get-logic node)))
"Constructs a tree of nodes, with name as the root node." (let loop ((rule (car rules))
(let ((node (make-node name parent 'untested))) (rest (cdr rules)))
(cond
((target-rule? rule)
(target-rule-prep-automatic-variables node rule)
(run-recipes! node (target-rule-get-recipes rule)))
((suffix-rule? rule)
(suffix-rule-prep-automatic-variables node rule)
(run-recipes! node (suffix-rule-get-recipes rule)))
(else
(error "bad rule")))
(let ((status (node-get-status node)))
(cond
((or (and (eq? 'and logic)
(eq? 'fail status))
(and (eq? 'or logic)
(eq? 'pass status))
(null? rest))
;; We're done
status)
(else
(loop (car rest) (cdr rest)))))))))
;; FIXME: here check that this name differs from all ancenstor's (define (run-recipes! node recipes)
;; names "Runs the recipes on this node, one by one. Recipes are either
strings, procedures that return strings, or generic procedures. If a
;; Try to the file's modification time. failure condition happens, mark the node as having failed."
(when (file-exists? name) (when (null? recipes)
(when (not (regular-file? name)) (error "no recipe"))
(not-a-regular-file "create-node" name)) (let loop ((opt/recipe (car recipes))
(when (not (access? name R_OK)) (rest (cdr recipes)))
(no-read-access-to-file "create-node" name)) (let ((opt (car opt/recipe))
(node-set-mtime! node (compute-mtime name))) (recipe (cdr opt/recipe)))
;; Recipes are either
;; - strings to pass to system
;; - procedures that return a string which is passed
;; to system
;; - procedures (that don't return a string) that are executed
;; that pass unless they return #f
;; OPT is one of 'default, 'ignore, 'silent
(cond
((string? recipe)
(format #t "[SYSTEM] ~A~%" recipe)
(let ((retval (%system-proc recipe)))
(format #t " --> ~S~%" retval)
(unless (zero? retval)
(set-fail! node))))
((procedure? recipe)
(if (procedure-name recipe)
(format #t "[PROC] ~A~%" (procedure-name recipe))
(format #t "[PROC] ~%"))
(let ((retval (recipe)))
(format #t " --> ~S~%" retval)
(cond
((eqv? retval #f)
(set-fail! node))
;; If a procedure returns a string, that string gets
;; processed by system.
((string=? retval)
(format #t "[SYSTEM] ~A~%" retval)
(let ((retval2 (%system-proc retval)))
(format #t " --> ~S~%" retval2)
(unless (zero? retval2)
(set-fail! node)))))))
(else
;; Not a string or procedure
(error "bad rule")))
;; Search for matching target rule.
(when (not (null? %target-rules)) (cond
(let loop ((rule (car %target-rules)) ((failed? node)
(rest (cdr %target-rules))) ;; quit
)
((null? rest)
(set-pass! node))
(else
((loop (car rest)
(cdr rest)))))))
(when (passed? node)
(let ((name (node-get-name node)))
(when (and (file-exists? name)
(regular-file? name))
(node-set-mtime! node (compute-mtime name))))))
;; N.B: here we assume target rule names and (define (run-default-recipe! node)
;; predicates are exclusively strings. "The default recipe passes if the file exists"
(if (string=? name (target-rule-get-name rule)) (let ((name (node-get-name node)))
(begin (if (and (file-exists? name)
;; OK we have a matching rule (regular-file? name))
(node-set-rules! node (list rule)) (begin
(node-set-logic! node 'and) (set-pass! node)
;; For target-rules, the prerequisites comes from the (node-set-mtime! node (compute-mtime name)))
;; rule itself. ;; else
(set-fail! node))))
;; Oooh, recursion! ;; Start at root
(node-set-children! node
(map (lambda (prereq)
(create-node prereq node))
(target-rule-get-prerequisites rule))))
;; else
(if (not (null? rest))
(loop (car rest) (cdr rest))
;; else, no matching rule found
(node-set-rules! node '())))))
#| ;; If cur is UNDETERMINED, find a leaf that is UNDETERMINED.
;; If no rule found so far, search for suffix rules. ;; Descent to first leaf.
(when (null? (node-get-rules node)) ;; If self's mtime is earlier than parent's mtime, mark self as PASS.
(for-each ;; Elif self has rules, run rules and mark self PASS/FAIL.
(lambda (rule) ;; Else self has no mtime or rules, so mark self FAIL.
(let ((targ (suffix-rule-get-target rule))) ;; Go to parent.
(when (or
;; string suffix
(and (string? targ)
(string-suffix? targ name))
;; procedure suffix
(and (procedure? targ)
(targ name)))
;; For suffix rules, there will be exactly one child per
;; rule and the name of the child is constructed from a
;; suffix and the parent's name.
(node-set-rules! node (cons rule (node-get-rules node)))
(node-set-logic! node 'or)
(let* ((src (suffix-rule-get-source rule))
(prereq
(if (string? src)
(string-append
(string-drop-right name (string-length src))
src)
;; else, src is a conversion func.
(src name))))
;; Note the recursion here.
(node-set-children! node
(cons (create-node prereq node)
(node-get-children node)))))))
%suffix-rules))
;; First matching rule has highest priority ;; IF PASS or FAIL, go to parent
(node-set-rules! node (reverse (node-get-rules node)))
(node-set-children! node (reverse (node-get-children node)))
|#
;; And node is ready to go
node))
(define (build root) ;; IF UNDETERMINED do these...
"Give a tree of <node>, this executes the recipes therein."
(let ((tree (create-node root #f))) ;; Are we done with the children?
(let ((node root)) ;; If AND rule and one child is FAIL, stop
(while #t ;; If OR rule and one child is PASS, stop
(if (undetermined? node) ;; If no children left, stop
;; Else keep going
;; Did the children pass?
;; IF AND rule and all children are PASS, true
;; IF OR rule an one child is PASS, true
;; Otherwise, false
;; If the children FAIL, cur is FAIL
;; If the children PASS, run rules and mark self PASS/FAIL
;; Go to parent
;; 3 failures
;; - If anything fails, stop immediately
;; - If anything fails, searching through tree
;; - Ignore errors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LET'S GO!
(define (initialize-rules targets builtins? ignore-errors? continue-on-error? no-execution? terse? verbose? ascii?)
(set! %target-rules '())
(set! %suffix-rules '())
(set! %top-level-targets targets)
(set! %ignore-errors? ignore-errors?)
(set! %continue-on-error? continue-on-error?)
(set! %no-execution? no-execution?)
(set! %terse? terse?)
(set! %verbose? verbose?)
(set! %ascii? ascii?)
(when builtins?
(add-builtins)))
(define (create-node name parent)
"Constructs a tree of nodes, with name as the root node."
(let ((node (make-node name parent 'undetermined)))
(node-set-children! node '())
;; FIXME: here check that this name differs from all ancenstor's
;; names
;; Try to the file's modification time.
(when (file-exists? name)
(when (not (regular-file? name))
(not-a-regular-file "create-node" name))
(when (not (access? name R_OK))
(no-read-access-to-file "create-node" name))
(node-set-mtime! node (compute-mtime name)))
;; Search for matching target rule.
(when (not (null? %target-rules))
(let loop ((rule (car %target-rules))
(rest (cdr %target-rules)))
;; N.B: here we assume target rule names and
;; predicates are exclusively strings.
(if (string=? name (target-rule-get-name rule))
(begin
;; OK we have a matching rule
(node-set-rules! node (list rule))
(node-set-logic! node 'and)
;; For target-rules, the prerequisites comes from the
;; rule itself.
;; Oooh, recursion!
(node-set-children! node
(map (lambda (prereq)
(create-node prereq node))
(target-rule-get-prerequisites rule))))
;; else
(if (not (null? rest))
(loop (car rest) (cdr rest))
;; else, no matching rule found
(node-set-rules! node '())))))
#|
;; If no rule found so far, search for suffix rules.
(when (null? (node-get-rules node))
(for-each
(lambda (rule)
(let ((targ (suffix-rule-get-target rule)))
(when (or
;; string suffix
(and (string? targ)
(string-suffix? targ name))
;; procedure suffix
(and (procedure? targ)
(targ name)))
;; For suffix rules, there will be exactly one child per
;; rule and the name of the child is constructed from a
;; suffix and the parent's name.
(node-set-rules! node (cons rule (node-get-rules node)))
(node-set-logic! node 'or)
(let* ((src (suffix-rule-get-source rule))
(prereq
(if (string? src)
(string-append
(string-drop-right name (string-length src))
src)
;; else, src is a conversion func.
(src name))))
;; Note the recursion here.
(node-set-children! node
(cons (create-node prereq node)
(node-get-children node)))))))
%suffix-rules))
;; First matching rule has highest priority
(node-set-rules! node (reverse (node-get-rules node)))
(node-set-children! node (reverse (node-get-children node)))
|#
;; And node is ready to go
node))
(define (build root)
"Give a tree of <node>, this executes the recipes therein."
(let ((tree (create-node root #f)))
(let ((node tree))
(while #t
(if (undetermined? node)
(begin
(if (children-complete? node) (if (children-complete? node)
(if (children-passed? node) (if (children-passed? node)
(if (up-to-date? node) (if (up-to-date? node)
(set-pass! node) (set-pass! node)
;; else, not up to date ;; else, not up to date
(if (has-recipe? node) (if (has-rules? node)
(run-recipe! node) (run-rules! node)
;; else, no recipe exists ;; else, no recipe exists
(run-default-recipe! node))) (run-default-recipe! node)))
;; else, children have failed ;; else, children have failed
(set-fail! node)) (set-fail! node))
;; else, children aren't complete ;; else, children aren't complete
(set! node (get-next-child node))) (set! node (get-next-child node))))
;; else, this node is determined ;; else, this node is determined
(if (and abort-on-error (failed? node)) (begin
(if (and (not %ignore-errors?) (failed? node))
(break) (break)
;; else not failed ;; else not failed
(if (has-parent? node) (if (has-parent? node)
(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
(break)))))))) (break)))))))))
|#

7
raw-tests.scm Normal file → Executable file

@ -5,8 +5,9 @@ exec guile -L . -s "$0" "$@"
(use-modules (potato make) (use-modules (potato make)
(srfi srfi-1)) (srfi srfi-1))
(initialize '("test" "foo.exe" "--verbose"))
(: "foo.exe" '("foo.c")
"cc -o foo.exe foo.c")
(execute)
(initialize #:arguments (command-line))
(write %opt-verbose) (newline)
(write %opt-quiet) (newline)

342
tests.scm

@ -6,12 +6,344 @@ exec guile -L . -s "$0" "$@"
(srfi srfi-1) (srfi srfi-1)
(srfi srfi-64)) (srfi srfi-64))
(test-begin "initialization")
(test-assert "initialize doesn't set verbose flag" ;; These stubs override the driver that calls
;; 'system' so we can instead just investigate
;; what string it was passed.
(define %cmd #f)
(define (stub-system-pass cmd)
(set! %cmd cmd)
0)
(define (stub-system-fail cmd)
(set! %cmd cmd)
1)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MAKEVARS
(test-begin "makevars")
(test-equal "can set makevar with environment"
"BAR1"
(begin (begin
(initialize #:arguments '() #:environ #f) (setenv "FOO1" "BAR1")
(not (%opt-verbose?)))) (initialize '("test" "--environment"))
(let ((result ($ FOO1)))
(unsetenv "FOO1")
result)))
(test-end "initialization") (test-equal "can set makevar with MAKEFLAGS"
"BAR2"
(begin
(setenv "MAKEFLAGS" "FOO2=BAR2")
(initialize '("test" "--environment"))
(let ((result ($ FOO2)))
(unsetenv "MAKEFLAGS")
result)))
(test-equal "can set makevar with initialize"
"BAR3"
(begin
(initialize '("test" "FOO3=BAR3"))
($ FOO3)))
(test-equal "can set makevar in script"
"BAR4"
(begin
(:= FOO4 "BAR4")
($ FOO4)))
(test-equal "can set makevar lazily in script"
"BAR5"
(begin
(?= FOO5 "BAR5")
($ FOO5)))
(test-assert "a lazy makevar of a procedure is a promise before it is referenced"
(begin
(?= FOO6 (lambda () "BAR6"))
(let ((val (hash-ref (@@ (potato makevars) %makevars) "FOO6")))
(promise? (car val)))))
(test-equal "a lazy makevar of a procedure is a string after it is referenced"
"BAR7"
(begin
(?= FOO7 (lambda () "BAR7"))
($ FOO7)
(let ((val (hash-ref (@@ (potato makevars) %makevars) "FOO7")))
(car val))))
(test-equal "referencing an unset makevar returns an empty string"
""
($ FOO8))
(test-error "referencing an unset makevar throws an error in strict mode"
#t
(begin
(initialize '("test" "--strict"))
($ FOO9)))
(test-equal "assign converts integers to strings"
"100"
(begin
(:= FOO10 100)
($ FOO10)))
(test-equal "assign converts characters to strings"
"x"
(begin
(:= FOO11 #\x)
($ FOO11)))
(test-equal "quote-reference adds quotation marks"
"\"BAR 12\""
(begin
(:= FOO12 "BAR 12")
(Q FOO12)))
(test-equal "quote-reference of an unassigned makevar returns empty quotation marks in non-strict mode"
"\"\""
(begin
(initialize '("test"))
(Q FOO13)))
(test-error "quote-reference of an unassigned makevar throws an error in strict mode"
#t
(begin
(initialize '("test" "--strict"))
(Q FOO13)))
(test-equal "script assignment overrides command-line assignment"
"BAZ14"
(begin
(initialize '("test" "FOO14=BAR14"))
(:= FOO14 "BAZ14")
($ FOO14)))
(test-equal "script assignment overrides MAKEFLAGS assignment"
"BAZ15"
(begin
(setenv "MAKEFLAGS" "FOO15=BAR15")
(initialize '("test" "--environment"))
(:= FOO15 "BAZ15")
($ FOO15)))
(test-equal "script assignment overrides environment assignment"
"BAZ16"
(begin
(setenv "FOO16" "BAR16")
(initialize '("test" "--environment"))
(unsetenv "FOO16")
(:= FOO16 "BAZ16")
($ FOO16)))
(test-equal "command-line assignment overrides script assignment in elevate mode"
"BAR14"
(begin
(initialize '("test" "FOO14=BAR14" "--elevate-environment"))
(:= FOO14 "BAZ14")
($ FOO14)))
(test-equal "MAKEFLAGS assignment overrides script assignment in elevate mode"
"BAR15"
(begin
(setenv "MAKEFLAGS" "FOO15=BAR15")
(initialize '("test" "--elevate-environment"))
(unsetenv "MAKEFLAGS")
(:= FOO15 "BAZ15")
($ FOO15)))
(test-equal "environment assignment overrides script assignment in elevate mode"
"BAR16"
(begin
(setenv "FOO16" "BAR16")
(initialize '("test" "--elevate-environment"))
(:= FOO16 "BAZ16")
($ FOO16)))
(test-end "makevars")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; RECIPE HELPERS
(test-begin "recipe_helpers")
(test-assert "string-compose returns 'default and procedure"
(let ((ret (~ "hello")))
(and
(eq? (car ret) 'default)
(procedure? (cdr ret)))))
(test-assert "silent-compose returns 'silent and procedure"
(let ((ret (~@ "hello")))
(and
(eq? (car ret) 'silent)
(procedure? (cdr ret)))))
(test-assert "always-execute-compose returns 'always-execute and procedure"
(let ((ret (~+ "hello")))
(and
(eq? (car ret) 'always-execute)
(procedure? (cdr ret)))))
(test-assert "ignore-error-compose returns 'ignore-error and procedure"
(let ((ret (~- "hello")))
(and
(eq? (car ret) 'ignore-error)
(procedure? (cdr ret)))))
(test-equal "string-compose string passthrough"
"hello"
(let ((ret (~ "hello")))
((cdr ret))))
(test-equal "string-compose two strings passthrough"
"hello world"
(let ((ret (~ "hello" "world")))
((cdr ret))))
(test-equal "string-compose empty initial string"
"world"
(let ((ret (~ "" "world")))
((cdr ret))))
(test-equal "string-compose empty terminal string"
"hello"
(let ((ret (~ "hello" "")))
((cdr ret))))
(test-equal "string-compose empty medial string"
"hello world"
(let ((ret (~ "hello" "" "world")))
((cdr ret))))
(test-equal "string-compose handles procedure"
"hello world"
(let ((ret (~ "hello" (lambda () "world"))))
((cdr ret))))
(test-equal "string-compose handles integer"
"hello 123"
(let ((ret (~ "hello" 123)))
((cdr ret))))
(test-equal "string-compose handles character"
"hello w"
(let ((ret (~ "hello" #\w)))
((cdr ret))))
(test-equal "string-compose handles makevar"
"hello BAR"
(begin
(:= FOO "BAR")
(let ((ret (~ "hello" ($ FOO))))
((cdr ret)))))
(test-equal "empty string-compose"
""
(let ((ret (~)))
((cdr ret))))
(test-end "recipe_helpers")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TARGET RULES
(test-begin "target_rules")
(test-assert "install alternate system driver"
(false-if-exception (install-alternate-system-driver stub-system-pass)))
(test-equal "target rule is a string"
"cc -o foo.exe foo.c"
(begin
(initialize '("test" "foo.exe"))
(: "foo.exe" '("foo.c")
"cc -o foo.exe foo.c")
(execute)
%cmd))
(test-assert "target rule is a procedure"
(begin
(let ((tmpvar #f))
(initialize '("test" "foo.exe"))
(: "foo.exe" '("foo.c")
(lambda ()
(set! tmpvar #t)))
(execute)
tmpvar)))
(test-equal "target rule is a procedure returning a string"
"cc -o foo.exe foo.c"
(begin
(initialize '("test" "foo.exe"))
(: "foo.exe" '("foo.c")
(lambda ()
("cc -o foo.exe foo.c")))
(execute)
%cmd))
(test-equal "target rule using string-compose on a string"
"cc -o foo.exe foo.c"
(begin
(initialize '("test" "foo.exe"))
(: "foo.exe" '("foo.c")
(~ "cc -o foo.exe foo.c"))
(execute)
%cmd))
(test-equal "target rule using string-compose on special variables"
"cc -o foo.exe foo.c"
(begin
(initialize '("test" "foo.exe"))
(: "foo.exe" '("foo.c")
(~ "cc -o" $@ $<))
(execute)
%cmd))
(test-equal "target rule check success"
#t
(begin
(initialize '("test" "foo.exe"))
(: "foo.exe" '("foo.c")
(~ "cc -o" $@ $<))
(execute)))
(test-assert "install failing alternate system driver"
(false-if-exception (install-alternate-system-driver stub-system-fail)))
(test-equal "target rule check failure of system call"
#f
(begin
(initialize '("test" "foo.exe"))
(: "foo.exe" '("foo.c")
(~ "cc -o" $@ $<))
(execute)))
(test-equal "target rule check failure of scheme procedure"
#f
(begin
(initialize '("test" "foo.exe"))
(: "foo.exe" '("foo.c")
(lambda ()
#f))
(execute)))
(test-end "target_rules")
(test-begin "suffix_rules")
(test-assert "install alternate system driver"
(false-if-exception (install-alternate-system-driver stub-system-pass)))
(test-equal "suffix rule simple"
"cc -c foo.c"
(begin
(initialize '("test" "foo.o"))
(-> ".c" ".o"
(~ "cc -c" $<))
(execute)
%cmd))
(test-end "suffix_rules")