Added makefile.rkt
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
|
0.1.5
|
||||||
|
|
||||||
|
Added raco helper for invoking Racket tools from makefile recipes.
|
||||||
|
The helper prefers the raco executable from the current Racket installation,
|
||||||
|
then the directory of the current racket executable, and uses PATH only as a
|
||||||
|
fallback.
|
||||||
|
Added documentation and tests for raco command execution.
|
||||||
|
|
||||||
0.1.4
|
0.1.4
|
||||||
|
|
||||||
Replaced files-for-regexp, list-dir-re, list-dir-re-r, and filter-dirs with
|
Replaced files-for-regexp, list-dir-re, list-dir-re-r, and filter-dirs with
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#lang racket
|
||||||
|
|
||||||
|
(require "main.rkt"
|
||||||
|
racket/string)
|
||||||
|
|
||||||
|
(target all
|
||||||
|
(displayln "use (make clean) or (make package) or (make commit/push")
|
||||||
|
)
|
||||||
|
|
||||||
|
(target commit
|
||||||
|
(display "commit message:")
|
||||||
|
(flush-output)
|
||||||
|
(let ((line (read-line)))
|
||||||
|
(set! line (string-trim line))
|
||||||
|
(run '(git add -A))
|
||||||
|
(run (append '(git "commit" -m ) (list line))))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
`racket-makefile` is a small `#lang` for make-style builds. It keeps ordinary
|
`racket-makefile` is a small `#lang` for make-style builds. It keeps ordinary
|
||||||
Racket available and only adds targets, dependencies, timestamp based rebuilds,
|
Racket available and only adds targets, dependencies, timestamp based rebuilds,
|
||||||
phony targets, external command execution and a few cleanup helpers.
|
phony targets, external command execution, Racket tool execution and a few cleanup helpers.
|
||||||
|
|
||||||
A makefile only defines its targets. Loading or running the file does not build
|
A makefile only defines its targets. Loading or running the file does not build
|
||||||
anything by itself. Builds are started explicitly with `make`.
|
anything by itself. Builds are started explicitly with `make`.
|
||||||
@@ -40,7 +40,7 @@ raco pkg install
|
|||||||
For a versioned archive, give the package name explicitly:
|
For a versioned archive, give the package name explicitly:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
raco pkg install --name racket-makefile racket-makefile-0.1.4.zip
|
raco pkg install --name racket-makefile racket-makefile-0.1.5.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running targets
|
## Running targets
|
||||||
@@ -78,6 +78,31 @@ registers the targets. Then use the Interactions window:
|
|||||||
Bare identifiers in `make` are target names, so no quote is needed. For
|
Bare identifiers in `make` are target names, so no quote is needed. For
|
||||||
example, `(make clean)` selects the target named `clean`.
|
example, `(make clean)` selects the target named `clean`.
|
||||||
|
|
||||||
|
## Running Racket tools with `raco`
|
||||||
|
|
||||||
|
Use `raco` to invoke a Racket tool from a target without depending on `%PATH%`
|
||||||
|
or `$PATH`:
|
||||||
|
|
||||||
|
```racket
|
||||||
|
(phony setup test)
|
||||||
|
|
||||||
|
(target setup
|
||||||
|
(raco '(setup racket-makefile)))
|
||||||
|
|
||||||
|
(target test
|
||||||
|
(raco '(test -p racket-makefile)))
|
||||||
|
```
|
||||||
|
|
||||||
|
`racket-makefile` first looks for `raco` in the console binary directory of
|
||||||
|
the Racket installation that is currently running the makefile. It then tries
|
||||||
|
the directory containing the current `racket` executable and uses `PATH` only
|
||||||
|
as a final fallback. On Windows this normally finds `raco.exe` next to
|
||||||
|
`racket.exe` even when the Racket directory is not in `%PATH%`.
|
||||||
|
|
||||||
|
The argument rules are the same as for `run`: symbols, strings, paths and
|
||||||
|
numbers are converted to command-line arguments, nested lists are flattened,
|
||||||
|
and `$target`, `$deps` and `$<` are available inside a recipe.
|
||||||
|
|
||||||
## Regexp-based cleanup
|
## Regexp-based cleanup
|
||||||
|
|
||||||
For cleanup rules that need more control than globs, `racket-makefile` also
|
For cleanup rules that need more control than globs, `racket-makefile` also
|
||||||
@@ -98,7 +123,7 @@ so they can be passed directly to `rm-f` and `rm-rf` with `apply`:
|
|||||||
(apply rm-rf
|
(apply rm-rf
|
||||||
(list-dirs "." #px"compiled$" #:recursive #t))
|
(list-dirs "." #px"compiled$" #:recursive #t))
|
||||||
(apply rm-f
|
(apply rm-f
|
||||||
(list-files "." #px"(?i:(?:[.]bak|~)$)" #:recursive #t))
|
(list-files "." #px"(?i:([.]bak|~)$)" #:recursive #t))
|
||||||
(displayln "done."))
|
(displayln "done."))
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -131,7 +156,7 @@ with normal definitions and loops:
|
|||||||
Dependency expressions may produce lists; they are recursively flattened.
|
Dependency expressions may produce lists; they are recursively flattened.
|
||||||
|
|
||||||
The language adds `target`, `deps`, `phony`, `default-target`, `make`, `run`,
|
The language adds `target`, `deps`, `phony`, `default-target`, `make`, `run`,
|
||||||
`rm-f`, `rm-rf`, `cleanup`, `list-dir/files`, `list-files`, `list-dirs`,
|
`raco`, `rm-f`, `rm-rf`, `cleanup`, `list-dir/files`, `list-files`, `list-dirs`,
|
||||||
`$target`, `$deps` and `$<`. Everything else is ordinary Racket.
|
`$target`, `$deps` and `$<`. Everything else is ordinary Racket.
|
||||||
|
|
||||||
See the installed `racket-makefile` Scribble documentation for the full API.
|
See the installed `racket-makefile` Scribble documentation for the full API.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
(define CFLAGS '(-Wall -O2))
|
(define CFLAGS '(-Wall -O2))
|
||||||
|
|
||||||
(default-target all)
|
(default-target all)
|
||||||
(phony all clean)
|
(phony all clean setup test)
|
||||||
|
|
||||||
(target all
|
(target all
|
||||||
(deps "hello"))
|
(deps "hello"))
|
||||||
@@ -28,3 +28,10 @@
|
|||||||
(rm-f "hello")
|
(rm-f "hello")
|
||||||
(rm-rf "compiled")
|
(rm-rf "compiled")
|
||||||
(cleanup "scrbl" '("**.html" "**.js" "**.css")))
|
(cleanup "scrbl" '("**.html" "**.js" "**.css")))
|
||||||
|
|
||||||
|
|
||||||
|
(target setup
|
||||||
|
(raco '(setup racket-makefile)))
|
||||||
|
|
||||||
|
(target test
|
||||||
|
(raco '(test -p racket-makefile)))
|
||||||
|
|||||||
@@ -11,5 +11,5 @@
|
|||||||
(apply rm-rf
|
(apply rm-rf
|
||||||
(list-dirs "." #px"compiled$" #:recursive #t))
|
(list-dirs "." #px"compiled$" #:recursive #t))
|
||||||
(apply rm-f
|
(apply rm-f
|
||||||
(list-files "." #px"(?i:(?:[.]bak|~)$)" #:recursive #t))
|
(list-files "." #px"(?i:([.]bak|~)$)" #:recursive #t))
|
||||||
(displayln "done."))
|
(displayln "done."))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#lang info
|
#lang info
|
||||||
|
|
||||||
(define pkg-authors '(hnmdijkema))
|
(define pkg-authors '(hnmdijkema))
|
||||||
(define version "0.1.4")
|
(define version "0.1.5")
|
||||||
(define license 'MIT)
|
(define license 'MIT)
|
||||||
(define collection "racket-makefile")
|
(define collection "racket-makefile")
|
||||||
(define pkg-desc
|
(define pkg-desc
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
default-target
|
default-target
|
||||||
make
|
make
|
||||||
run
|
run
|
||||||
|
raco
|
||||||
rm-f
|
rm-f
|
||||||
rm-rf
|
rm-rf
|
||||||
cleanup
|
cleanup
|
||||||
|
|||||||
+62
-7
@@ -3,11 +3,14 @@
|
|||||||
(require file/glob
|
(require file/glob
|
||||||
racket/file
|
racket/file
|
||||||
racket/list
|
racket/list
|
||||||
|
racket/path
|
||||||
racket/string
|
racket/string
|
||||||
racket/system
|
racket/system
|
||||||
|
setup/dirs
|
||||||
"engine.rkt")
|
"engine.rkt")
|
||||||
|
|
||||||
(provide run
|
(provide run
|
||||||
|
raco
|
||||||
rm-f
|
rm-f
|
||||||
rm-rf
|
rm-rf
|
||||||
cleanup
|
cleanup
|
||||||
@@ -21,32 +24,33 @@
|
|||||||
(error who "~a is only available while a target recipe is running" description))
|
(error who "~a is only available while a target recipe is running" description))
|
||||||
value)
|
value)
|
||||||
|
|
||||||
(define (command-item->strings value)
|
(define (command-item->strings who value)
|
||||||
(cond
|
(cond
|
||||||
[(eq? value '$target)
|
[(eq? value '$target)
|
||||||
(list (recipe-value 'run current-target "$target"))]
|
(list (recipe-value who current-target "$target"))]
|
||||||
[(eq? value '$deps)
|
[(eq? value '$deps)
|
||||||
(recipe-value 'run current-dependencies "$deps")]
|
(recipe-value who current-dependencies "$deps")]
|
||||||
[(eq? value '$<)
|
[(eq? value '$<)
|
||||||
(define first (current-first-dependency))
|
(define first (current-first-dependency))
|
||||||
(unless first
|
(unless first
|
||||||
(error 'run "$< is not available: this target has no dependencies"))
|
(error who "$< is not available: this target has no dependencies"))
|
||||||
(list first)]
|
(list first)]
|
||||||
[(symbol? value) (list (symbol->string value))]
|
[(symbol? value) (list (symbol->string value))]
|
||||||
[(path? value) (list (path->string value))]
|
[(path? value) (list (path->string value))]
|
||||||
[(string? value) (list value)]
|
[(string? value) (list value)]
|
||||||
[(number? value) (list (number->string value))]
|
[(number? value) (list (number->string value))]
|
||||||
[(list? value) (append-map command-item->strings value)]
|
[(list? value) (append-map (lambda (item) (command-item->strings who item)) value)]
|
||||||
[else
|
[else
|
||||||
(raise-argument-error
|
(raise-argument-error
|
||||||
'run
|
who
|
||||||
"command item (string, symbol, path, number, or list)"
|
"command item (string, symbol, path, number, or list)"
|
||||||
value)]))
|
value)]))
|
||||||
|
|
||||||
(define (run command)
|
(define (run command)
|
||||||
(unless (list? command)
|
(unless (list? command)
|
||||||
(raise-argument-error 'run "list?" command))
|
(raise-argument-error 'run "list?" command))
|
||||||
(define arguments (append-map command-item->strings command))
|
(define arguments
|
||||||
|
(append-map (lambda (item) (command-item->strings 'run item)) command))
|
||||||
(when (null? arguments)
|
(when (null? arguments)
|
||||||
(error 'run "empty command"))
|
(error 'run "empty command"))
|
||||||
(define program (car arguments))
|
(define program (car arguments))
|
||||||
@@ -59,6 +63,57 @@
|
|||||||
(error 'run "command failed: ~a" program))
|
(error 'run "command failed: ~a" program))
|
||||||
(void))
|
(void))
|
||||||
|
|
||||||
|
|
||||||
|
(define (raco-executable-name)
|
||||||
|
(if (eq? (system-type 'os) 'windows)
|
||||||
|
"raco.exe"
|
||||||
|
"raco"))
|
||||||
|
|
||||||
|
(define (existing-raco-in directory)
|
||||||
|
(and directory
|
||||||
|
(let ([path (build-path directory (raco-executable-name))])
|
||||||
|
(and (file-exists? path) path))))
|
||||||
|
|
||||||
|
(define (find-raco-executable)
|
||||||
|
;; Prefer the console executable directory reported by the current Racket
|
||||||
|
;; installation. This does not depend on PATH and therefore also works for
|
||||||
|
;; typical Windows installations under Program Files.
|
||||||
|
(define console-bin
|
||||||
|
(with-handlers ([exn:fail? (lambda (_) #f)])
|
||||||
|
(find-console-bin-dir)))
|
||||||
|
(define from-console-bin
|
||||||
|
(existing-raco-in console-bin))
|
||||||
|
|
||||||
|
;; A useful fallback for portable/non-standard installations: raco normally
|
||||||
|
;; lives alongside the currently running racket executable.
|
||||||
|
(define exec-file
|
||||||
|
(with-handlers ([exn:fail? (lambda (_) #f)])
|
||||||
|
(find-system-path 'exec-file)))
|
||||||
|
(define from-exec-dir
|
||||||
|
(and exec-file
|
||||||
|
(existing-raco-in (path-only exec-file))))
|
||||||
|
|
||||||
|
;; PATH is intentionally last; it might refer to a different Racket install.
|
||||||
|
(or from-console-bin
|
||||||
|
from-exec-dir
|
||||||
|
(find-executable-path (raco-executable-name))
|
||||||
|
(error 'raco "cannot find raco for the current Racket installation")))
|
||||||
|
|
||||||
|
(define (raco command)
|
||||||
|
(unless (list? command)
|
||||||
|
(raise-argument-error 'raco "list?" command))
|
||||||
|
(define arguments
|
||||||
|
(append-map (lambda (item) (command-item->strings 'raco item)) command))
|
||||||
|
(when (null? arguments)
|
||||||
|
(error 'raco "empty command"))
|
||||||
|
(define executable (find-raco-executable))
|
||||||
|
(printf "> ~a ~a\n"
|
||||||
|
(path->string executable)
|
||||||
|
(string-join arguments " "))
|
||||||
|
(unless (apply system* executable arguments)
|
||||||
|
(error 'raco "command failed: ~a" (car arguments)))
|
||||||
|
(void))
|
||||||
|
|
||||||
(define (rm-f . paths)
|
(define (rm-f . paths)
|
||||||
(for ([path (in-list paths)])
|
(for ([path (in-list paths)])
|
||||||
(case (file-or-directory-type path #f)
|
(case (file-or-directory-type path #f)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
racket/file
|
racket/file
|
||||||
(only-in racket-makefile
|
(only-in racket-makefile
|
||||||
target deps phony default-target make
|
target deps phony default-target make
|
||||||
run rm-f rm-rf cleanup
|
run raco rm-f rm-rf cleanup
|
||||||
list-dir/files list-files list-dirs
|
list-dir/files list-files list-dirs
|
||||||
$target $deps $<)))
|
$target $deps $<)))
|
||||||
|
|
||||||
@@ -14,8 +14,7 @@
|
|||||||
@defmodulelang[racket-makefile]
|
@defmodulelang[racket-makefile]
|
||||||
|
|
||||||
@tt{racket-makefile} is a small make-style language built on Racket. It adds
|
@tt{racket-makefile} is a small make-style language built on Racket. It adds
|
||||||
targets, dependencies, timestamp based rebuilding, phony targets, command
|
targets, dependencies, timestamp based rebuilding, phony targets, command execution, Racket tool execution, and a few cleanup helpers. The rest of the language is ordinary
|
||||||
execution, and a few cleanup helpers. The rest of the language is ordinary
|
|
||||||
Racket.
|
Racket.
|
||||||
|
|
||||||
A makefile only defines targets. Loading or running the module does not execute
|
A makefile only defines targets. Loading or running the module does not execute
|
||||||
@@ -32,7 +31,7 @@ raco pkg install
|
|||||||
For a versioned archive, specify the package name explicitly:
|
For a versioned archive, specify the package name explicitly:
|
||||||
|
|
||||||
@verbatim{
|
@verbatim{
|
||||||
raco pkg install --name racket-makefile racket-makefile-0.1.4.zip
|
raco pkg install --name racket-makefile racket-makefile-0.1.5.zip
|
||||||
}
|
}
|
||||||
|
|
||||||
After installation a makefile can start with:
|
After installation a makefile can start with:
|
||||||
@@ -195,6 +194,36 @@ Ordinary Racket values can be inserted with quasiquote:
|
|||||||
(run `(,CC ,@CFLAGS -o $target $<)))
|
(run `(,CC ,@CFLAGS -o $target $<)))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@section{Running Racket tools}
|
||||||
|
|
||||||
|
@defproc[(raco [command list?]) void?]{
|
||||||
|
Runs a @tt{raco} command using the @tt{raco} executable that belongs to the
|
||||||
|
Racket installation currently running the makefile. The helper first checks
|
||||||
|
the console binary directory reported by the current Racket installation, then
|
||||||
|
the directory containing the current @tt{racket} executable, and only then
|
||||||
|
falls back to @tt{PATH}. This makes the helper useful on Windows installations
|
||||||
|
where @tt{raco.exe} is installed next to @tt{racket.exe} but is not on
|
||||||
|
@tt{%PATH%}.
|
||||||
|
|
||||||
|
The command syntax is the same as for @racket[run], except that the executable
|
||||||
|
is supplied automatically. Symbols, strings, paths and numbers are converted
|
||||||
|
to command-line arguments, nested lists are flattened, and the recipe values
|
||||||
|
@racket['$target], @racket['$deps], and @racket['$<] are supported. A non-zero
|
||||||
|
result raises an error.
|
||||||
|
}
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
(phony setup test)
|
||||||
|
|
||||||
|
(target setup
|
||||||
|
(raco '(setup racket-makefile)))
|
||||||
|
|
||||||
|
(target test
|
||||||
|
(raco '(test -p racket-makefile)))
|
||||||
|
]
|
||||||
|
|
||||||
@section{Cleanup helpers}
|
@section{Cleanup helpers}
|
||||||
|
|
||||||
@defproc[(rm-f [path path-string?] ...) void?]{
|
@defproc[(rm-f [path path-string?] ...) void?]{
|
||||||
@@ -271,7 +300,7 @@ These helpers make regexp-based cleanup concise. For example:
|
|||||||
(apply rm-rf
|
(apply rm-rf
|
||||||
(list-dirs "." #px"compiled$" #:recursive #t))
|
(list-dirs "." #px"compiled$" #:recursive #t))
|
||||||
(apply rm-f
|
(apply rm-f
|
||||||
(list-files "." #px"(?i:(?:[.]bak|~)$)" #:recursive #t))
|
(list-files "." #px"(?i:([.]bak|~)$)" #:recursive #t))
|
||||||
(displayln "done."))
|
(displayln "done."))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#lang racket
|
||||||
|
|
||||||
|
(require rackunit
|
||||||
|
(only-in racket-makefile raco))
|
||||||
|
|
||||||
|
(check-not-exn
|
||||||
|
(lambda ()
|
||||||
|
(raco '(help))))
|
||||||
|
|
||||||
|
;; It must not require raco to be on PATH. The current Racket installation
|
||||||
|
;; should locate its own raco first.
|
||||||
|
(define env (environment-variables-copy (current-environment-variables)))
|
||||||
|
(environment-variables-set! env #"PATH" #f)
|
||||||
|
(parameterize ([current-environment-variables env])
|
||||||
|
(check-not-exn
|
||||||
|
(lambda ()
|
||||||
|
(raco '(help)))))
|
||||||
Reference in New Issue
Block a user