racket-makefile
1 A functional makefile
2 Makefile definitions
makefile
target
deps
phony
default-target
3 Generated targets
4 Executing targets
make
current-makefile-prefix
5 Inspecting registered makefiles
makefile-prefixes
makefile-targets
makefile-target-exists?
makefile-target-procedure
6 Rash integration
7 Recipe context
$target
$deps
$<
8 Dependency processing
9 Refreshing definitions
refresh-makefile
10 Running commands
run
raco
11 Cleanup helpers
rm-f
rm-rf
cleanup
list-dir/  files
list-files
list-dirs
9.3

racket-makefile🔗ℹ

Hans Dijkema / hans@dijkewijk.nl

 (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 ...)

Defines one prefixed makefile and evaluates form ... as ordinary Racket code in the lexical context of that makefile. The prefix is explicit, normally a quoted symbol such as 'wiki.

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 ...)

Registers one target procedure for the surrounding makefile. The name is an ordinary Racket expression.

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 ...)

Specifies target dependencies. Each dependency is an ordinary Racket expression. The form is valid only as the dependency clause of target.

syntax

(phony name ...)

Marks the values produced by 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.

syntax

(default-target name)

Selects the target value produced by name for (make) in the surrounding makefile prefix. If no default is specified, the first registered target is used.

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

(make name ...)  void?

  name : (or/c symbol? path-string?)
Builds the supplied targets in the makefile selected by current-makefile-prefix. 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 make call.

For a target that needs rebuilding, the engine looks up the registered target procedure for the active prefix and calls it.

A parameter containing the active makefile prefix, or #f before any makefile has been evaluated. Evaluating makefile sets the parameter persistently to that makefile’s prefix.

Another registered makefile can be selected explicitly:

(current-makefile-prefix 'wiki)
(make 'status)

5 Inspecting registered makefiles🔗ℹ

procedure

(makefile-prefixes)  list?

Returns the registered makefile prefixes in evaluation order. The original prefix values are preserved.

procedure

(makefile-targets [prefix])  list?

  prefix : (or/c symbol? path-string?)
   = (current-makefile-prefix)
Returns the targets registered for 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.

procedure

(makefile-target-exists? name)  boolean?

  name : (or/c symbol? path-string?)
Returns whether name exists in the active makefile. To inspect another prefix, use (makefile-target-exists? prefix name).

procedure

(makefile-target-procedure name)  procedure?

  name : (or/c symbol? path-string?)
Returns the registered target procedure for name in the active makefile. To inspect another prefix, use (makefile-target-procedure prefix name). An unknown target raises an exception.

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

$target

The current target name while a generated target procedure is running.

syntax

$deps

The flattened list of dependencies of the current target.

syntax

$<

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.

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

(refresh-makefile)  void?

Reloads the source module containing the most recently evaluated 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 Run is usually the simpler way to reevaluate a Racket or Rash makefile.

10 Running commands🔗ℹ

procedure

(run command)  void?

  command : list?
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 '$target, '$deps, and '$< are expanded from the current target context when they occur in the command list.

procedure

(raco command)  void?

  command : list?
Runs raco from the active Racket installation. The command is supplied as a list, for example (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 PATH. A non-zero result raises an error.

11 Cleanup helpers🔗ℹ

procedure

(rm-f path ...)  void?

  path : path-string?
Removes files when they exist. Missing files are ignored. Directories are rejected.

procedure

(rm-rf path ...)  void?

  path : path-string?
Removes files or directory trees recursively. Missing paths are ignored.

procedure

(cleanup directory patterns)  void?

  directory : path-string?
  patterns : list?
Removes files below directory that match the supplied Racket glob patterns.

procedure

(list-dir/files directory    
  regexp    
  [#:recursive recursive])  list?
  directory : path-string?
  regexp : regexp?
  recursive : any/c = #f
Returns matching files and directories. The regular expression is applied to each file or directory name, not to the complete path.

procedure

(list-files directory    
  regexp    
  [#:recursive recursive])  list?
  directory : path-string?
  regexp : regexp?
  recursive : any/c = #f
Like list-dir/files, but keeps only files.

procedure

(list-dirs directory    
  regexp    
  [#:recursive recursive])  list?
  directory : path-string?
  regexp : regexp?
  recursive : any/c = #f
Like list-dir/files, but keeps only directories.