Files
racket-makefile/scrbl/racket-makefile.scrbl
T

275 lines
7.4 KiB
Racket

#lang scribble/manual
@(require (for-label racket/base
racket/file
(only-in racket-makefile
target deps phony default-target make
run rm-f rm-rf cleanup
$target $deps $<)))
@title{racket-makefile}
@author{Hans Dijkema}
@defmodulelang[racket-makefile]
@tt{racket-makefile} is a small make-style language built on Racket. It adds
targets, dependencies, timestamp based rebuilding, phony targets, command
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 @racket[make].
@section{Installation}
Install the package from a local checkout with:
@verbatim{
raco pkg install
}
For a versioned archive, specify the package name explicitly:
@verbatim{
raco pkg install --name racket-makefile racket-makefile-0.1.2.zip
}
After installation a makefile can start with:
@verbatim{
#lang racket-makefile
}
@section{A first makefile}
For example:
@racketblock[
(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 @tt{all} or
any other target.
@section{Executing targets}
@defform[(make name ...)]{
Builds the supplied targets. A bare identifier is always interpreted as a
literal target name, so @racket[(make clean)] selects the target named
@racket['clean] and does not require quoting.
With no arguments, @racket[(make)] builds the target selected by
@racket[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 @racket[(make clean all)]. Dependencies that are shared by multiple
selected targets are built only once during that @racket[make] invocation.
}
From the command line, load the makefile with @tt{-t} and evaluate a
@racket[make] form with @tt{-e}:
@verbatim{
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 @tt{-e} executes no target:
@verbatim{
racket -t Makefile.rkt
}
In DrRacket, open the makefile and press @bold{Run}. This loads and registers
the targets. Then execute targets in the Interactions window:
@verbatim{
> (make)
> (make clean)
> (make all)
> (make clean all)
}
@section{Targets and dependencies}
@defform[(target name (deps dependency ...) body ...)]{
Defines a target. @racket[name] can be a symbol, string, path, or a Racket
expression producing one of those values. Each @racket[dependency] can also
produce a list; dependency lists are recursively flattened.
An unbound identifier is treated as a literal symbol. Consequently,
@racket[(target clean ...)] defines the target @racket['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 @racket[(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.
}
@defform[(deps dependency ...)]{
Specifies the dependencies of a target. Dependency expressions that produce
lists are automatically spliced into the dependency list. @racket[deps] is only
valid directly inside @racket[target].
}
@defform[(phony name ...)]{
Marks targets as phony. A phony target is always executed when requested or
when reached as a dependency.
}
@defform[(default-target name)]{
Selects the target used by @racket[(make)] when no target is supplied. If no
default target is specified, the first declared target is used.
}
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.
@section{Recipe context}
Inside a target recipe the following identifiers are available:
@defidform[$target]{The current target name.}
@defidform[$deps]{A list containing all dependencies of the current target.}
@defidform[$<]{The first dependency of the current target. An error is raised
when the target has no dependencies.}
The same names can occur as symbols inside a quoted command passed to
@racket[run].
@section{Running commands}
@defproc[(run [command list?]) void?]{
Runs an external command directly, without an intermediate shell. The first
item is the executable and the remaining items are arguments. Symbols are
converted to strings. Nested lists are flattened, which makes Racket lists of
flags convenient to use.
The symbols @racket['$target], @racket['$deps], and @racket['$<] are expanded
from the current recipe. @racket['$deps] is spliced into the command.
A non-zero command result raises an error.
}
For example:
@racketblock[
(target "hello"
(deps "hello.c")
(run '(cc -Wall -O2 -o $target $<)))
]
Ordinary Racket values can be inserted with quasiquote:
@racketblock[
(define CC 'cc)
(define CFLAGS '(-Wall -O2))
(target "hello"
(deps "hello.c")
(run `(,CC ,@CFLAGS -o $target $<)))
]
@section{Cleanup helpers}
@defproc[(rm-f [path path-string?] ...) void?]{
Removes files when they exist. Missing files are ignored. Directories are not
removed; use @racket[rm-rf] for those.
}
@defproc[(rm-rf [path path-string?] ...) void?]{
Removes files or directory trees recursively. Missing paths are ignored.
}
@defproc[(cleanup [directory path-string?]
[patterns list?])
void?]{
Removes files below @racket[directory] that match the supplied Racket glob
patterns. Directories themselves are left in place. A pattern containing
@tt{**} searches recursively.
}
For example:
@racketblock[
(target clean
(rm-rf "compiled")
(cleanup "scrbl"
'("**.html"
"**.js"
"**.css")))
]
@section{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:
@racketblock[
(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 @racket[objects] evaluates to a list of dependencies. Dependency
lists are recursively flattened by @tt{racket-makefile}.
@section{Package example}
The optional @hyperlink["https://docs.racket-lang.org/package-zipper/index.html"]{@tt{package-zipper}}
package combines naturally with @tt{racket-makefile}:
@racketblock[
(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:
@verbatim{
racket -t Makefile.rkt -e "(make package)"
}