(define-library (zilch planner step) (import (scheme base) (scheme write) (scheme process-context) (scheme lazy) (scheme case-lambda) (chicken format) (srfi 128) (srfi 146)) (export register-build-step empty-build-plan build-plan-print build-plan-tick build-plan-next-id build-plan-current-plan build-plan-append build-plan-tail-call build-plan-return-append) (begin (define-record-type (make-build-plan steps ready-to-run finalized last-id) build-plan? (steps build-plan-steps set-build-plan-steps!) (ready-to-run build-plan-ready-to-run set-build-plan-ready-to-run!) (finalized build-plan-finalized set-build-plan-finalized!) (last-id build-plan-last-id set-build-plan-last-id!)) ;; Represents a single step in the build. ;; id is a monotonically increasing identifier. ;; dependencies is an alist of identifiers for other build steps to when they were added. (define-record-type (make-build-step id dependencies type arguments called-from) build-step? (id build-step-id) (dependencies build-step-dependencies set-build-step-dependencies!) (type build-step-type) (arguments build-step-arguments set-build-step-arguments!) (called-from build-step-called-from)) (define-record-type (make-build-step-type proc name batch) build-step-type? (proc build-step-type-proc) (name build-step-type-name) (batch build-step-type-batch)) ;; Returns a new empty build plan. (define (empty-build-plan) (make-build-plan (mapping (make-default-comparator)) '() (mapping (make-default-comparator)) 0)) ;; Returns a monotonically-increasing identifier for this build plan. (define (build-plan-next-id plan) (define id (+ (build-plan-last-id plan) 1)) (set-build-plan-last-id! plan id) id) ;; Appends a new step to the build plan, returning its ID. (define (build-plan-append plan type args dependencies) (define id (build-plan-next-id plan)) (define new-step (make-build-step id dependencies type args #f)) (set-build-plan-steps! plan (mapping-set! (build-plan-steps plan) id new-step)) id) (define known-build-types (mapping (make-default-comparator))) ;; Register a build step type. ;; A build step type's procedure is called (proc args . dependency-outputs), ;; and returns a single value, which is the result. ;; A batch build step is called as (proc (id args . dependency-outputs) (id args . dependency-outputs) ....) ;; and returns an alist of (id . result). (define (register-build-step name is-batch proc) (set! known-build-types (mapping-set! known-build-types name (make-build-step-type proc name is-batch)))) (define (build-plan-print plan) (printf "build plan! ~S steps to go, of which ~S ready to run (~S finalized)\n" (mapping-size (build-plan-steps plan)) (length (build-plan-ready-to-run plan)) (mapping-size (build-plan-finalized plan))) (mapping-for-each (lambda (step-id step) (define ready-to-run (member step (build-plan-ready-to-run plan))) (printf "- step ~S: ~S (ready to run? ~S)\n" step-id (build-step-type step) (not (eq? ready-to-run #f))) (printf " arguments: \n") (for-each (lambda (arg) (printf " - ~S\n" arg)) (build-step-arguments step)) (printf " dependencies: ~S\n\n" (build-step-dependencies step))) (build-plan-steps plan)) (mapping-for-each (lambda (step-id res) (printf "- result ~S: ~S\n" step-id res)) (build-plan-finalized plan)) (printf "\n")) (define build-plan-current-plan-inner (make-parameter #f)) (define build-plan-early-exit (make-parameter #f)) ;; Returns a handle to the current build plan, if inside a build step. (define (build-plan-current-plan) (define plan (build-plan-current-plan-inner)) (unless plan (error "cannot fetch current plan outside build plan context")) plan) ;; Tail call into a different build plan step. Arguments are similar to (build-plan-append). (define (build-plan-tail-call type args dependencies) (unless (build-plan-early-exit) (error "cannot build-plan-tail-call outside build plan context")) ((build-plan-early-exit) (list 'tail-call type args dependencies))) ;; Stops executing this build plan step, and appends dependencies. ;; If a second argument is given, the arguments to the current build step ;; are replaced wholesale. (define build-plan-return-append (case-lambda ((extra-dependencies new-args) (unless (build-plan-early-exit) (error "cannot build-plan-return-append outside build plan context")) ((build-plan-early-exit) (list 'append extra-dependencies new-args))) ((extra-dependencies) (build-plan-return-append extra-dependencies #f)))) ;; Run all ready-to-run build steps, and mark any build steps newly runnable as such. (define (build-plan-tick plan) (define (collect-build-step-args step) (append (build-step-arguments step) (map (lambda (v) (mapping-ref (build-plan-finalized plan) v)) (build-step-dependencies step)))) ;; Register the result of the build step and remove it from the list of pending steps. (define (handle-build-step-result step-id result) (set-build-plan-finalized! plan (mapping-set! (build-plan-finalized plan) step-id result)) (set-build-plan-steps! plan (mapping-delete! (build-plan-steps plan) step-id))) ;; Collect all steps we can run now. ;; These are grouped by build step type, for batching reasons. (define ready-to-run (build-plan-ready-to-run plan)) (set-build-plan-ready-to-run! plan '()) (define run-groupings (mapping (make-default-comparator))) ;; Collect the type of steps we can run right now. (for-each (lambda (step) (set! run-groupings (mapping-update!/default run-groupings (build-step-type step) (lambda (prev) (cons step prev)) '()))) ready-to-run) (define (run-build-step step-type step) (define result (call-with-current-continuation (lambda (cc) (list 'result (parameterize ((build-plan-early-exit cc) (build-plan-current-plan-inner plan)) (apply (build-step-type-proc step-type) (collect-build-step-args step))))))) (case (car result) ((result) (handle-build-step-result (build-step-id step) (list-ref result 1))) ((tail-call) (set-build-plan-steps! plan (mapping-set! (build-plan-steps plan) (build-step-id step) (make-build-step (build-step-id step) (list-ref result 3) (list-ref result 1) (list-ref result 2) (cons (build-plan-next-id plan) step))))) ((append) (set-build-plan-steps! plan (mapping-set! (build-plan-steps plan) (build-step-id step) (make-build-step (build-step-id step) (append (build-step-dependencies step) (list-ref result 1)) (build-step-type step) (or (list-ref result 2) (build-step-arguments step)) (cons (build-plan-next-id plan) step))))))) ;; Run all build steps that are ready to run. (mapping-for-each (lambda (step-type-name steps) (define step-type (mapping-ref known-build-types step-type-name)) (cond ((build-step-type-batch step-type) (parameterize ((build-plan-early-exit (lambda args (error "cannot early-exit from batch build step"))) (build-plan-current-plan-inner plan)) (for-each (lambda (result) (apply handle-build-step-result result)) (apply (build-step-type-proc step-type) (map collect-build-step-args steps))))) (else (for-each (lambda (step) (run-build-step step-type step)) steps)))) run-groupings) ;; Mark any build step that is ready to run as such. (mapping-for-each (lambda (step-id step) (define finalized (do ((args (build-step-dependencies step) (cdr args))) ((or (null? args) (not (mapping-contains? (build-plan-finalized plan) (car args)))) (null? args)))) (when finalized (set-build-plan-ready-to-run! plan (cons step (build-plan-ready-to-run plan))))) (build-plan-steps plan)) (cond ((mapping-empty? (build-plan-steps plan)) #t) ((and (null? ready-to-run) (null? (build-plan-ready-to-run plan))) (error "deadlock")) (else #f)))))