Skip to content

Commit

Permalink
add dry-run support
Browse files Browse the repository at this point in the history
The primary intent is to recognize `-n`/`-t`/`-q` from `make` and not
ignore them. A careful setup of targets and `:recur` options might
even make those modes useful.
  • Loading branch information
mflatt committed Oct 14, 2023
1 parent e1579c5 commit bdad684
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 27 deletions.
51 changes: 36 additions & 15 deletions lib/zuo/build.zuo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"thread.zuo"
"config.zuo"
"jobserver.zuo"
"dry-run.zuo"
"private/build-db.zuo")

(provide (rename-out [make-target target]
Expand Down Expand Up @@ -207,17 +208,18 @@
;; Where the contracts below say "dep-sha256s", that's a hash table
;; mapping a dependency's key to a SHA-256.

(struct build-state (ch ; channel to hold the state while target is running
target-state ; key -> (cons sha256 dep-sha256s) | 'pending | channel
target-accum ; key -> dep-sha256s
db ; key -> (cons sha256 dep-sha256s) | #t [for db file itself]
time-cache ; key -> (cons timestamp sha256)
saw-targets ; key -> target [to detect multiple for same output]
thread-count ; channel to hold thread-scheduler state
thread-seens ; sym -> hash [to detect cycles when count goes to 0]
resourcer ; build stat's resource manager
log? ; logging enabled?
uni?)) ; just one job?
(struct build-state (ch ; channel to hold the state while target is running
target-state ; key -> (cons sha256 dep-sha256s) | 'pending | channel
target-accum ; key -> dep-sha256s
db ; key -> (cons sha256 dep-sha256s) | #t [for db file itself]
time-cache ; key -> (cons timestamp sha256)
saw-targets ; key -> target [to detect multiple for same output]
thread-count ; channel to hold thread-scheduler state
thread-seens ; sym -> hash [to detect cycles when count goes to 0]
resourcer ; build stat's resource manager
log? ; logging enabled?
uni? ; just one job?
dry-run-mode)) ; #f, 'dry-run, 'question, or 'would

;; Main entry point to build a target `t`
(define (build t-in [token #f] [options (hash)])
Expand Down Expand Up @@ -255,12 +257,15 @@
(make-resourcer num-jobs))
(or (hash-ref options 'log? #f)
(assoc "ZUO_BUILD_LOG" (hash-ref (runtime-env) 'env)))
(= num-jobs 1)))
(= num-jobs 1)
(hash-ref options 'dry-run-mode (maybe-dry-run-mode))))
(when resourcer
(release-resource state "nested"))
(do-build t state seen #t)
(define end-state (do-build t state seen #t))
(when resourcer
(acquire-resource state "continue from nested")))))))
(acquire-resource state "continue from nested"))
(when (eq? 'would (build-state-dry-run-mode end-state))
(exit 1)))))))

