diff --git a/CHEATSHEET.md b/CHEATSHEET.md index 16b3691..72c4fba 100644 --- a/CHEATSHEET.md +++ b/CHEATSHEET.md @@ -13,7 +13,7 @@ Add this at the top of your build script. Add this at the bottom of your build script - (build) + (execute) The rules go in between `initialize` and `build` diff --git a/README.md b/README.md index fa2fbbb..d8ace2d 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,7 @@ is named `makefile.scm`; however, you may choose any name. !# (use-modules (potato make)) - (setup (command-line)) - + (initialize) This boilerplate loads the library functions and it parses the 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' -a, --ascii use ASCII-only output and no colors + -W, --warn + enable warning messages [var=value...] 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 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 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` 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 -> returned. +> 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. 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 > procedure is *forced* to return a string. Also, the value in the diff --git a/doodle.txt b/doodle.txt index 900f0b6..6d8ca39 100644 --- a/doodle.txt +++ b/doodle.txt @@ -2,3 +2,9 @@ U+220E END OF PROOF U+227A PRECEDES U+227B SUCCEEDS + +a.out +→ foo.o [PASS] +-> bar.o [PASS] + +a.out ≺ diff --git a/potato/exceptions.scm b/potato/exceptions.scm index 829ed2a..e147da6 100644 --- a/potato/exceptions.scm +++ b/potato/exceptions.scm @@ -5,6 +5,7 @@ bad-proc-output invalid-macro not-a-regular-file + not-a-procedure no-read-access-to-file )) @@ -58,6 +59,16 @@ (define (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) (make-exception (make-programming-error) diff --git a/potato/make.scm b/potato/make.scm index ae30335..3c305dc 100644 --- a/potato/make.scm +++ b/potato/make.scm @@ -9,12 +9,11 @@ #:use-module (potato rules) #:use-module (potato text) #:export (initialize - execute - ) + execute) #:re-export (%suffix-rules lazy-assign ?= assign := - reference $ + reference $ Q reference-func $$ target-rule : suffix-rule -> @@ -23,10 +22,15 @@ newer-prerequisites $? prerequisites $^ primary-prerequisite $< - compose ~ + string-compose ~ + silent-compose ~@ + always-execute-compose ~+ + ignore-error-compose ~- + install-alternate-system-driver )) (define %version "1.0") +(define %debug-argv0 #f) ;; #:re-export ( ;; lazy-assign ?= @@ -58,7 +62,7 @@ ;; If the -t option was specified, make shall write to standard ;; output a message for each file that was touched. -(define %opt-quiet #f) +(define %opt-terse #f) (define %opt-verbose #f) (define %opt-ignore-errors #f) (define %opt-continue-on-error #f) @@ -68,7 +72,7 @@ (define (critical spec . args) (apply format (append (list #t spec) args))) (define (print spec . args) - (unless %opt-quiet + (unless %opt-terse (apply format (append (list #t spec) args)))) (define (debug spec . args) (when %opt-verbose @@ -77,7 +81,7 @@ (define option-spec '((help (single-char #\h) (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)) (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)) (no-execution (single-char #\n) (value #f)) (ascii (single-char #\A) (value #f)) + (strict (single-char #\S) (value #f)) )) (define (display-help-and-exit argv0) (format #t "~A [-hvqVeEbn] [KEY=VALUE ...] [targets ...]~%" argv0) (format #t " -h, --help print help and exit~%") (format #t " -v, --version print version and exit~%") - (format #t " -q, --quiet print minimal output~%") - (format #t " -V, --verbose print maximum output~%") + (format #t " -q, --terse use terse output~%") + (format #t " -V, --verbose use verbose output~%") (format #t " -e, --environment use environment variables~%") (format #t " -E, --elevate-environment~%") (format #t " use environment variables and let~%") @@ -101,13 +106,15 @@ (format #t " -b, --builtins~%") (format #t " include some common variables and suffix rules~%") (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 " keep building even if commands fail~%") + (format #t " after an error, keep building other targets~%") (format #t " -n, --no-execution~%") (format #t " only execute rules marked as 'always execute'~%") (format #t " -a, --ascii~%") (format #t " ASCII only output and no colors~%") + (format #t " -S, --strict~%") + (format #t " causes some behaviours to throw errors~%") (exit 0)) (define (display-version-and-exit argv0) @@ -120,7 +127,7 @@ of pairs of KEY VAL" (filter-map (lambda (str) - (let ((tok (string-split str #\x))) + (let ((tok (string-split str #\=))) (cond ((= 1 (length tok)) #f) @@ -141,15 +148,20 @@ return them in a list." lst)) (define* (initialize #:optional - (arguments '())) + (arguments #f)) "Set up the options, rules, and makevars. If ARGUMENTS is not set, it will use options, makevars, and targets as specified by the command line. If it is set, it is expected to be a list of strings that are command-line 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) - (set! arguments (program-arguments))) + (set! arguments (list (car (program-arguments))))) ;; We start of with the --help and --version command-line arguments. (let ((options (getopt-long arguments option-spec)) @@ -159,7 +171,8 @@ arguments." (%opt-no-errors #f) (%opt-continue-on-error #f) (%opt-no-execution #f) - (%opt-ascii #f)) + (%opt-ascii #f) + (%opt-strict #f)) (when (option-ref options 'help #f) (display-help-and-exit (car arguments))) (when (option-ref options 'version #f) @@ -176,12 +189,12 @@ arguments." (let ((mf (getenv "MAKEFLAGS"))) (when mf (let ((tokens (string-tokenize mf))) - (when (member "quiet" tokens) - (set! %opt-quiet #t) + (when (member "terse" tokens) + (set! %opt-terse #t) (set! %opt-verbose #f)) (when (member "verbose" tokens) (set! %opt-verbose #t) - (set! %opt-quiet #f)) + (set! %opt-terse #f)) (when (member "builtins" tokens) (set! %opt-builtins #t)) (when (member "ascii" tokens) @@ -190,28 +203,32 @@ arguments." (set! %opt-ignore-errors #t)) (when (member "continue-on-error" tokens) (set! %opt-continue-on-error #t)) + (when (member "strict" tokens) + (set! %opt-strict #t)) (when (member "no-execution" tokens) (set! %opt-no-execution #t)))))) ;; Now the bulk of the command-line options. - (when (option-ref options 'quiet #f) - (set! %opt-quiet #t) + (when (option-ref options 'terse #f) + (set! %opt-terse #t) (set! %opt-verbose #f)) (when (option-ref options 'verbose #f) (set! %opt-verbose #t) - (set! %opt-quiet #f)) - (set! %opt-builtins - (option-ref options 'builtins #f)) - (set! %opt-elevate-environment - (option-ref options 'elevate-environment #f)) - (set! %opt-ignore-errors - (option-ref options 'ignore-errors #f)) - (set! %opt-continue-on-error - (option-ref options 'continue-on-error #f)) - (set! %opt-no-execution - (option-ref options 'no-execution #f)) - (set! %opt-ascii - (option-ref options 'ascii #f)) + (set! %opt-terse #f)) + (when (option-ref options 'builtins #f) + (set! %opt-builtins #t)) + (when (option-ref options 'elevate-environment #f) + (set! %opt-elevate-environment #t)) + (when (option-ref options 'ignore-errors #f) + (set! %opt-ignore-errors #t)) + (when (option-ref options 'continue-on-error #f) + (set! %opt-continue-on-error #t)) + (when (option-ref options 'no-execution #f) + (set! %opt-no-execution #t)) + (when (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 ;; the build environment. @@ -221,14 +238,20 @@ arguments." %opt-environment %opt-elevate-environment %opt-builtins + %opt-strict %opt-verbose %opt-ascii) - #;(initialize-rules %opt-no-builtins - %opt-verbose) - ;; The remaining command-line words are the build targets that ;; we're going to tackle. (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) %targets ))) @@ -262,7 +285,7 @@ targets listed on the parsed command-line are used." (if (not (build target)) ;; %opt-ignore-errors ;; %opt-continue-on-error - ;; %opt-quiet + ;; %opt-terse ;; %opt-verbose)) (begin (print "The recipe for “~A” has failed.~%" target)) diff --git a/potato/makevars.scm b/potato/makevars.scm index 596d403..4d89913 100644 --- a/potato/makevars.scm +++ b/potato/makevars.scm @@ -9,7 +9,7 @@ %elevate-environment? lazy-assign ?= assign := - reference $ + reference $ Q reference-func $$ dump-makevars )) @@ -27,6 +27,7 @@ (define %ascii? #f) (define %makevars #f) (define %elevate-environment? #f) +(define %strict #f) (define %verbose? #t) (define (debug spec . args) (when %verbose? @@ -55,7 +56,7 @@ later equals signs." "The logic of whether which makemacro priority levels can override others." (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)) #f ;; else @@ -185,15 +186,17 @@ the value of MAKEFLAGS or SHELL." environment? elevate-environment? builtins? + strict? verbose? ascii?) (set! %elevate-environment? elevate-environment?) (set! %makevars (make-hash-table)) + (set! %strict strict?) (set! %verbose? verbose?) (set! %ascii? ascii?) (when builtins? (makevars-add-builtins)) - (when environment? + (when (or environment? elevate-environment?) (makevars-add-environment) (makevars-add-makeflags)) (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 referenced. If VAL is not given, the empty string will be used." - (when (and (not (string? key)) - (not (procedure? key))) - (bad-key-type "lazy-assign" (list key))) - (when (and (not (string? val)) - (not (procedure? val))) - (bad-value-type "lazy-assign" (list val))) - (let ((KEY (if (string? key) key (key))) - (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)))) + (when (procedure? key) + (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))) (define-syntax ?= (lambda (stx) @@ -240,21 +237,17 @@ VAL. 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. If VAL is not given, the empty string will be used." - (when (and (not (string? key)) - (not (procedure? key))) - (bad-key-type "assign" (list key))) - (when (and (not (string? val)) - (not (procedure? val))) - (bad-value-type "assign" (list val))) - (let ((KEY (if (string? key) key (key))) - (VAL (if (string? val) val (val)))) - (unless (string? KEY) - (bad-proc-output "assign" KEY)) - (unless (string? VAL) - (bad-proc-output "assign" VAL)) - (makevars-set KEY VAL) - (when %verbose? - (format #t "~A=~A~%" KEY VAL)))) + (when (procedure? key) + (set! key (key))) + (unless (string? key) + (set! key (format #f "~a" key))) + (when (procedure? val) + (set! val (val))) + (unless (string? val) + (set! val (format #f "~a" val))) + (makevars-set key val) + (when %verbose? + (format #t "~A=~A~%" key val))) (define-syntax := (lambda (stx) @@ -264,8 +257,7 @@ string to use as the key in the hash table entry. ((_ key) #'(assign (symbol->string (syntax->datum #'key))))))) - -(define* (reference key #:optional (transformer #f)) +(define* (reference key quoted? #:optional (transformer #f)) "Looks up KEY in the %makevars hash table. KEY may be a string or a procedure that evaluates to a string. If the value of the key @@ -284,33 +276,62 @@ space-separated token in the looked-up value." (set! key (key)) (unless (string? key) (bad-proc-output "reference" key))) + (when (not (string? key)) + (set! key (format #t "~a" key))) (let* ((val&priority (hash-ref %makevars key)) (val (if (pair? val&priority) (car val&priority) #f)) (priority (if (pair? val&priority) (cdr val&priority) #f))) (if (not val) - #f + (if %strict + (error (format #t "There is no makevar for key ~a~%" key)) + ;; else + (if quoted? + "\"\"" + "")) ;; else (begin - (when (promise? val) + (cond + ((promise? val) (set! val (force val)) - (unless (string? val) - (bad-proc-output "reference" val)) - (hash-set! %makevars key (cons val priority)) - (when %verbose? - (format #t "~A=~A~%" key val))) + (cond + ((string? val) + ;; noop + #t) + ((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) (set! val (string-append-with-spaces (map transformer (string-tokenize val))))) - val)))) + (if quoted? + (string-append "\"" val "\"") + val))))) (define-syntax $ (lambda (stx) (syntax-case stx () - ((_ key val) - #'(reference (symbol->string (syntax->datum #'key)) transformer)) + ((_ key transformer) + #'(reference (symbol->string (syntax->datum #'key)) #f transformer)) ((_ 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) "Looks up KEY in the %makevars hash table. KEY shall be a string @@ -347,3 +368,4 @@ that string." (syntax-case stx () ((_ key) #'(reference-func (symbol->string (syntax->datum #'key))))))) + diff --git a/potato/rules.scm b/potato/rules.scm index d305388..b337d29 100644 --- a/potato/rules.scm +++ b/potato/rules.scm @@ -1,9 +1,11 @@ (define-module (potato rules) #:use-module (ice-9 pretty-print) + #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (potato exceptions) #:use-module (potato builtins) #:use-module (potato makevars) + #:use-module (potato text) #:export( @@ -11,6 +13,7 @@ %suffix-rules initialize-rules first-target-rule-name + install-alternate-system-driver target-rule : suffix-rule -> target-name $@ @@ -25,6 +28,18 @@ 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 @@ -52,10 +67,11 @@ it is evaluated." (effective-arg #f)) (cond ((procedure? arg) + (format #t "BLAMMO ~s ~s ~%" arg (arg)) (set! effective-arg (arg)) - #;(unless (string? effective-arg) - (bad-proc-output "~" arg)) + (unless (string? effective-arg) + (bad-proc-output "~" arg)) ) ((string? arg) (set! effective-arg arg)) @@ -94,7 +110,7 @@ it is evaluated." (define (always-execute-compose . args) (cons 'always-execute (apply base-compose args))) -(define ~@ always-execute-compose) +(define ~+ always-execute-compose) (define (regular-file? filename) (let ((st (stat filename #f))) @@ -105,6 +121,15 @@ it is evaluated." (+ (* 1000000000 (stat:mtime 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 @@ -128,10 +153,25 @@ it is evaluated." (define* (target-rule name #:optional (prerequisites '()) #:rest recipes) "Register a new target rule" - ;; FIXME: Typecheck - (let ((rule (make-target-rule name prerequisites recipes 1))) - ;; Add to %target-rules - (set! %target-rules (cons rule %target-rules)))) + + (when %verbose? + (format #t "Defining target rule: ~A ~A ~A~%" prerequisites (right-arrow) name)) + ;; 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 (define : target-rule) @@ -166,9 +206,25 @@ it is evaluated." (define (suffix-rule source target . recipes) "Register a suffix rule" + ;; FIXME: Typecheck - (let ((rule (make-suffix-rule source target recipes 1))) - (set! %suffix-rules (cons rule %suffix-rules)))) + (when %verbose? + (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 (define -> suffix-rule) @@ -183,14 +239,14 @@ it is evaluated." ;; just a label. (name node-get-name node-set-name!) ;; A 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 ;; time in nanoseconds since the epoch. If 'name' does not exist, ;; _mtime is #f. (mtime node-get-mtime node-set-mtime!) ;; One of 'pass, 'fail, or 'undetermined (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!) (children node-get-children node-set-children!) ;; Determines how many children must pass for the parent @@ -198,60 +254,51 @@ it is evaluated." (logic node-get-logic node-set-logic!) ) +(define (has-rules? node) + (not (null? (node-get-rules node)))) -;;;;;;;;; -;; Automatic variables -;; target-name $@ -;; target-basename $* -;; newer-prerequisites $? -;; prerequisites $^ -;; primary-prerequisite $< +(define (set-fail! node) + (node-set-status! node 'fail)) +(define (set-pass! node) + (node-set-status! node 'pass)) -;; This is set in the builder to make automatic variables work. -(define %node-cur #f) +(define (failed? node) + (eqv? (node-get-status node) 'fail)) -(define target-name - (lambda () - (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 (passed? node) + (eqv? (node-get-status node) 'pass)) +(define (leaf-node? node) + (null? (node-get-children node))) (define (undetermined? node) (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) (cond ((leaf-node? node) @@ -276,32 +323,26 @@ it is evaluated." (else #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) "Return the first child node that is not yet PASS or FAIL" (let ((children (node-get-children node))) - (any (lambda (child) - (if (eqv? (node-get-status child) 'undetermined) - child - #f)) - children))) - -(define (failed? node) - (eqv? (node-get-status node) 'fail)) + (if (null? children) + #f + + (any (lambda (child) + (if (eqv? (node-get-status child) 'undetermined) + child + #f)) + children)))) (define (has-parent? node) (if (node-get-parent node) #t #f)) +(define (get-parent node) + (node-get-parent node)) + (define (up-to-date? node) "Checks if node is up to date: - it has an mtime @@ -317,247 +358,369 @@ it is evaluated." #t #f))) -(define (get-parent node) - (node-get-parent node)) -#| -(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)))))) - +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; AUTOMATIC VARIABLES - (define (run-default-recipe! node) - "The default recipe passes if the file exists" - (let ((name (node-get-name node))) - (if (and (file-exists? name) - (regular-file? name)) - (begin - (node-set-status! node 'pass) - (node-set-mtime! node (compute-mtime name))) - ;; else - (node-set-status! node 'fail)))) +(define target-name 'unspecified) +(define target-basename 'unspecified) +(define prerequisites '()) +(define primary-prerequisite 'unspecified) +(define newer-prerequisites '()) - ;; 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. - ;; Descent to first leaf. - ;; If self's mtime is earlier than parent's mtime, mark self as PASS. - ;; Elif self has rules, run rules and mark self PASS/FAIL. - ;; Else self has no mtime or rules, so mark self FAIL. - ;; Go to parent. +(define (target-rule-prep-automatic-variables node rule) + (set! target-name (node-get-name node)) + (set! target-basename (basename target-name)) + (set! prerequisites (target-rule-get-prerequisites rule)) + (set! primary-prerequisite (if (null? prerequisites) #f (car prerequisites))) + (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 - - ;; IF UNDETERMINED do these... - - ;; Are we done with the children? - ;; If AND rule and one child is FAIL, stop - ;; If OR rule and one child is PASS, stop - ;; 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 +(define (suffix-rule-prep-automatic-variables node rule) + (set! target-name (node-get-name node)) + (set! target-basename (basename target-name)) + (set! primary-prerequisite (string-append target-basename (suffix-rule-get-source rule))) + (set! prerequisites (list primary-prerequisite)) + (set! newer-prerequisites + ;; If this node doesn't have a real file attached, then the + ;; prerequisite is newer. + (if (not (node-get-mtime node)) + (list primary-prerequisite) + ;; Prerequisites that have no mtime or a higher mtime are + ;; "newer". + (cond + ((and (file-exists? primary-prerequisite) + (regular-file? primary-prerequisite) + (> (node-get-mtime node) (compute-mtime primary-prerequisite))) + (list primary-prerequisite)) + (else + '()))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; 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?) - (set! %target-rules '()) - (set! %suffix-rules '()) - (set! %debug? debug?) - (unless no-builtins? - ;; Set up initial suffix rules - (suffix-rule ".c" ".o" - (string-compose - (reference "CC") - (reference "CFLAGS") - "-c" - primary-prerequisite))) - ) +(define (run-rules! node) + "Runs the rules associated with this node. If this node has *and* +logic, it runs the recipes one by one, quitting on the first failure. +If this rule has *or* logic, it runs them quitting on the first +success." + (let ((rules (node-get-rules node))) + (when (null? rules) + (error "no rules")) - (define (create-node name parent) - "Constructs a tree of nodes, with name as the root node." - (let ((node (make-node name parent 'untested))) + (let ((logic (node-get-logic node))) + (let loop ((rule (car rules)) + (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 - ;; 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))) +(define (run-recipes! node recipes) + "Runs the recipes on 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." + (when (null? recipes) + (error "no recipe")) + (let loop ((opt/recipe (car recipes)) + (rest (cdr recipes))) + (let ((opt (car opt/recipe)) + (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)) - (let loop ((rule (car %target-rules)) - (rest (cdr %target-rules))) + + (cond + ((failed? node) + ;; 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 - ;; 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. +(define (run-default-recipe! node) + "The default recipe passes if the file exists" + (let ((name (node-get-name node))) + (if (and (file-exists? name) + (regular-file? name)) + (begin + (set-pass! node) + (node-set-mtime! node (compute-mtime name))) + ;; else + (set-fail! node)))) - ;; 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 '()))))) +;; Start at root - #| - ;; 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)) +;; If cur is UNDETERMINED, find a leaf that is UNDETERMINED. +;; Descent to first leaf. +;; If self's mtime is earlier than parent's mtime, mark self as PASS. +;; Elif self has rules, run rules and mark self PASS/FAIL. +;; Else self has no mtime or rules, so mark self FAIL. +;; Go to parent. - ;; 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)) +;; IF PASS or FAIL, go to parent - (define (build root) - "Give a tree of , this executes the recipes therein." - (let ((tree (create-node root #f))) - (let ((node root)) - (while #t - (if (undetermined? node) +;; IF UNDETERMINED do these... + +;; Are we done with the children? +;; If AND rule and one child is FAIL, stop +;; If OR rule and one child is PASS, stop +;; 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 , 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-passed? node) (if (up-to-date? node) (set-pass! node) ;; else, not up to date - (if (has-recipe? node) - (run-recipe! node) + (if (has-rules? node) + (run-rules! node) ;; else, no recipe exists (run-default-recipe! node))) ;; else, children have failed (set-fail! node)) ;; else, children aren't complete - (set! node (get-next-child node))) - ;; else, this node is determined - (if (and abort-on-error (failed? node)) + (set! node (get-next-child node)))) + ;; else, this node is determined + (begin + (if (and (not %ignore-errors?) (failed? node)) (break) ;; else not failed (if (has-parent? node) (set! node (get-parent node)) ;; else, there is no parent to this node - (break)))))))) - -|# + (break))))))))) + + diff --git a/raw-tests.scm b/raw-tests.scm old mode 100644 new mode 100755 index 208667c..b39e5e9 --- a/raw-tests.scm +++ b/raw-tests.scm @@ -5,8 +5,9 @@ exec guile -L . -s "$0" "$@" (use-modules (potato make) (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) diff --git a/tests.scm b/tests.scm index ed5b0d7..1248988 100755 --- a/tests.scm +++ b/tests.scm @@ -6,12 +6,344 @@ exec guile -L . -s "$0" "$@" (srfi srfi-1) (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 - (initialize #:arguments '() #:environ #f) - (not (%opt-verbose?)))) + (setenv "FOO1" "BAR1") + (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")