Made it possible to use (refresh-makefile) in order to refresh an edited makefile on the fly.
This commit is contained in:
@@ -1,3 +1,17 @@
|
||||
0.1.8
|
||||
|
||||
Fixed refresh-makefile for ordinary #lang racket makefiles that require main.rkt
|
||||
directly. Target declarations now remember their source makefile, and refresh
|
||||
explicitly resets the registered target state before loading the edited file.
|
||||
Removed targets therefore disappear after refresh as expected.
|
||||
|
||||
0.1.7
|
||||
|
||||
Added refresh-makefile for reloading the current Makefile.rkt from an existing
|
||||
Racket/DrRacket interaction session. The makefile source path is kept in a
|
||||
simple module-level variable, so expensive unchanged required modules can stay
|
||||
loaded while makefile target definitions are refreshed.
|
||||
|
||||
0.1.6
|
||||
|
||||
Fixed the raco helper test for the Racket package build service: raco help
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
(dgit 'push))
|
||||
|
||||
(target status
|
||||
(displayln "git status:")
|
||||
(void (dgit 'status))
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ raco pkg install
|
||||
For a versioned archive, give the package name explicitly:
|
||||
|
||||
```text
|
||||
raco pkg install --name racket-makefile racket-makefile-0.1.6.zip
|
||||
raco pkg install --name racket-makefile racket-makefile-0.1.8.zip
|
||||
```
|
||||
|
||||
## Running targets
|
||||
@@ -78,6 +78,19 @@ registers the targets. Then use the Interactions window:
|
||||
Bare identifiers in `make` are target names, so no quote is needed. For
|
||||
example, `(make clean)` selects the target named `clean`.
|
||||
|
||||
After editing the makefile, use `refresh-makefile` in the same Interactions
|
||||
window to reload the makefile without restarting the Racket process:
|
||||
|
||||
```racket
|
||||
> (refresh-makefile)
|
||||
> (make all)
|
||||
```
|
||||
|
||||
This works both with `#lang racket-makefile` and with an ordinary `#lang racket`
|
||||
makefile that requires `main.rkt`. It is useful when the makefile requires modules
|
||||
that are expensive to load. Unchanged required modules can remain instantiated
|
||||
while the target definitions from the makefile are registered again.
|
||||
|
||||
## Running Racket tools with `raco`
|
||||
|
||||
Use `raco` to invoke a Racket tool from a target without depending on `%PATH%`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#lang info
|
||||
|
||||
(define pkg-authors '(hnmdijkema))
|
||||
(define version "0.1.6")
|
||||
(define version "0.1.8")
|
||||
(define license 'MIT)
|
||||
(define collection "racket-makefile")
|
||||
(define pkg-desc
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
phony
|
||||
default-target
|
||||
make
|
||||
refresh-makefile
|
||||
run
|
||||
raco
|
||||
rm-f
|
||||
@@ -29,6 +30,47 @@
|
||||
)
|
||||
;(all-from-out git))
|
||||
|
||||
|
||||
;; The makefile module currently loaded in this Racket process. Keeping only
|
||||
;; its source path is enough: target state lives in the engine and is rebuilt
|
||||
;; when the makefile module is evaluated again.
|
||||
(define loaded-makefile #f)
|
||||
|
||||
(define (remember-makefile! path)
|
||||
(set! loaded-makefile path)
|
||||
(void))
|
||||
|
||||
(define-for-syntax (remember-source-form stx)
|
||||
(define source (syntax-source stx))
|
||||
(if (path? source)
|
||||
#`(remember-makefile! (string->path #,(path->string source)))
|
||||
#'(void)))
|
||||
|
||||
(define (refresh-makefile)
|
||||
(unless loaded-makefile
|
||||
(error 'refresh-makefile "no racket-makefile has been loaded"))
|
||||
(define source loaded-makefile)
|
||||
(define directory (or (path-only source) (current-directory)))
|
||||
;; Load the edited makefile as a fresh module in the same namespace. Its
|
||||
;; required modules therefore reuse their existing module instances, while
|
||||
;; the makefile body itself runs again and re-registers all targets.
|
||||
(define refresh-source
|
||||
(make-temporary-file "racket-makefile-refresh~a.rkt" #f directory))
|
||||
(copy-file source refresh-source #t)
|
||||
;; With #lang racket, main.rkt is only required and our custom module-begin
|
||||
;; does not run. Reset explicitly so removed/renamed targets disappear too.
|
||||
(reset-makefile!)
|
||||
(dynamic-wind
|
||||
void
|
||||
(lambda ()
|
||||
(dynamic-require refresh-source #f))
|
||||
(lambda ()
|
||||
;; Loading the temporary copy also runs remember-makefile!. Keep the
|
||||
;; original source as the file to use for the next refresh.
|
||||
(set! loaded-makefile source)
|
||||
(delete-file refresh-source)))
|
||||
(void))
|
||||
|
||||
;; For target declarations we want a bound identifier to remain an ordinary
|
||||
;; Racket expression. This makes generated targets such as (target obj ...)
|
||||
;; possible inside a for loop. An unbound identifier is a literal target name.
|
||||
@@ -51,10 +93,12 @@
|
||||
[(_ form ...)
|
||||
#'(racket-module-begin
|
||||
(reset-makefile!)
|
||||
(remember-makefile!
|
||||
(variable-reference->module-source (#%variable-reference)))
|
||||
form ...
|
||||
;; Re-export make so that `racket -t Makefile.rkt -e "(make all)"`
|
||||
;; imports the make form into the command-line evaluation namespace.
|
||||
(provide make quote #%top-interaction #%app #%datum #%top))]))
|
||||
(provide make refresh-makefile quote #%top-interaction #%app #%datum #%top))]))
|
||||
|
||||
(define-syntax (deps stx)
|
||||
(raise-syntax-error 'deps "only valid as the dependency clause of target" stx))
|
||||
@@ -66,30 +110,42 @@
|
||||
[(target-dependency ...)
|
||||
(map literal-name-or-expression
|
||||
(syntax->list #'(dependency ...)))])
|
||||
#'(register-target!
|
||||
(with-syntax ([remember-source (remember-source-form stx)])
|
||||
#'(begin
|
||||
remember-source
|
||||
(register-target!
|
||||
target-name
|
||||
(list target-dependency ...)
|
||||
(lambda () body ... (void))))]
|
||||
(lambda () body ... (void))))))]
|
||||
[(_ name body ...)
|
||||
(with-syntax ([target-name (literal-name-or-expression #'name)])
|
||||
#'(register-target!
|
||||
(with-syntax ([target-name (literal-name-or-expression #'name)]
|
||||
[remember-source (remember-source-form stx)])
|
||||
#'(begin
|
||||
remember-source
|
||||
(register-target!
|
||||
target-name
|
||||
'()
|
||||
(lambda () body ... (void))))]))
|
||||
(lambda () body ... (void)))))]))
|
||||
|
||||
(define-syntax (phony stx)
|
||||
(syntax-case stx ()
|
||||
[(_ name ...)
|
||||
(with-syntax ([(target-name ...)
|
||||
(map literal-name-or-expression
|
||||
(syntax->list #'(name ...)))])
|
||||
#'(mark-phony! target-name ...))]))
|
||||
(syntax->list #'(name ...)))]
|
||||
[remember-source (remember-source-form stx)])
|
||||
#'(begin
|
||||
remember-source
|
||||
(mark-phony! target-name ...)))]))
|
||||
|
||||
(define-syntax (default-target stx)
|
||||
(syntax-case stx ()
|
||||
[(_ name)
|
||||
(with-syntax ([target-name (literal-name-or-expression #'name)])
|
||||
#'(set-default-target! target-name))]))
|
||||
(with-syntax ([target-name (literal-name-or-expression #'name)]
|
||||
[remember-source (remember-source-form stx)])
|
||||
#'(begin
|
||||
remember-source
|
||||
(set-default-target! target-name)))]))
|
||||
|
||||
(define-syntax (make stx)
|
||||
(syntax-case stx ()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@(require (for-label racket/base
|
||||
racket/file
|
||||
(only-in racket-makefile
|
||||
target deps phony default-target make
|
||||
target deps phony default-target make refresh-makefile
|
||||
run raco rm-f rm-rf cleanup
|
||||
list-dir/files list-files list-dirs
|
||||
$target $deps $<)))
|
||||
@@ -31,7 +31,7 @@ raco pkg install
|
||||
For a versioned archive, specify the package name explicitly:
|
||||
|
||||
@verbatim{
|
||||
raco pkg install --name racket-makefile racket-makefile-0.1.6.zip
|
||||
raco pkg install --name racket-makefile racket-makefile-0.1.8.zip
|
||||
}
|
||||
|
||||
After installation a makefile can start with:
|
||||
@@ -109,6 +109,22 @@ the targets. Then execute targets in the Interactions window:
|
||||
> (make clean all)
|
||||
}
|
||||
|
||||
After editing the makefile, reload its target definitions in the same Racket
|
||||
process with:
|
||||
|
||||
@verbatim{
|
||||
> (refresh-makefile)
|
||||
> (make all)
|
||||
}
|
||||
|
||||
@defproc[(refresh-makefile) void?]{
|
||||
Reloads the most recently loaded @tt{racket-makefile} source file and registers
|
||||
its targets again without restarting the Racket process. This works for both
|
||||
@tt{#lang racket-makefile} and ordinary @tt{#lang racket} makefiles that require
|
||||
@tt{main.rkt}. Modules required by the makefile that are already instantiated
|
||||
are reused. This is useful for makefiles that require expensive libraries.
|
||||
}
|
||||
|
||||
@section{Targets and dependencies}
|
||||
|
||||
@defform[(target name (deps dependency ...) body ...)]{
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#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)))
|
||||
Reference in New Issue
Block a user