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

221 lines
9.8 KiB
Racket

#lang scribble/manual
@(require (for-label racket
racket/file
racket-makefile))
@title{racket-makefile}
@author["Hans Dijkema / hans@dijkewijk.nl"]
@defmodule[racket-makefile]
@tt{racket-makefile} provides make-style dependency builds as ordinary Racket functionality. The package has no Rash dependency. Rash integration is provided by the separate @tt{rash-makefile} package.
@section{A functional makefile}
A makefile is ordinary Racket code inside @racket[makefile]. The prefix and static symbolic target names are explicit values:
@racketblock[
(require racket-makefile)
(makefile 'wiki
(default-target 'all)
(phony 'status 'clean 'all)
(target 'status
(displayln "status"))
(target 'clean
(rm-rf "compiled"))
(target 'all
(deps 'status)
(displayln "all")))
]
A statically quoted target name defines a real Racket procedure binding. The example defines @racket[makefile-target-wiki-status], @racket[makefile-target-wiki-clean], and @racket[makefile-target-wiki-all]. They can be inspected or called like any other procedure.
@racketblock[
(procedure? makefile-target-wiki-status)
(makefile-target-wiki-status)
]
Calling a generated target procedure directly executes the recipe directly. Calling the target through @racket[make] adds dependency traversal and timestamp based rebuilding.
@section{Makefile definitions}
@defform[(makefile prefix form ...)]{
Defines one prefixed makefile and evaluates @racket[form ...] as ordinary Racket code in the lexical context of that makefile. The @racket[prefix] is explicit, normally a quoted symbol such as @racket['wiki].
Before the body is evaluated, registrations for the same prefix are removed. After the complete body has been evaluated, @racket[current-makefile-prefix] is set to the prefix. Consequently the last evaluated @racket[makefile] form becomes the active makefile.
Ordinary definitions, loops, conditionals, and other Racket forms may occur in the body. The declaration forms @racket[target], @racket[phony], and @racket[default-target] obtain the surrounding prefix lexically and can therefore also occur inside nested Racket forms.
}
@defform[(target name (deps dependency ...) body ...)]{
Registers one target procedure for the surrounding makefile. The @racket[name] is an ordinary Racket expression.
When @racket[name] is a quoted static value, for example @racket['status], the macro also defines a normal Racket procedure binding. In a makefile with prefix @racket['wiki], @racket[(target 'status ...)] defines @racket[makefile-target-wiki-status].
When @racket[name] is an expression such as a variable, the expression is evaluated while the surrounding makefile body runs. The resulting procedure is registered dynamically and closes over the lexical values used by its recipe. This allows ordinary loops to generate targets.
Dependency expressions are evaluated when the target is registered. A dependency expression may produce nested lists; the build engine flattens them.
}
A target without a @racket[deps] clause has no dependencies.
@defform[(deps dependency ...)]{
Specifies target dependencies. Each dependency is an ordinary Racket expression. The form is valid only as the dependency clause of @racket[target].
}
@defform[(phony name ...)]{
Marks the values produced by @racket[name ...] as phony for the surrounding makefile prefix. A value may also be a list of target names. A phony target is always executed when requested or reached as a dependency.
}
@defform[(default-target name)]{
Selects the target value produced by @racket[name] for @racket[(make)] in the surrounding makefile prefix. If no default is specified, the first registered target is used.
}
@section{Generated targets}
Because @racket[makefile] is a lexical context instead of a fixed list of clauses, targets can be generated by ordinary Racket code:
@racketblock[
(makefile 'stuff
(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))
(default-target 'all)
(phony 'all 'clean)
(target 'all
(deps objects))
(target 'clean
(apply rm-f objects)))
]
The loop registers a separate target procedure for each object file. Each procedure closes over the corresponding @racket[src] and @racket[obj] values. The quoted static targets still define the normal bindings @racket[makefile-target-stuff-all] and @racket[makefile-target-stuff-clean].
@section{Executing targets}
@defproc[(make [name (or/c symbol? path-string?)] ...) void?]{
Builds the supplied targets in the makefile selected by @racket[current-makefile-prefix]. @racket[make] is an ordinary procedure, so symbolic target names are quoted explicitly.
With no arguments, the configured default target is used. If no explicit default exists, the first target of the active makefile is used. Multiple supplied targets are processed in order, and shared dependencies are built once during one @racket[make] call.
For a target that needs rebuilding, the engine looks up the registered target procedure for the active prefix and calls it.
}
@defthing[current-makefile-prefix parameter?]{
A parameter containing the active makefile prefix, or @racket[#f] before any makefile has been evaluated. Evaluating @racket[makefile] sets the parameter persistently to that makefile's prefix.
Another registered makefile can be selected explicitly:
@racketblock[
(current-makefile-prefix 'wiki)
(make 'status)
]
}
@section{Inspecting registered makefiles}
@defproc[(makefile-prefixes) list?]{
Returns the registered makefile prefixes in evaluation order. The original prefix values are preserved.
}
@defproc[(makefile-targets [prefix (or/c symbol? path-string?) (current-makefile-prefix)]) list?]{
Returns the targets registered for @racket[prefix] in registration order. The original target values are preserved, so symbolic targets are returned as symbols and dynamically generated path targets are returned as paths.
}
@defproc[(makefile-target-exists? [name (or/c symbol? path-string?)]) boolean?]{
Returns whether @racket[name] exists in the active makefile. To inspect another prefix, use @racket[(makefile-target-exists? prefix name)].
}
@defproc[(makefile-target-procedure [name (or/c symbol? path-string?)]) procedure?]{
Returns the registered target procedure for @racket[name] in the active makefile. To inspect another prefix, use @racket[(makefile-target-procedure prefix name)]. An unknown target raises an exception.
}
@section{Rash integration}
Rash integration is intentionally provided by the separate @tt{rash-makefile} package so that @tt{racket-makefile} remains a pure Racket dependency.
@section{Recipe context}
@defidform[$target]{The current target name while a generated target procedure is running.}
@defidform[$deps]{The flattened list of dependencies of the current target.}
@defidform[$<]{The first dependency of the current target. An error is raised when the target has no dependencies.}
The generated procedure establishes this context even when called directly.
@section{Dependency processing}
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 in the same prefix nor an existing file is an error. Dependency cycles are reported as errors.
Multiple makefile prefixes can remain registered simultaneously. Dependency lookup stays within the prefix of the target being built.
@section{Refreshing definitions}
@defproc[(refresh-makefile) void?]{
Reloads the source module containing the most recently evaluated @racket[makefile] form. Existing makefile registrations are cleared before the source is evaluated again. Modules already instantiated in the current namespace are reused.
In DrRacket, pressing @bold{Run} is usually the simpler way to reevaluate a Racket or Rash makefile.
}
@section{Running commands}
@defproc[(run [command list?]) void?]{
Runs an external command directly without an intermediate shell. Symbols, strings, paths, numbers, and nested lists are converted to command-line arguments. A non-zero result raises an error.
The symbols @racket['$target], @racket['$deps], and @racket['$<] are expanded from the current target context when they occur in the command list.
}
@defproc[(raco [command list?]) void?]{
Runs @tt{raco} from the active Racket installation. The command is supplied as a list, for example @racket[(raco '(setup racket-makefile))]. The helper first looks in the console executable directory of the current Racket installation, then next to the running Racket executable, and only then on @tt{PATH}. A non-zero result raises an error.
}
@section{Cleanup helpers}
@defproc[(rm-f [path path-string?] ...) void?]{
Removes files when they exist. Missing files are ignored. Directories are rejected.
}
@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.
}
@defproc[(list-dir/files [directory path-string?]
[regexp regexp?]
[#:recursive recursive any/c #f])
list?]{
Returns matching files and directories. The regular expression is applied to each file or directory name, not to the complete path.
}
@defproc[(list-files [directory path-string?]
[regexp regexp?]
[#:recursive recursive any/c #f])
list?]{
Like @racket[list-dir/files], but keeps only files.
}
@defproc[(list-dirs [directory path-string?]
[regexp regexp?]
[#:recursive recursive any/c #f])
list?]{
Like @racket[list-dir/files], but keeps only directories.
}