Added makefile.rkt

This commit is contained in:
2026-08-09 00:32:36 +02:00
parent b563d359f0
commit 0f1c1da672
10 changed files with 181 additions and 19 deletions
+8
View File
@@ -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
+20
View File
@@ -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))))
)
+29 -4
View File
@@ -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.
+8 -1
View File
@@ -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)))
+1 -1
View File
@@ -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 -1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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)
+34 -5
View File
@@ -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."))
] ]
+17
View File
@@ -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)))))