#lang racket (require rackunit racket/file racket/runtime-path racket/system) (define racket-executable (find-system-path 'exec-file)) (define tmp (make-temporary-file "racket-makefile-refresh~a" 'directory)) (define (write-lines path . lines) (call-with-output-file path #:exists 'truncate/replace (lambda (out) (for ([line (in-list lines)]) (displayln line out))))) (dynamic-wind void (lambda () ;; This module records how often it is instantiated. A refresh must reuse ;; the existing module instance instead of loading it a second time. (write-lines (build-path tmp "slow.rkt") "#lang racket/base" "(require racket/file)" "(define count-file \"slow-count.txt\")" "(define count (if (file-exists? count-file) (string->number (file->string count-file)) 0))" "(call-with-output-file count-file #:exists 'truncate/replace (lambda (out) (display (add1 count) out)))" "(provide slow-value)" "(define slow-value \"loaded\")") (write-lines (build-path tmp "Makefile.rkt") "#lang racket-makefile" "(require \"slow.rkt\")" "(phony change)" "(target change (copy-file \"NewMakefile.rkt\" \"Makefile.rkt\" #t))") (write-lines (build-path tmp "NewMakefile.rkt") "#lang racket-makefile" "(require \"slow.rkt\")" "(phony changed)" "(target changed" " (call-with-output-file \"result.txt\" #:exists 'truncate/replace" " (lambda (out) (display slow-value out))))") (parameterize ([current-directory tmp]) (check-true (system* racket-executable "-t" "Makefile.rkt" "-e" "(make change)" "-e" "(refresh-makefile)" "-e" "(make changed)"))) (check-equal? (file->string (build-path tmp "result.txt")) "loaded") (check-equal? (file->string (build-path tmp "slow-count.txt")) "1") (check-equal? (filter (lambda (path) (regexp-match? #rx"^racket-makefile-refresh.*[.]rkt$" (path->string (file-name-from-path path)))) (directory-list tmp #:build? #t)) '())) (lambda () (delete-directory/files tmp #:must-exist? #f))) ;; A makefile can also use ordinary #lang racket and require main.rkt directly. ;; This is the style used by racket-makefile's own development Makefile. The ;; declaration macros must remember the source file in that case, because the ;; custom racket-makefile #%module-begin is not involved. (define direct-tmp (make-temporary-file "racket-makefile-direct-refresh~a" 'directory)) (define-runtime-path package-main "../main.rkt") (define-runtime-path package-commands "../private/commands.rkt") (define-runtime-path package-engine "../private/engine.rkt") (dynamic-wind void (lambda () (make-directory (build-path direct-tmp "private")) (copy-file package-main (build-path direct-tmp "main.rkt")) (copy-file package-commands (build-path direct-tmp "private" "commands.rkt")) (copy-file package-engine (build-path direct-tmp "private" "engine.rkt")) (write-lines (build-path direct-tmp "slow.rkt") "#lang racket/base" "(require racket/file)" "(define count-file \"slow-count.txt\")" "(define count (if (file-exists? count-file) (string->number (file->string count-file)) 0))" "(call-with-output-file count-file #:exists 'truncate/replace (lambda (out) (display (add1 count) out)))" "(provide slow-value)" "(define slow-value \"loaded\")") (write-lines (build-path direct-tmp "Makefile.rkt") "#lang racket" "(require \"main.rkt\" \"slow.rkt\")" "(phony change old)" "(target old (displayln \"old\"))" "(target change (copy-file \"NewMakefile.rkt\" \"Makefile.rkt\" #t))") (write-lines (build-path direct-tmp "NewMakefile.rkt") "#lang racket" "(require \"main.rkt\" \"slow.rkt\")" "(phony changed)" "(target changed" " (call-with-output-file \"result.txt\" #:exists 'truncate/replace" " (lambda (out) (display slow-value out))))") (write-lines (build-path direct-tmp "driver.rkt") "#lang racket" "(require \"main.rkt\")" "(dynamic-require \"Makefile.rkt\" #f)" "(make change)" "(refresh-makefile)" "(make changed)" "(with-handlers ([exn:fail? (lambda (e) (displayln \"old target removed\"))])" " (make old)" " (error 'refresh-test \"old target survived refresh\"))") (parameterize ([current-directory direct-tmp]) (check-true (system* racket-executable "driver.rkt"))) (check-equal? (file->string (build-path direct-tmp "result.txt")) "loaded") (check-equal? (file->string (build-path direct-tmp "slow-count.txt")) "1")) (lambda () (delete-directory/files direct-tmp #:must-exist? #f)))