Change behavior. (require racket-makefile) and use (make <target>) instead of racket Makefile.rkt <target>
This commit is contained in:
@@ -20,3 +20,4 @@ compiled/
|
||||
|
||||
/*.bak
|
||||
/gui/*.bak
|
||||
/doc
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
0.1.2
|
||||
|
||||
Makefile loading no longer starts a build automatically.
|
||||
Added explicit make form: (make), (make target), and (make target ...).
|
||||
Command-line use is now: racket -t Makefile.rkt -e "(make target)".
|
||||
DrRacket can load the makefile with Run and execute make forms interactively.
|
||||
Bare identifiers in make are always interpreted as literal target names.
|
||||
Expanded documentation and examples for command-line and DrRacket use.
|
||||
|
||||
0.1.1
|
||||
|
||||
Initial racket-makefile language implementation.
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
Racket available and only adds targets, dependencies, timestamp based rebuilds,
|
||||
phony targets, external command execution and a few cleanup helpers.
|
||||
|
||||
A makefile only defines its targets. Loading or running the file does not build
|
||||
anything by itself. Builds are started explicitly with `make`.
|
||||
|
||||
```racket
|
||||
#lang racket-makefile
|
||||
|
||||
@@ -26,6 +29,8 @@ phony targets, external command execution and a few cleanup helpers.
|
||||
(cleanup "scrbl" '("**.html" "**.js" "**.css")))
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Install a local checkout with:
|
||||
|
||||
```text
|
||||
@@ -35,20 +40,68 @@ raco pkg install
|
||||
For a versioned archive, give the package name explicitly:
|
||||
|
||||
```text
|
||||
raco pkg install --name racket-makefile racket-makefile-0.1.1.zip
|
||||
raco pkg install --name racket-makefile racket-makefile-0.1.2.zip
|
||||
```
|
||||
|
||||
Run the default target or select one or more targets explicitly:
|
||||
## Running targets
|
||||
|
||||
Load the makefile with `-t` and evaluate a `make` expression with `-e`:
|
||||
|
||||
```text
|
||||
racket Makefile.rkt
|
||||
racket Makefile.rkt clean
|
||||
racket Makefile.rkt clean all
|
||||
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)"
|
||||
```
|
||||
|
||||
The language adds `target`, `deps`, `phony`, `default-target`, `run`, `rm-f`,
|
||||
`rm-rf`, `cleanup`, `$target`, `$deps` and `$<`. Dependency expressions may
|
||||
also produce lists, which are automatically flattened. Everything else is
|
||||
`(make)` uses the target selected with `default-target`. If no default target is
|
||||
specified, the first declared target is used.
|
||||
|
||||
Loading the file alone only registers the targets:
|
||||
|
||||
```text
|
||||
racket -t Makefile.rkt
|
||||
```
|
||||
|
||||
No recipe is executed in that case.
|
||||
|
||||
In DrRacket, open the makefile and press **Run**. This also only loads and
|
||||
registers the targets. Then use the Interactions window:
|
||||
|
||||
```racket
|
||||
> (make)
|
||||
> (make clean)
|
||||
> (make all)
|
||||
> (make clean all)
|
||||
```
|
||||
|
||||
Bare identifiers in `make` are target names, so no quote is needed. For
|
||||
example, `(make clean)` selects the target named `clean`.
|
||||
|
||||
## Generated targets
|
||||
|
||||
Because the rest of the language is ordinary Racket, targets can be generated
|
||||
with normal definitions and loops:
|
||||
|
||||
```racket
|
||||
(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))
|
||||
```
|
||||
|
||||
Dependency expressions may produce lists; they are recursively flattened.
|
||||
|
||||
The language adds `target`, `deps`, `phony`, `default-target`, `make`, `run`,
|
||||
`rm-f`, `rm-rf`, `cleanup`, `$target`, `$deps` and `$<`. Everything else is
|
||||
ordinary Racket.
|
||||
|
||||
See the installed `racket-makefile` Scribble documentation for the full API.
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
#lang racket-makefile
|
||||
|
||||
;; Load only:
|
||||
;; racket -t Makefile.rkt
|
||||
;;
|
||||
;; Build the default target:
|
||||
;; racket -t Makefile.rkt -e "(make)"
|
||||
;;
|
||||
;; Clean and rebuild:
|
||||
;; racket -t Makefile.rkt -e "(make clean all)"
|
||||
;;
|
||||
;; In DrRacket, press Run and then enter (make), (make clean), etc.
|
||||
|
||||
(define CC 'cc)
|
||||
(define CFLAGS '(-Wall -O2))
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#lang racket-makefile
|
||||
|
||||
;; Ordinary Racket can generate targets and dependency lists.
|
||||
|
||||
(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))
|
||||
@@ -1,7 +1,7 @@
|
||||
#lang info
|
||||
|
||||
(define pkg-authors '(hnmdijkema))
|
||||
(define version "0.1.1")
|
||||
(define version "0.1.2")
|
||||
(define license 'MIT)
|
||||
(define collection "racket-makefile")
|
||||
(define pkg-desc
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
deps
|
||||
phony
|
||||
default-target
|
||||
make
|
||||
run
|
||||
rm-f
|
||||
rm-rf
|
||||
@@ -20,20 +21,32 @@
|
||||
$deps
|
||||
$<)
|
||||
|
||||
;; For target declarations we want a bound identifier to remain an ordinary
|
||||
;; Racket expression. This makes generated targets such as (target obj ...)
|
||||
;; possible inside a for loop. An unbound identifier is a literal target name.
|
||||
(define-for-syntax (literal-name-or-expression stx)
|
||||
(if (and (identifier? stx)
|
||||
(not (identifier-binding stx)))
|
||||
(datum->syntax stx `(quote ,(syntax-e stx)) stx stx)
|
||||
stx))
|
||||
|
||||
;; For interactive make invocations a bare identifier is always a literal
|
||||
;; target name. Thus (make compile) means the target named "compile" even if
|
||||
;; Racket happens to provide a binding named compile.
|
||||
(define-for-syntax (literal-make-name stx)
|
||||
(if (identifier? stx)
|
||||
(datum->syntax stx `(quote ,(syntax-e stx)) stx stx)
|
||||
stx))
|
||||
|
||||
(define-syntax (makefile-module-begin stx)
|
||||
(syntax-case stx ()
|
||||
[(_ form ...)
|
||||
#'(racket-module-begin
|
||||
(reset-makefile!)
|
||||
form ...
|
||||
(module+ main
|
||||
(run-selected-target!)))]))
|
||||
;; Re-export make so that `racket -t Makefile.rkt -e "(make all)"`
|
||||
;; imports the make form into the command-line evaluation namespace.
|
||||
(provide make quote #%top-interaction #%app #%datum #%top))]))
|
||||
|
||||
(define-syntax (deps stx)
|
||||
(raise-syntax-error 'deps "only valid as the dependency clause of target" stx))
|
||||
@@ -70,6 +83,14 @@
|
||||
(with-syntax ([target-name (literal-name-or-expression #'name)])
|
||||
#'(set-default-target! target-name))]))
|
||||
|
||||
(define-syntax (make stx)
|
||||
(syntax-case stx ()
|
||||
[(_ name ...)
|
||||
(with-syntax ([(target-name ...)
|
||||
(map literal-make-name
|
||||
(syntax->list #'(name ...)))])
|
||||
#'(make-targets! target-name ...))]))
|
||||
|
||||
(define-syntax $target
|
||||
(syntax-id-rules ()
|
||||
[$target
|
||||
|
||||
+9
-8
@@ -6,7 +6,7 @@
|
||||
mark-phony!
|
||||
set-default-target!
|
||||
reset-makefile!
|
||||
run-selected-target!
|
||||
make-targets!
|
||||
current-target
|
||||
current-dependencies
|
||||
current-first-dependency)
|
||||
@@ -128,20 +128,21 @@
|
||||
(hash-set! built-results key rebuilt?)
|
||||
rebuilt?]))
|
||||
|
||||
(define (selected-targets)
|
||||
(define args (vector->list (current-command-line-arguments)))
|
||||
(define (default-targets)
|
||||
(cond
|
||||
[(pair? args) args]
|
||||
[default-target-name (list default-target-name)]
|
||||
[(pair? target-order) (list (car target-order))]
|
||||
[else '()]))
|
||||
|
||||
(define (run-selected-target!)
|
||||
(define names (selected-targets))
|
||||
(when (null? names)
|
||||
(define (make-targets! . names)
|
||||
(define selected
|
||||
(if (null? names)
|
||||
(default-targets)
|
||||
(append-map target-keys names)))
|
||||
(when (null? selected)
|
||||
(error 'racket-makefile "no target defined"))
|
||||
(define built-results (make-hash))
|
||||
(define visiting (make-hash))
|
||||
(for ([name (in-list names)])
|
||||
(for ([name (in-list selected)])
|
||||
(build-target! name built-results visiting))
|
||||
(void))
|
||||
|
||||
+81
-10
@@ -3,7 +3,7 @@
|
||||
@(require (for-label racket/base
|
||||
racket/file
|
||||
(only-in racket-makefile
|
||||
target deps phony default-target
|
||||
target deps phony default-target make
|
||||
run rm-f rm-rf cleanup
|
||||
$target $deps $<)))
|
||||
|
||||
@@ -17,6 +17,9 @@ 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:
|
||||
@@ -28,7 +31,7 @@ raco pkg install
|
||||
For a versioned archive, specify the package name explicitly:
|
||||
|
||||
@verbatim{
|
||||
raco pkg install --name racket-makefile racket-makefile-0.1.1.zip
|
||||
raco pkg install --name racket-makefile racket-makefile-0.1.2.zip
|
||||
}
|
||||
|
||||
After installation a makefile can start with:
|
||||
@@ -37,16 +40,74 @@ After installation a makefile can start with:
|
||||
#lang racket-makefile
|
||||
}
|
||||
|
||||
Run a makefile with Racket:
|
||||
@section{A first makefile}
|
||||
|
||||
@verbatim{
|
||||
racket Makefile.rkt
|
||||
racket Makefile.rkt clean
|
||||
racket Makefile.rkt clean package
|
||||
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.
|
||||
}
|
||||
|
||||
With no command-line target, @racket[default-target] is used. If no default is
|
||||
specified, the first declared target is used.
|
||||
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}
|
||||
|
||||
@@ -77,7 +138,8 @@ when reached as a dependency.
|
||||
}
|
||||
|
||||
@defform[(default-target name)]{
|
||||
Selects the target used when no target is supplied on the command line.
|
||||
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
|
||||
@@ -183,6 +245,9 @@ example, targets can be generated in a loop:
|
||||
(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}}
|
||||
@@ -201,3 +266,9 @@ package combines naturally with @tt{racket-makefile}:
|
||||
(target package
|
||||
(zip-package))
|
||||
]
|
||||
|
||||
The package target can then be executed explicitly, for example:
|
||||
|
||||
@verbatim{
|
||||
racket -t Makefile.rkt -e "(make package)"
|
||||
}
|
||||
|
||||
+3
-3
@@ -32,18 +32,18 @@
|
||||
(list (list "result.txt"))
|
||||
void)
|
||||
|
||||
(run-selected-target!)
|
||||
(make-targets!)
|
||||
(check-equal? build-count 1)
|
||||
(check-true (file-exists? "result.txt"))
|
||||
|
||||
;; Nothing changed, so the file target remains up to date.
|
||||
(run-selected-target!)
|
||||
(make-targets!)
|
||||
(check-equal? build-count 1)
|
||||
|
||||
;; Make the input newer than the output and verify that it rebuilds.
|
||||
(define result-time (file-or-directory-modify-seconds "result.txt"))
|
||||
(file-or-directory-modify-seconds "input.txt" (+ result-time 2))
|
||||
(run-selected-target!)
|
||||
(make-targets!)
|
||||
(check-equal? build-count 2)))
|
||||
(lambda ()
|
||||
(delete-directory/files tmp #:must-exist? #f)))
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#lang racket
|
||||
|
||||
(require rackunit
|
||||
racket/file
|
||||
racket/runtime-path
|
||||
racket/system)
|
||||
|
||||
(define-runtime-path package-root "..")
|
||||
|
||||
(define racket-executable
|
||||
(find-system-path 'exec-file))
|
||||
|
||||
(define tmp (make-temporary-file "racket-makefile-make~a" 'directory))
|
||||
|
||||
(define (run-racket . args)
|
||||
(parameterize ([current-directory tmp])
|
||||
(apply system* racket-executable args)))
|
||||
|
||||
(dynamic-wind
|
||||
void
|
||||
(lambda ()
|
||||
(call-with-output-file (build-path tmp "Makefile.rkt")
|
||||
(lambda (out)
|
||||
(displayln "#lang racket-makefile" out)
|
||||
(displayln "(default-target all)" out)
|
||||
(displayln "(phony all clean 'compile)" out)
|
||||
(displayln "(target all (call-with-output-file \"all.out\" (lambda (o) (display \"all\" o)) #:exists 'replace))" out)
|
||||
(displayln "(target clean (rm-f \"all.out\" \"compile.out\"))" out)
|
||||
;; `compile` is deliberately chosen because Racket already binds that
|
||||
;; identifier. `(make compile)` must still mean the target named compile.
|
||||
(displayln "(target 'compile (call-with-output-file \"compile.out\" (lambda (o) (display \"compile\" o)) #:exists 'replace))" out)))
|
||||
|
||||
;; Loading the makefile alone must not run any target.
|
||||
(check-true (run-racket "-t" "Makefile.rkt"))
|
||||
(check-false (file-exists? (build-path tmp "all.out")))
|
||||
|
||||
;; Explicit make invocation from the command line.
|
||||
(check-true (run-racket "-t" "Makefile.rkt" "-e" "(make all)"))
|
||||
(check-true (file-exists? (build-path tmp "all.out")))
|
||||
|
||||
;; A bare target identifier is literal, even when Racket binds the same name.
|
||||
(check-true (run-racket "-t" "Makefile.rkt" "-e" "(make compile)"))
|
||||
(check-true (file-exists? (build-path tmp "compile.out")))
|
||||
|
||||
;; Multiple targets are executed in the requested order.
|
||||
(check-true (run-racket "-t" "Makefile.rkt" "-e" "(make clean all)"))
|
||||
(check-true (file-exists? (build-path tmp "all.out")))
|
||||
(check-false (file-exists? (build-path tmp "compile.out")))
|
||||
|
||||
;; (make) uses the configured default target.
|
||||
(delete-file (build-path tmp "all.out"))
|
||||
(check-true (run-racket "-t" "Makefile.rkt" "-e" "(make)"))
|
||||
(check-true (file-exists? (build-path tmp "all.out"))))
|
||||
(lambda ()
|
||||
(delete-directory/files tmp #:must-exist? #f)))
|
||||
Reference in New Issue
Block a user