(define (build/maybe-dep t-in token add-dep?)
(unless (token? token) (arg-error 'build/dep "build token" token))
Expand Down Expand Up @@ -482,6 +487,16 @@
(define done-state (update-target-state newer-state t (cons sha256 all-dep-sha256s)))
(channel-put result-ch 'done)
done-state]
[(and (build-state-dry-run-mode newer-state)
(not (hash-ref (target-options t) 'recur? #f)))
(define mode (build-state-dry-run-mode newer-state))
(when (eq? mode 'dry-run)
(alert (~a "would build target: " (target-name t))))
(define done-state (update-target-state newer-state t (cons no-sha256 all-dep-sha256s)))
(channel-put result-ch 'done)
(if (eq? mode 'question)
(build-state-set-dry-run-mode done-state 'would)
done-state)]
[else
(unless to-build
(error "build: out-of-date target has no build procedure" (target-name t)))
Expand Down Expand Up @@ -560,6 +575,11 @@
(if (and v (> v 0))
(hash-set opts 'jobs v)
(error "not a positive integer" n)))]
:once-any
[opts ("-n" "--just-print" "--dry-run") "Print needed builds without running"
(hash-set opts 'dry-run-mode 'dry-run)]
[opts ("-q" "--question") "Check whether builds are needed without running"
(hash-set opts 'dry-run-mode 'question)]
:args
args
(lambda (opts)
Expand Down Expand Up @@ -1071,4 +1091,5 @@
':command 'command?
':noisy 'noisy?
':quiet 'quiet?
':eager 'eager?))
':eager 'eager?
':recur 'recur?))
26 changes: 26 additions & 0 deletions lib/zuo/dry-run.zuo
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#lang zuo/base

(provide maybe-dry-run-mode)

(define (maybe-dry-run-mode)
(define a (and (eq? 'unix (system-type))
(assoc "MAKEFLAGS" (hash-ref (runtime-env) 'env))))
(define (new-mode mode sym)
(when (and mode (not (eq? mode sym)))
(error (~a "`MAKEFLAGS` specified both " mode " and " sym " modes")))
sym)
(and a
(let ([s (cdr a)])
(let loop ([i 0] [mode #f])
(cond
[(or (= i (string-length s))
(equal? (char " ") (string-ref s i)))
mode]
[(equal? (char "n") (string-ref s i))
(loop (+ i 1) (new-mode mode 'dry-run))]
[(equal? (char "q") (string-ref s i))
(loop (+ i 1) (new-mode mode 'question))]
[(equal? (char "t") (string-ref s i))
(error "`MAKEFLAGS` has `-t`, but trace mode is not supported")]
[else
(loop (+ i 1) mode)])))))
2 changes: 1 addition & 1 deletion lib/zuo/jobserver.zuo
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
(assoc "MAKEFLAGS" (hash-ref (runtime-env) 'env))))
(and a
(let ([args (shell->strings (cdr a))])
(and (ormap (lambda (arg) (string=? "-j" arg)) args)
(and (ormap (glob->matcher "-j*") args)
(ormap (let ([match? (let ([fds? (glob->matcher "--jobserver-fds=*")]
[auth? (glob->matcher "--jobserver-auth=*")])
(lambda (s) (or (fds? s) (auth? s))))])
Expand Down
6 changes: 4 additions & 2 deletions lib/zuo/private/main.zuo
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
zuo/shell
zuo/c
zuo/glob
zuo/jobserver)
zuo/jobserver
zuo/dry-run)

(provide (all-from-out zuo/private/base/main
zuo/cmdline
Expand All @@ -17,4 +18,5 @@
zuo/shell
zuo/c
zuo/glob
zuo/jobserver))
zuo/jobserver
zuo/dry-run))
4 changes: 3 additions & 1 deletion zuo-doc/fake-zuo.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@
config-file->hash

maybe-jobserver-client
maybe-jobserver-jobs))
maybe-jobserver-jobs

maybe-dry-run-mode))

(intro-define-fake)
38 changes: 31 additions & 7 deletions zuo-doc/zuo-build.scrbl
Original file line number Diff line number Diff line change
Expand Up @@ -360,16 +360,23 @@ The following keys are recognized in @racket[options]:
its dependencies as quiet.}

@item{@racket['eager?] mapped to any value: if non-@racket[#f], then
the target's build step is not run in a separate thread, which
has the effect of ordering the build step before others that do
the target's rule is not run in a separate thread, which
has the effect of ordering the rule before others that do
run in a separate thread.}

@item{@racket['recur?] mapped to any value: if non-@racket[#f], then
the target's rule is run in dry-run modes of @racket[build] the
same as non-dry-run modes. This option is analogous to prefixing
a command with @litchar{+} in a makefile.}

@item{@racket['db-dir] mapped to a path or @racket[#f]: if
non-@racket[#f], build information for the target is stored in
@filepath{_zuo.db} and @filepath{_zuo_tc.db} files in the
specified directory, instead of the directory of @racket[name].}

]}
]

@history[#:changed "1.8" @elem{Added @racket['recur?] for @racket[options].}]}

@deftogether[(
@defproc[(rule [dependencies (listof (or/c target? path-string?))]
Expand Down Expand Up @@ -442,6 +449,17 @@ following keys are recognized:
logging also can be enabled by setting the
@envvar{ZUO_BUILD_LOG} environment variable}

@item{@racket['dry-run-mode] mapped to @racket[#f], @racket['question], or
@racket['dry-run]: enables ``dry run'' mode when
non-@racket[#f]; when the value is @racket['dry-run],
@racket[build] prints targets whose rules would be run (without
running them); when the value is @racket['question],
@racket[build] does not rules, but exits with
@racket[1] when some target's rule would be run; a @tech{target}
can be made immune to dry-run mode through a @racket['recur?]
option; when @racket['dry-run] is not set in @racket[options],
the mode is determined by calling @racket[maybe-dry-run-mode]}

]

If @racket[token] is not @racket[#f], it must be a @tech{build token}
Expand All @@ -461,7 +479,9 @@ the same build.

@history[#:changed "1.1" @elem{Use @racket[maybe-jobserver-client] if
@racket['jobs] is not set in
@racket[options].}]}
@racket[options].}
#:changed "1.8" @elem{Added support for @racket['dry-run-mode]
in @racket[options].}]}


@defproc[(build/dep [target (or target? path-string?)] [token token?]) void?]{
Expand All @@ -487,7 +507,8 @@ similar to @hyperlink[shake-url]{Shake}'s ``order only'' dependencies.}
Parses command-line arguments to build one or more targets in
@racket[targets], where the first one is built by default. The
@racket[options] argument is passed along to @racket[build], but may
be adjusted via command-line flags such as @DFlag{jobs}.
be adjusted via command-line flags such as @DFlag{jobs}, @Flag{n},
or @Flag{q}.

If @racket[options] has a mapping for @racket['args], the value is
used as the command-line arguments to parse instead of
Expand Down Expand Up @@ -664,9 +685,12 @@ to an input-file target. A @racket[_dep-path-or-target] can also be a
target that is created outside the @racket[make-targets] call.

An @racket[_option] can be @racket[':precious], @racket[':command],
@racket[':noisy], @racket[':quiet], or @racket[':eager] to set the
corresponding option (see @racket[target]) in a target.}
@racket[':noisy], @racket[':quiet], @racket[':eager], or @racket[':recur] to set the
corresponding option (see @racket[target]) in a target.

A @racket[':db-dir] line (appearing at most once) specifies where
build information should be recorded for all targets. Otherwise, the
build result for each target is stored in the target's directory.

@history[#:changed "1.8" @elem{Added @racket[':recur] for
@racket[_option].}]}
21 changes: 21 additions & 0 deletions zuo-doc/zuo-lib.scrbl
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,24 @@ number.

Unlike @racket[maybe-jobserver-client], @racket[maybe-jobserver-jobs]
does not need to be called in a @tech{threading context}.}

@; ------------------------------------------------------------

@section[#:tag "zuo-dry-run"]{Dry-Run Mode Detection}

@defzuomodule[zuo/dry-run]

@history[#:added "1.8"]

@defproc[(maybe-dry-run-mode) (or/c #f 'dry-run 'question)]{

Returns a non-@racket[#f] value when a dry-run configuration is found via the
@envvar{MAKEFLAGS} environment variable, @racket[#f] otherwise. That
environment variable is normally set by @exec{make} when it runs a target
command and when @Flag{n}, @Flag{q}, or @Flag{t} was provided.

Since ``touch'' mode is not supported by @racket[build], detection of
a @Flag{t} flag triggers an error instead of a non-@racket[#f] value.
Conflicting options also trigger an error.

}
2 changes: 1 addition & 1 deletion zuo.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
declarations. */

#define ZUO_VERSION 1
#define ZUO_MINOR_VERSION 7
#define ZUO_MINOR_VERSION 8

#if defined(_MSC_VER) || defined(__MINGW32__)
# define ZUO_WINDOWS
Expand Down

0 comments on commit bdad684

Please sign in to comment.