pman/potato/rules.scm

944 lines
34 KiB
Scheme
Raw Normal View History

2021-02-08 07:46:24 +01:00
(define-module (potato rules)
#:use-module (ice-9 pretty-print)
2021-02-15 00:28:12 +01:00
#:use-module (ice-9 optargs)
2021-02-13 01:34:18 +01:00
#:use-module (srfi srfi-1)
2021-02-08 07:46:24 +01:00
#:use-module (srfi srfi-9)
#:use-module (potato exceptions)
#:use-module (potato builtins)
#:use-module (potato makevars)
2021-02-13 01:34:18 +01:00
#:use-module (potato text)
2021-02-08 07:46:24 +01:00
#:export(<target-rule>
<suffix-rule>
<node>
%target-rules
%suffix-rules
initialize-rules
first-target-rule-name
2021-02-13 01:34:18 +01:00
install-alternate-system-driver
2021-02-15 00:28:12 +01:00
target-rule :
suffix-rule ->
2021-02-08 07:46:24 +01:00
target-name $@
2021-02-14 08:42:16 +01:00
newer-prerequisites $? $$?
2021-02-08 07:46:24 +01:00
primary-prerequisite $<
target-basename $*
2021-02-14 08:42:16 +01:00
prerequisites $^ $$^
2021-02-08 07:46:24 +01:00
build
2021-02-10 15:28:32 +01:00
string-compose ~
silent-compose ~@
always-execute-compose ~+
ignore-error-compose ~-
2021-02-08 07:46:24 +01:00
))
2021-02-13 01:34:18 +01:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GLOBALS
(define %ignore-errors? #f)
(define %continue-on-error? #f)
(define %no-execution? #f)
(define %verbosity 2)
2021-02-13 01:34:18 +01:00
(define %ascii? #f)
(define %top-level-targets '())
2021-02-10 15:28:32 +01:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPER FUNCTIONS
2021-02-15 00:28:12 +01:00
2021-02-10 15:28:32 +01:00
(define (basename str)
"Strip off the '.ext' part of a filename string."
2021-02-15 00:28:12 +01:00
(unless (string? str)
(scm-error 'wrong-type-arg "basename" "Not a string: ~S" (list str) #f))
2021-02-10 15:28:32 +01:00
(let ((idx (string-index-right str #\.)))
(if idx
(substring str 0 idx)
str)))
(define (base-compose . args)
"Returns a lambda that appends args together as a string,
adding intermediate spaces. If an arg is a procedure,
it is evaluated."
(lambda ()
;; Loop over all the args, appending them together as a
;; string. Try to be smart about the types of args.
(let loop ((args args)
(result ""))
(cond
((null? args)
result)
(else
(let ((arg (car args))
(effective-arg #f))
(cond
((procedure? arg)
(set! effective-arg (arg))
2021-02-15 00:28:12 +01:00
2021-02-13 01:34:18 +01:00
(unless (string? effective-arg)
2021-02-15 00:28:12 +01:00
(bad-proc-output "~" arg)))
2021-02-10 15:28:32 +01:00
((string? arg)
(set! effective-arg arg))
(else
;; Not a string or procedure?
;; Let's just write it, I guess. YOLO!
(set! effective-arg
(format #f "~a" arg))))
;; Loop, only adding spaces as necessary
(let ((need-a-space?
(and (not (string-null? result))
(not (string-null? effective-arg)))))
(loop
(cdr args)
(string-append
result
(if need-a-space? " " "")
effective-arg)))))))))
(define (string-compose . args)
(cons 'default (apply base-compose args)))
(define ~ string-compose)
(define (ignore-error-compose . args)
(cons 'ignore-error (apply base-compose args)))
(define ~- ignore-error-compose)
(define (silent-compose . args)
(cons 'silent (apply base-compose args)))
(define ~@ silent-compose)
(define (always-execute-compose . args)
(cons 'always-execute (apply base-compose args)))
2021-02-13 01:34:18 +01:00
(define ~+ always-execute-compose)
2021-02-10 15:28:32 +01:00
(define (regular-file? filename)
2021-02-15 00:28:12 +01:00
(unless (string? filename)
(scm-error 'wrong-type-arg "regular-file?" "Not a string: ~S" (list filename) #f))
2021-02-10 15:28:32 +01:00
(let ((st (stat filename #f)))
(eq? (stat:type st) 'regular)))
(define (compute-mtime filename)
2021-02-15 00:28:12 +01:00
(unless (string? filename)
(scm-error 'wrong-type-arg "regular-file?" "Not a string: ~S" (list filename) #f))
2021-02-10 15:28:32 +01:00
(let ((st (stat filename #f)))
(+ (* 1000000000 (stat:mtime st))
(stat:mtimensec st))))
2021-02-13 01:34:18 +01:00
(define %system-proc system)
(define (install-alternate-system-driver proc)
2021-02-15 00:28:12 +01:00
"Give a procure to use rather than the standard 'system' procedure,
installs it as the system driver. Returns the old system driver."
2021-02-13 01:34:18 +01:00
(unless (procedure? proc)
2021-02-15 00:28:12 +01:00
(scm-error 'wrong-type-arg "install-alternate-system-driver" "Not a procedure: ~S" (list proc) #f))
(let ((old-proc %system-proc))
(set! %system-proc proc)
old-proc))
2021-02-13 01:34:18 +01:00
2021-02-10 15:28:32 +01:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TARGET STRUCT AND METHODS
2021-02-08 07:46:24 +01:00
(define-record-type <target-rule>
(make-target-rule name prerequisites recipes priority)
target-rule?
;; A filename, for real targets, or just a name for phony targets
(name target-rule-get-name target-rule-set-name!)
;; A list of filenames and/or phony targets that have target rules
(prerequisites target-rule-get-prerequisites
target-rule-set-prerequisites!)
;; A list of strings or procedures
(recipes target-rule-get-recipes
target-rule-set-recipes!)
2021-02-15 00:28:12 +01:00
;; 1 = script-defined. 2 = built-in
2021-02-08 07:46:24 +01:00
(priority target-rule-get-priority
target-rule-set-priority!))
2021-02-10 15:28:32 +01:00
;; List of all target rules in order of importance
(define %target-rules '())
(define* (target-rule name #:optional (prerequisites '()) #:rest recipes)
"Register a new target rule"
2021-02-13 01:34:18 +01:00
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
2021-02-15 00:28:12 +01:00
(if (null? prerequisites)
2021-02-15 04:54:37 +01:00
(format #t "Target rule: ~a~A~a~%~!" (lquo) name (rquo))
(format #t "Target rule: ~a~A~a ~A ~A~%~!" (lquo) name (rquo) (left-arrow) prerequisites)))
2021-02-15 00:28:12 +01:00
;; Empty recipes is shorthand for a recipe that always passes.
(when (null? recipes)
(set! recipes (list #t)))
2021-02-13 01:34:18 +01:00
;; If any recipes are raw strings, we need to make them into
;; (cons 'default string)
(let ((recipes2
(map (lambda (recipe)
(cond
((pair? recipe)
recipe)
2021-02-15 00:28:12 +01:00
(else
(cons 'default recipe))))
2021-02-13 01:34:18 +01:00
recipes)))
(let ((rule (make-target-rule name prerequisites recipes2 1)))
;; Add to %target-rules
(set! %target-rules (cons rule %target-rules)))))
2021-02-10 15:28:32 +01:00
;; Alias
(define : target-rule)
(define (first-target-rule-name)
(if (null? %target-rules)
#f
;; else
2021-02-15 00:28:12 +01:00
(target-rule-get-name (last %target-rules))))
2021-02-10 15:28:32 +01:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SUFFIX STRUCT AND METHODS
2021-02-08 07:46:24 +01:00
(define-record-type <suffix-rule>
(make-suffix-rule source-suffix target-suffix recipes priority)
suffix-rule?
;; A string, usually like ".c". Or a string->string proc.
(source-suffix suffix-rule-get-source
suffix-rule-set-source)
;; A string, usually like ".o". Or a string->bool proc.
(target-suffix suffix-rule-get-target
suffix-rule-set-suffix!)
;; A list of strings or procedures
(recipes suffix-rule-get-recipes
suffix-rule-set-recipes!)
2021-02-15 00:28:12 +01:00
;; 1 = script-defined. 2 = built-in
2021-02-08 07:46:24 +01:00
(priority suffix-rule-get-priority
suffix-rule-set-priority!))
2021-02-10 15:28:32 +01:00
;; The list of all registered suffix rules in order of importance
(define %suffix-rules '())
(define (suffix-rule source target . recipes)
"Register a suffix rule"
2021-02-15 00:28:12 +01:00
2021-02-10 15:28:32 +01:00
;; FIXME: Typecheck
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "Suffix rule: ~a~A~a ~A ~a~A~a~%~!"
(lquo) source (rquo) (right-arrow) (lquo) target (rquo)))
2021-02-13 01:34:18 +01:00
;; If any recipes are raw strings, we need to make them into
;; (cons 'default string)
(let ((recipes2
(map (lambda (recipe)
(cond
((pair? recipe)
recipe)
(else
2021-02-14 08:42:16 +01:00
(cons 'default recipe))))
2021-02-13 01:34:18 +01:00
recipes)))
2021-02-15 00:28:12 +01:00
2021-02-14 08:42:16 +01:00
(let ((rule (make-suffix-rule source target recipes2 1)))
2021-02-13 01:34:18 +01:00
(set! %suffix-rules (cons rule %suffix-rules)))))
2021-02-10 15:28:32 +01:00
;; Alias
(define -> suffix-rule)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; NODE STRUCT AND METHODS
2021-02-08 07:46:24 +01:00
(define-record-type <node>
(make-node name parent status)
node?
;; For a real target, this is a filename. For a phony target it is
;; just a label.
(name node-get-name node-set-name!)
;; A <node> which is the parent of this node, or #f.
2021-02-13 01:34:18 +01:00
(parent node-get-parent node-set-parent!)
2021-02-08 07:46:24 +01:00
;; 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!)
2021-02-10 15:28:32 +01:00
;; One of 'pass, 'fail, or 'undetermined
2021-02-08 07:46:24 +01:00
(status node-get-status node-set-status!)
2021-02-15 00:28:12 +01:00
;; Either 'target or 'suffix or 'default
(rule-type node-get-rule-type node-set-rule-type!)
2021-02-13 01:34:18 +01:00
;; A list of rules
2021-02-08 07:46:24 +01:00
(rules node-get-rules node-set-rules!)
(children node-get-children node-set-children!)
)
2021-02-15 00:28:12 +01:00
(define (using-target-rule? node)
(eq? 'target (node-get-rule-type node)))
(define (using-suffix-rules? node)
(eq? 'suffix (node-get-rule-type node)))
(define (using-default-rule? node)
(eq? 'default (node-get-rule-type node)))
2021-02-10 15:28:32 +01:00
2021-02-13 01:34:18 +01:00
(define (set-fail! node)
(node-set-status! node 'fail))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (set-pass! node)
(node-set-status! node 'pass))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (failed? node)
(eqv? (node-get-status node) 'fail))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (passed? node)
(eqv? (node-get-status node) 'pass))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (leaf-node? node)
(null? (node-get-children node)))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (undetermined? node)
(eq? (node-get-status node) 'undetermined))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (any-child-has-passed? node)
2021-02-15 00:28:12 +01:00
(unless (node? node)
(scm-error 'wrong-type-arg "any-child-has-passed?" "Not a node: ~S" (list node) #f))
(when (null? (node-get-children node))
(scm-error 'misc-error "any-child-has-passed?" "Node ~a has no children"
(list (node-get-name node)) #t))
2021-02-13 01:34:18 +01:00
(let ((children (node-get-children node)))
2021-02-15 00:28:12 +01:00
(any passed? children)))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (every-child-has-passed? node)
2021-02-15 00:28:12 +01:00
(unless (node? node)
(scm-error 'wrong-type-arg "every-child-has-passed?" "Not a node: ~S" (list node) #f))
(when (null? (node-get-children node))
(scm-error 'misc-error "every-child-has-passed?" "Node ~a has no children"
(list (node-get-name node)) #t))
2021-02-13 01:34:18 +01:00
(let ((children (node-get-children node)))
2021-02-15 00:28:12 +01:00
(every passed? children)))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (any-child-has-failed? node)
2021-02-15 00:28:12 +01:00
(unless (node? node)
(scm-error 'wrong-type-arg "any-child-has-failed?" "Not a node: ~S" (list node) #f))
(when (null? (node-get-children node))
(scm-error 'misc-error "any-child-has-failed?" "Node ~a has no children"
(list (node-get-name node)) #t))
2021-02-13 01:34:18 +01:00
(let ((children (node-get-children node)))
2021-02-15 00:28:12 +01:00
(any failed? children)))
2021-02-08 07:46:24 +01:00
2021-02-13 01:34:18 +01:00
(define (every-child-has-failed? node)
2021-02-15 00:28:12 +01:00
(unless (node? node)
(scm-error 'wrong-type-arg "every-child-has-failed?" "Not a node: ~S" (list node) #f))
(when (null? (node-get-children node))
(scm-error 'misc-error "every-child-has-failed?" "Node ~a has no children"
(list (node-get-name node)) #t))
2021-02-13 01:34:18 +01:00
(let ((children (node-get-children node)))
2021-02-15 00:28:12 +01:00
(every failed? children)))
2021-02-08 07:46:24 +01:00
(define (children-complete? node)
(cond
((leaf-node? node)
#t)
2021-02-15 00:28:12 +01:00
((eqv? 'target (node-get-rule-type node))
2021-02-08 07:46:24 +01:00
(or (every-child-has-passed? node)
(any-child-has-failed? node)))
2021-02-15 00:28:12 +01:00
((eqv? 'suffix (node-get-rule-type node))
2021-02-08 07:46:24 +01:00
(or (every-child-has-failed? node)
(any-child-has-passed? node)))
(else
#f)))
(define (children-passed? node)
(cond
((null? (node-get-children node))
#t)
2021-02-15 00:28:12 +01:00
((eq? 'target (node-get-rule-type node))
2021-02-08 07:46:24 +01:00
(every-child-has-passed? node))
2021-02-15 00:28:12 +01:00
((eq? 'suffix (node-get-rule-type node))
2021-02-08 07:46:24 +01:00
(any-child-has-passed? node))
(else
#f)))
2021-02-10 15:28:32 +01:00
(define (get-next-child node)
"Return the first child node that is not yet PASS or FAIL"
(let ((children (node-get-children node)))
2021-02-15 00:28:12 +01:00
(cond
((null? children)
#f)
(else
(any (lambda (child)
(if (eqv? (node-get-status child) 'undetermined)
child
#f))
children)))))
2021-02-10 15:28:32 +01:00
(define (has-parent? node)
(if (node-get-parent node)
#t
#f))
2021-02-15 04:54:37 +01:00
(define (has-children? node)
(if (null? (node-get-children node))
#f
#t))
2021-02-13 01:34:18 +01:00
(define (get-parent node)
(node-get-parent node))
2021-02-10 15:28:32 +01:00
(define (up-to-date? node)
"Checks if node is up to date:
- it has an mtime
- all its children have mtimes
- its mtime is older than the mtime of its children"
2021-02-14 08:42:16 +01:00
(let ((children (node-get-children node))
(parent-mtime (node-get-mtime node)))
(if (or (null? children) (not (integer? parent-mtime)))
;; Targets without children are always rebuilt.
;; Targets without mtimes are always rebuilt.
#f
(let ((children-mtime (map node-get-mtime children)))
(if (every (lambda (child-mtime)
(and (integer? child-mtime)
(>= parent-mtime child-mtime)))
children-mtime)
#t
#f)))))
2021-02-10 15:28:32 +01:00
2021-02-15 04:54:37 +01:00
(define (node-depth node)
2021-02-15 00:28:12 +01:00
(let loop ((depth 0)
(cur node))
(if (has-parent? cur)
(loop (1+ depth) (get-parent cur))
;;
2021-02-15 04:54:37 +01:00
depth)))
(define (node-depth-string node)
(make-string (* 2 (node-depth node)) #\space))
2021-02-15 00:28:12 +01:00
2021-02-13 01:34:18 +01:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; AUTOMATIC VARIABLES
(define target-name 'unspecified)
(define target-basename 'unspecified)
(define prerequisites '())
(define primary-prerequisite 'unspecified)
(define newer-prerequisites '())
2021-02-14 08:42:16 +01:00
(define (string-append-with-spaces lst)
"Appends the strings in lst, adding spaces in between."
(if (null? lst)
""
;; else
(fold
(lambda (elem prev)
(string-append prev " " elem))
(car lst)
(cdr lst))))
2021-02-13 01:34:18 +01:00
(define $@ (lambda () target-name))
(define $* (lambda () target-basename))
(define $< (lambda () primary-prerequisite))
(define $$? (lambda () newer-prerequisites))
2021-02-14 08:42:16 +01:00
(define $? (lambda () (string-append-with-spaces newer-prerequisites)))
2021-02-13 01:34:18 +01:00
(define $$^ (lambda () prerequisites))
2021-02-14 08:42:16 +01:00
(define $^ (lambda () (string-append-with-spaces prerequisites)))
2021-02-13 01:34:18 +01:00
(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))
2021-02-14 08:42:16 +01:00
(set! primary-prerequisite (if (null? prerequisites) "" (car prerequisites)))
2021-02-13 01:34:18 +01:00
(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))))
(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
'())))))
2021-02-10 15:28:32 +01:00
2021-02-13 01:34:18 +01:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MIXED METHODS
;; requiring more than one of node, automatic variables, suffix rules
;; and target rules
(define (add-builtins)
2021-02-15 04:54:37 +01:00
#|
2021-02-13 01:34:18 +01:00
(-> ".c" ""
(~ ($ CC) ($ CFLAGS) ($ LDFLAGS) "-o" $@ $<))
(-> ".f" ""
2021-02-15 00:28:12 +01:00
(~ ($ FC) ($ FFLAGS) ($ LDFLAGS) "-o" $@ $<))
2021-02-13 01:34:18 +01:00
(-> ".sh" ""
(~ "cp" $< $@)
(~ "chmod a+x" $< $@))
2021-02-15 04:54:37 +01:00
|#
2021-02-13 01:34:18 +01:00
(-> ".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) $<)))
2021-02-15 00:28:12 +01:00
(define (run-target-rule! node)
"Runs the (singular) target rule associated with this node."
(unless (node? node)
(scm-error 'wrong-type-arg "run-target-rule!" "Not a node: ~S" (list node) #f))
2021-02-13 01:34:18 +01:00
(let ((rules (node-get-rules node)))
(when (null? rules)
2021-02-15 00:28:12 +01:00
(scm-error 'misc-error "run-target-rule!" "Node ~S has no target rules"
(list (node-get-name node)) #f))
(unless (= 1 (length rules))
(scm-error 'misc-error "run-target-rule!" "Node ~S has ~A target rules"
(list (node-get-name node) (length rules)) #f))
(unless (or (leaf-node? node) (every-child-has-passed? node))
(scm-error 'misc-error "run-target-rule!" "Node ~S: not all children have passed"
(list (node-get-name node)) #f))
(let ((rule (car rules)))
(target-rule-prep-automatic-variables node rule)
(run-recipes! node (target-rule-get-recipes rule))
(let ((status (node-get-status node)))
status))))
(define (run-suffix-rules! node)
"Runs the one-or-more suffix rules associated with this node. It
runs them one-by-one, quitting on the first success."
(unless (node? node)
(scm-error 'wrong-type'arg "run-suffix-rules!" "Not a node: ~S" (list node) #f))
(let ((rules (node-get-rules node))
(children (node-get-children node)))
(when (null? rules)
(scm-error 'misc-error "run-suffix-rules!" "Node ~S has no rules"
(list (node-get-name node)) #f))
(when (null? children)
(scm-error 'misc-error "run-suffix-rule!" "Node ~S has no children"
(list (node-get-name node)) #f))
(unless (any-child-has-passed? node)
(scm-error 'misc-error "run-suffix-rule!" "Node ~S: not child has passed"
(list (node-get-name node)) #f))
(unless (= (length rules) (length children))
(scm-error 'misc-error "run-suffix-rule!" "Node ~S: must have as many children as rules"
(list (node-get-name node)) #f))
(let ((i 0)
(len (length children)))
(while (< i len)
(let ((rule (list-ref rules i))
(child (list-ref children i)))
(when (passed? child)
(when (>= %verbosity 3)
(format #t "~A: attempting to make using ~a~A~a ~a ~a~A~a rule"
(node-get-name node)
(lquo) (suffix-rule-get-source rule) (rquo)
(right-arrow)
(lquo) (suffix-rule-get-target rule) (rquo)))
(suffix-rule-prep-automatic-variables node rule)
(run-recipes! node (suffix-rule-get-recipes rule)))
(when (passed? node)
(break))
(set! i (1+ i)))))
(when (>= %verbosity 3)
(if (passed? node)
2021-02-15 04:54:37 +01:00
(format #t "PASS: ~a~%~!" (node-get-name node))
(format #t "FAIL: ~a~%~!" (node-get-name node))))
2021-02-15 00:28:12 +01:00
(node-get-status node)))
2021-02-13 01:34:18 +01:00
(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."
2021-02-15 00:28:12 +01:00
(unless (node? node)
(scm-error 'wrong-type-arg "run-recipes!" "Not a node: ~S" (list node) #f))
;;(unless (and (list? recipes) (not (null? recipes)))
;; (scm-error 'wrong-type-arg "run-recipes!" "Not a non-null list: ~S" (list recipes) #f))
(let ((i 0)
(len (length recipes)))
(while (< i len)
(let* ((opt/recipe (list-ref recipes i))
(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
((eq? recipe #t)
(set-pass! node))
((eq? recipe #f)
(set-fail! node))
((string? recipe)
(when (= %verbosity 1)
2021-02-15 04:54:37 +01:00
(format #t "~a~%~!" (node-get-name node)))
2021-02-15 00:28:12 +01:00
(when (or (and (= %verbosity 2) (not (eq? 'silent opt)))
(= %verbosity 3))
2021-02-15 04:54:37 +01:00
(format #t "~A~%~!" recipe))
2021-02-15 00:28:12 +01:00
(let ((retval (%system-proc recipe)))
(if (zero? retval)
(set-pass! node)
(set-fail! node))))
((procedure? recipe)
(let ((retval (recipe)))
(cond
;; If a procedure returns a string, that string gets
;; processed by system.
((string? retval)
(when (= %verbosity 1)
2021-02-15 04:54:37 +01:00
(format #t "~a~%~!" (node-get-name node)))
2021-02-15 00:28:12 +01:00
(when (or (and (= %verbosity 2) (not (eq? 'silent opt)))
(= %verbosity 3))
2021-02-15 04:54:37 +01:00
(format #t "~A~%~!" retval))
2021-02-15 00:28:12 +01:00
(let ((retval2 (%system-proc retval)))
(if (zero? retval2)
(set-pass! node)
(set-fail! node))))
;; A scheme procedure recipe that returns false.
((eqv? retval #f)
(set-fail! node))
(else
;; Otherwise, this was a procedure that didn't return
;; #f or a string, so it gets a pass.
(set-pass! node)))))
(else
;; Can't be converted to a viable string or procedure
(scm-error 'misc-error "run-recipes!" "bad recipe: ~S" (list recipe) #f)))
(when (failed? node) (break))
(set! i (1+ i))))
(when (passed? 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-rule! node)
"The default rule if not other rule exists. It just passes if the
file exists."
2021-02-13 01:34:18 +01:00
(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))))
;; Start at root
;; 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.
;; 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
2021-02-10 15:28:32 +01:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2021-02-14 08:42:16 +01:00
;; LET'S GO!
2021-02-13 01:34:18 +01:00
(define (initialize-rules targets builtins? ignore-errors? continue-on-error? no-execution? verbosity ascii?)
2021-02-13 01:34:18 +01:00
(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! %verbosity verbosity)
2021-02-13 01:34:18 +01:00
(set! %ascii? ascii?)
(when builtins?
(add-builtins)))
(define (create-node name parent)
"Constructs a tree of nodes, with name as the root node."
2021-02-15 04:54:37 +01:00
(when (and (node? parent) (> (node-depth parent) 30))
(error "Stack overflow"))
2021-02-13 01:34:18 +01:00
(let ((node (make-node name parent 'undetermined)))
(node-set-children! node '())
2021-02-15 00:28:12 +01:00
(node-set-rule-type! node 'default)
;; FIXME: here check that this name differs from all ancestor's
2021-02-13 01:34:18 +01:00
;; names
2021-02-14 08:42:16 +01:00
2021-02-13 01:34:18 +01:00
;; 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)))
2021-02-14 08:42:16 +01:00
2021-02-13 01:34:18 +01:00
;; Search for matching target rule.
(when (not (null? %target-rules))
(let loop ((rule (car %target-rules))
(rest (cdr %target-rules)))
2021-02-14 08:42:16 +01:00
2021-02-13 01:34:18 +01:00
;; 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))
2021-02-15 00:28:12 +01:00
(node-set-rule-type! node 'target)
2021-02-13 01:34:18 +01:00
;; For target-rules, the prerequisites comes from the
;; rule itself.
2021-02-14 08:42:16 +01:00
2021-02-13 01:34:18 +01:00
;; 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))
2021-02-14 08:42:16 +01:00
(for-each
(lambda (rule)
(let ((targ (suffix-rule-get-target rule)))
(when (string-suffix? 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)))
2021-02-15 00:28:12 +01:00
(node-set-rule-type! node 'suffix)
2021-02-14 08:42:16 +01:00
(let* ((src (suffix-rule-get-source rule))
(prereq
(string-append
(string-drop-right name (string-length src))
src)))
;; Note the recursion here.
(node-set-children! node
(cons (create-node prereq node)
(node-get-children node)))))))
%suffix-rules))
;; FIXME: First matching rule has highest priority? Or is last better?
2021-02-13 01:34:18 +01:00
(node-set-rules! node (reverse (node-get-rules node)))
(node-set-children! node (reverse (node-get-children node)))
2021-02-15 04:54:37 +01:00
;;(format #t "matching suffix rules ~S~%~!" (node-get-rules node))
;;(format #t "matching children rules ~S~%~!" (node-get-children node))
2021-02-14 08:42:16 +01:00
2021-02-13 01:34:18 +01:00
;; And node is ready to go
node))
(define (build root)
2021-02-15 00:28:12 +01:00
"Give a tree of <node>, this executes the recipes therein.
This is where the magic happens."
2021-02-13 01:34:18 +01:00
(let ((tree (create-node root #f)))
(let ((node tree))
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ABegin building target ~a~A~a.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
2021-02-13 01:34:18 +01:00
(while #t
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~AConsidering target ~a~A~a.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
2021-02-13 01:34:18 +01:00
(if (undetermined? node)
(begin
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a is undetermined.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo))
(unless (node-get-mtime node)
(format #t "~AFile ~a~A~a does not exist.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo))))
2021-02-10 15:28:32 +01:00
(if (children-complete? node)
2021-02-15 00:28:12 +01:00
(begin
2021-02-15 04:54:37 +01:00
(when (and (>= %verbosity 3) (has-children? node))
(format #t "~AFinished prerequisites of target file ~a~A~a.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(if (children-passed? node)
(begin
2021-02-15 04:54:37 +01:00
(when (and (>= %verbosity 3) (has-children? node))
(format #t "~AThe prerequisites of target file ~a~A~a have passed.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(if (up-to-date? node)
(begin
(when (node-get-mtime node)
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a is up to date.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo))))
2021-02-15 00:28:12 +01:00
(set-pass! node))
;; else, not up to date
(begin
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a is not up to date.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(cond
((using-target-rule? node)
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a has a target rule.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(run-target-rule! node))
((using-suffix-rules? node)
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a has a suffix rule.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(run-suffix-rules! node))
((using-default-rule? node)
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a is using the default rule.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(run-default-rule! node))
(else
(error "bad rules")))
(if (passed? node)
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a has passed.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a has failed.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))))))
2021-02-15 00:28:12 +01:00
;; else, children have failed
(begin
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~AThe prerequisites of target file ~a~A~a have failed.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(set-fail! node))))
2021-02-10 15:28:32 +01:00
;; else, children aren't complete
2021-02-15 00:28:12 +01:00
(begin
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~AThe prerequisites of target file ~a~A~a are incomplete.~%~!"
(node-depth-string node) (lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(let ((next (get-next-child node)))
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ADescending node ~a~A~a ~a ~a~A~a.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)
(right-arrow)
(lquo) (node-get-name next) (rquo)))
2021-02-15 00:28:12 +01:00
(set! node (get-next-child node))
2021-02-15 04:54:37 +01:00
))))
2021-02-13 01:34:18 +01:00
;; else, this node is determined
(begin
2021-02-15 00:28:12 +01:00
(if (passed? node)
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 2)
(format #t "~A~a~A~a: ~APASS~A~%~!"
2021-02-15 04:54:37 +01:00
(node-depth-string node) (lquo) (node-get-name node) (rquo)
(green) (default)))
(when (>= %verbosity 2)
(format #t "~A~a~A~a: ~AFAIL~A~%~!"
2021-02-15 04:54:37 +01:00
(node-depth-string node) (lquo) (node-get-name node) (rquo)
(red) (default))))
2021-02-15 00:28:12 +01:00
(if (has-parent? node)
(begin
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~AAscending node ~a~A~a ~a ~a~A~a.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)
(right-arrow)
(lquo) (node-get-name (node-get-parent node)) (rquo)))
2021-02-15 00:28:12 +01:00
(set! node (get-parent node)))
;; else, there is no parent to this node
(begin
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 3)
(format #t "~ATarget file ~a~A~a has no parent.~%~!"
(node-depth-string node)
(lquo) (node-get-name node) (rquo)))
2021-02-15 00:28:12 +01:00
(if (passed? node)
2021-02-15 04:54:37 +01:00
(when (>= %verbosity 1)
(format #t "~A~a~A~a: ~ACOMPLETE~A~%~!"
2021-02-15 04:54:37 +01:00
(node-depth-string node)
(lquo) (node-get-name node) (rquo)
(green) (default)))
(when (>= %verbosity 1)
(format #t "~A~a~A~a: ~ACOMPLETE~A~%~!"
2021-02-15 04:54:37 +01:00
(node-depth-string node)
(lquo) (node-get-name node) (rquo)
(red) (default))))
2021-02-15 00:28:12 +01:00
(break)))))))
2021-02-14 08:42:16 +01:00
;; Return the command output of the root node
(passed? tree)))