racket-makefile
| (require racket-makefile) | package: racket-makefile |
racket-makefile provides make-style dependency builds as ordinary Racket functionality. The package has no Rash dependency. Rash integration is provided by the separate rash-makefile package.
1 A functional makefile
A makefile is ordinary Racket code inside makefile. The prefix and static symbolic target names are explicit values:
(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 makefile-target-wiki-status, makefile-target-wiki-clean, and makefile-target-wiki-all. They can be inspected or called like any other procedure.
(procedure? makefile-target-wiki-status) (makefile-target-wiki-status)
Calling a generated target procedure directly executes the recipe directly. Calling the target through make adds dependency traversal and timestamp based rebuilding.
2 Makefile definitions
syntax
(makefile prefix form ...)
Before the body is evaluated, registrations for the same prefix are removed. After the complete body has been evaluated, current-makefile-prefix is set to the prefix. Consequently the last evaluated makefile form becomes the active makefile.
Ordinary definitions, loops, conditionals, and other Racket forms may occur in the body. The declaration forms target, phony, and default-target obtain the surrounding prefix lexically and can therefore also occur inside nested Racket forms.
syntax
(target name (deps dependency ...) body ...)
When name is a quoted static value, for example 'status, the macro also defines a normal Racket procedure binding. In a makefile with prefix 'wiki, (target 'status ...) defines makefile-target-wiki-status.
When 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 deps clause has no dependencies.
syntax
(deps dependency ...)
syntax
(phony name ...)
syntax
(default-target name)
3 Generated targets
Because makefile is a lexical context instead of a fixed list of clauses, targets can be generated by ordinary Racket code:
(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 src and obj values. The quoted static targets still define the normal bindings makefile-target-stuff-all and makefile-target-stuff-clean.
4 Executing targets
procedure
name : (or/c symbol? path-string?)
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 make call.
For a target that needs rebuilding, the engine looks up the registered target procedure for the active prefix and calls it.
Another registered makefile can be selected explicitly:
(current-makefile-prefix 'wiki) (make 'status)
5 Inspecting registered makefiles
procedure
procedure
(makefile-targets [prefix]) → list?
prefix : (or/c symbol? path-string?) = (current-makefile-prefix)
procedure
(makefile-target-exists? name) → boolean?
name : (or/c symbol? path-string?)
procedure
(makefile-target-procedure name) → procedure?
name : (or/c symbol? path-string?)
6 Rash integration
Rash integration is intentionally provided by the separate rash-makefile package so that racket-makefile remains a pure Racket dependency.
7 Recipe context
syntax
syntax
syntax
The generated procedure establishes this context even when called directly.
8 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.
9 Refreshing definitions
procedure
In DrRacket, pressing Run is usually the simpler way to reevaluate a Racket or Rash makefile.
10 Running commands
The symbols '$target, '$deps, and '$< are expanded from the current target context when they occur in the command list.
11 Cleanup helpers
procedure
path : path-string?
procedure
path : path-string?
procedure
directory : path-string? patterns : list?
procedure
(list-dir/files directory regexp [ #:recursive recursive]) → list? directory : path-string? regexp : regexp? recursive : any/c = #f
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