racket-makefile
| #lang racket-makefile | package: racket-makefile |
racket-makefile is a small make-style language built on Racket. It adds targets, dependencies, timestamp based rebuilding, phony targets, command execution, Racket tool execution, and a few cleanup helpers. The rest of the language is ordinary Racket.
A makefile only defines targets. Loading or running the module does not execute any recipe automatically. Builds are started explicitly with make.
1 Installation
Install the package from a local checkout with:
raco pkg install |
For a versioned archive, specify the package name explicitly:
raco pkg install --name racket-makefile racket-makefile-0.1.5.zip |
After installation a makefile can start with:
#lang racket-makefile |
2 A first makefile
For example:
(define CC 'cc) (define CFLAGS '(-Wall -O2)) (default-target all) (phony all clean) (target all (deps "hello")) (target "hello" (deps "hello.c") (run `(,CC ,@CFLAGS -o $target $<))) (target clean (rm-f "hello") (rm-rf "compiled") (cleanup "scrbl" '("**.html" "**.js" "**.css")))
Loading the file only registers these targets; it does not build all or any other target.
3 Executing targets
syntax
(make name ...)
With no arguments, (make) builds the target selected by default-target. If no default target has been specified, the first declared target is used.
Multiple targets are allowed and are processed in the supplied order, for example (make clean all). Dependencies that are shared by multiple selected targets are built only once during that make invocation.
From the command line, load the makefile with -t and evaluate a make form with -e:
racket -t Makefile.rkt -e "(make)" |
racket -t Makefile.rkt -e "(make all)" |
racket -t Makefile.rkt -e "(make clean)" |
racket -t Makefile.rkt -e "(make clean all)" |
Loading a makefile without -e executes no target:
racket -t Makefile.rkt |
In DrRacket, open the makefile and press Run. This loads and registers the targets. Then execute targets in the Interactions window:
> (make) |
> (make clean) |
> (make all) |
> (make clean all) |
4 Targets and dependencies
syntax
(target name (deps dependency ...) body ...)
An unbound identifier is treated as a literal symbol. Consequently, (target clean ...) defines the target 'clean, while a bound identifier can be used to generate targets from ordinary Racket code. If a literal target name happens to be bound by Racket, quote it explicitly, for example (target 'compile ...).
The body is not evaluated when the target is declared. It is saved as the target recipe and evaluated only when the target must be rebuilt.
syntax
(deps dependency ...)
syntax
(phony name ...)
syntax
(default-target name)
A non-phony target is rebuilt when its output does not exist or when a dependency is newer than the target. Registered target dependencies are built first. A dependency that is neither a registered target nor an existing file is an error. Dependency cycles are reported as errors.
5 Recipe context
Inside a target recipe the following identifiers are available:
syntax
syntax
syntax
The same names can occur as symbols inside a quoted command passed to run.
6 Running commands
The symbols '$target, '$deps, and '$< are expanded from the current recipe. '$deps is spliced into the command.
A non-zero command result raises an error.
For example:
(target "hello" (deps "hello.c") (run '(cc -Wall -O2 -o $target $<)))
Ordinary Racket values can be inserted with quasiquote:
(define CC 'cc) (define CFLAGS '(-Wall -O2)) (target "hello" (deps "hello.c") (run `(,CC ,@CFLAGS -o $target $<)))
7 Running Racket tools
The command syntax is the same as for 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 '$target, '$deps, and '$< are supported. A non-zero result raises an error.
For example:
(phony setup test) (target setup (raco '(setup racket-makefile))) (target test (raco '(test -p racket-makefile)))
8 Cleanup helpers
procedure
path : path-string?
procedure
path : path-string?
procedure
directory : path-string? patterns : list?
For example:
(target clean (rm-rf "compiled") (cleanup "scrbl" '("**.html" "**.js" "**.css")))
9 Regexp directory helpers
procedure
(list-dir/files directory regexp [ #:recursive recursive]) → list? directory : path-string? regexp : regexp? recursive : any/c = #f
For a non-recursive listing, complete paths are built from directory, so the result can be passed directly to file operations such as rm-f and rm-rf.
procedure
(list-files directory regexp [ #:recursive recursive]) → list? directory : path-string? regexp : regexp? recursive : any/c = #f
procedure
(list-dirs directory regexp [ #:recursive recursive]) → list? directory : path-string? regexp : regexp? recursive : any/c = #f
These helpers make regexp-based cleanup concise. For example:
(default-target all) (phony all clean) (target all (displayln "use (make clean)")) (target clean (display "cleaning up...") (apply rm-rf (list-dirs "." #px"compiled$" #:recursive #t)) (apply rm-f (list-files "." #px"(?i:([.]bak|~)$)" #:recursive #t)) (displayln "done."))
The second regular expression is case-insensitive and matches names ending in .bak or ~.
10 Using ordinary Racket
No separate make programming language is introduced. Definitions, functions, loops, conditionals, modules, and Racket libraries remain available. For example, targets can be generated in a loop:
(define sources '("foo.c" "bar.c" "baz.c")) (define objects (for/list ([src sources]) (define obj (path-replace-extension src #".o")) (target obj (deps src) (run `(cc -c $< -o $target))) obj)) (target all (deps objects))
The expression objects evaluates to a list of dependencies. Dependency lists are recursively flattened by racket-makefile.
11 Package example
The optional package-zipper package combines naturally with racket-makefile:
(require package-zipper) (default-target package) (phony clean package) (target clean (rm-rf "compiled") (cleanup "scrbl" '("**.html" "**.js" "**.css"))) (target package (zip-package))
The package target can then be executed explicitly, for example:
racket -t Makefile.rkt -e "(make package)" |