5.5 KiB
racket-makefile
racket-makefile provides make-style dependency builds as ordinary Racket functionality.
racket-makefile is a Racket library and has no dependency on Rash. Rash integration is provided separately by the rash-makefile package.
Functional makefiles
A makefile is ordinary Racket code inside a makefile form. The prefix and static symbolic target names are explicit values:
#lang racket
(require racket-makefile)
(makefile 'wiki
(default-target 'all)
(phony 'all 'clean 'status)
(target 'status
(displayln "status"))
(target 'clean
(rm-rf "compiled"))
(target 'all
(deps 'status)
(displayln "all")))
A statically quoted target name defines a real named procedure. The example above defines, among others:
makefile-target-wiki-status
makefile-target-wiki-clean
makefile-target-wiki-all
They are ordinary procedures:
(procedure? makefile-target-wiki-status)
(makefile-target-wiki-status)
make is also an ordinary procedure:
(make)
(make 'status)
(make 'clean 'all)
make resolves target names for the active makefile in an internal prefix-aware registry. It handles dependencies and timestamp checks and invokes the registered target procedure when the target needs to run.
Ordinary Racket and generated targets
The body of makefile is not restricted to target clauses. Definitions, loops, conditionals, and other Racket forms can be used directly.
A target name is an expression. A quoted target name such as 'all is known while the module is expanded and therefore gets a named Racket binding. A target name such as obj is evaluated while the makefile is registered and can therefore 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 foo.o, bar.o, and baz.o as separate target procedures. Each generated procedure closes over the corresponding src and obj values. The static targets still define makefile-target-stuff-all and makefile-target-stuff-clean as normal Racket bindings.
Makefile prefixes
Every makefile has an explicit prefix:
(makefile 'wiki
(target 'status ...))
(makefile 'audio
(target 'status ...))
Targets with the same name can therefore coexist. Evaluating a makefile form makes that prefix current. Consequently the last makefile definition evaluated is the one used by an unqualified make call:
(current-makefile-prefix)
;; 'audio
(make 'status)
;; invokes makefile-target-audio-status through the make engine
current-makefile-prefix is a parameter, so another registered makefile can be selected explicitly:
(current-makefile-prefix 'wiki)
(make 'status)
Re-evaluating a makefile with the same prefix replaces the registrations for that prefix.
Inspecting makefiles and targets
The registered makefiles and targets can be inspected without exposing the mutable registry:
(makefile-prefixes)
;; '(wiki audio)
(makefile-targets)
;; targets for the current prefix
(makefile-targets 'wiki)
;; targets registered for 'wiki
(makefile-target-exists? 'status)
(makefile-target-exists? 'wiki 'status)
(makefile-target-procedure 'status)
(makefile-target-procedure 'wiki 'status)
makefile-targets returns targets in registration order and preserves their original values. A quoted symbolic target is returned as a symbol, while a dynamically generated path target is returned as a path. The forms without an explicit prefix use current-makefile-prefix.
Rash integration
Rash integration is intentionally kept out of this package. Install and require the separate rash-makefile package when make target line syntax is desired.
Targets and dependencies
target, phony, and default-target are valid only in the lexical body of makefile. They may occur inside ordinary Racket forms nested in that body.
Dependencies are ordinary Racket expressions and may return nested lists; the build engine flattens them.
(makefile 'example
(define prerequisites '("one.c" "two.c"))
(target 'program
(deps prerequisites)
(run '(cc -o program one.c two.c))))
The automatic recipe values remain available inside target procedures:
$target
$deps
$<
Because a target is a real procedure, calling a generated static procedure directly runs its recipe directly. Calling it through make adds dependency traversal and timestamp-based rebuilding.
Refreshing
A makefile form remembers the source module in which it occurs. refresh-makefile reloads that source without restarting the Racket process:
(refresh-makefile)
(make 'all)
In DrRacket, pressing Run is normally the simpler way to reevaluate a Rash or Racket makefile.
Helpers
The functional helpers provided by racket-makefile are run, raco, rm-f, rm-rf, cleanup, list-dir/files, list-files, and list-dirs. raco locates the executable belonging to the active Racket installation before falling back to PATH.
racket-makefile also continues to re-export the package APIs it uses for build scripts, including git-cli, package-zipper, net/sendurl, and racket/string.