3 # spatchcache: a poor-man's "ccache"-alike for "spatch" in git.git
5 # This caching command relies on the peculiarities of the Makefile
6 # driving "spatch" in git.git, in particular if we invoke:
9 # # See "spatchCache.cacheWhenStderr" for why "--very-quiet" is
11 # make coccicheck SPATCH_FLAGS=--very-quiet
13 # We can with COMPUTE_HEADER_DEPENDENCIES (auto-detected as true with
14 # "gcc" and "clang") write e.g. a .depend/grep.o.d for grep.c, when we
17 # The .depend/grep.o.d will have the full header dependency tree of
18 # grep.c, and we can thus cache the output of "spatch" by:
20 # 1. Hashing all of those files
21 # 2. Hashing our source file, and the *.cocci rule we're
23 # 3. Running spatch, if suggests no changes (by far the common
24 # case) we invoke "spatchCache.getCmd" and
25 # "spatchCache.setCmd" with a hash SHA-256 to ask "does this
26 # ID have no changes" or "say that ID had no changes>
27 # 4. If no "spatchCache.{set,get}Cmd" is specified we'll use
28 # "redis-cli" and maintain a SET called "spatch-cache". Set
29 # appropriate redis memory policies to keep it from growing
32 # This along with the general incremental "make" support for
33 # "contrib/coccinelle" makes it viable to (re-)run coccicheck
34 # e.g. when merging integration branches.
36 # Note that the "--very-quiet" flag is currently critical. The cache
37 # will refuse to cache anything that has output on STDERR (which might
38 # be errors from spatch), but see spatchCache.cacheWhenStderr below.
40 # The STDERR (and exit code) could in principle be cached (as with
41 # ccache), but then the simple structure in the Redis cache would need
42 # to change, so just supply "--very-quiet" for now.
44 # To use this, simply set SPATCH to
45 # contrib/coccinelle/spatchcache. Then optionally set:
48 # # Optional: path to a custom spatch
49 # spatch = ~/g/coccicheck/spatch.opt
51 # As well as this trace config (debug implies trace):
53 # cacheWhenStderr = true
57 # The ".depend/grep.o.d" can also be customized, as a string that will
58 # be eval'd, it has access to a "$dirname" and "$basename":
61 # dependFormat = "$dirname/.depend/${basename%.c}.o.d"
63 # Setting "trace" to "true" allows for seeing when we have a cache HIT
64 # or MISS. To debug whether the cache is working do that, and run e.g.:
67 # <make && make coccicheck, as above>
68 # grep -hore HIT -e MISS -e SET -e NOCACHE -e CANTCACHE .build/contrib/coccinelle | sort | uniq -c
73 # A subsequent "make cocciclean && make coccicheck" should then have
74 # all "HIT"'s and "CANTCACHE"'s.
76 # The "spatchCache.cacheWhenStderr" option is critical when using
77 # spatchCache.{trace,debug} to debug whether something is set in the
78 # cache, as we'll write to the spatch logs in .build/* we'd otherwise
79 # always emit a NOCACHE.
81 # Reading the config can make the command much slower, to work around
82 # this the config can be set in the environment, with environment
83 # variable name corresponding to the config key. "default" can be used
84 # to use whatever's the script default, e.g. setting
85 # spatchCache.cacheWhenStderr=true and deferring to the defaults for
88 # export GIT_CONTRIB_SPATCHCACHE_DEBUG=default
89 # export GIT_CONTRIB_SPATCHCACHE_TRACE=default
90 # export GIT_CONTRIB_SPATCHCACHE_CACHEWHENSTDERR=true
91 # export GIT_CONTRIB_SPATCHCACHE_SPATCH=default
92 # export GIT_CONTRIB_SPATCHCACHE_DEPENDFORMAT=default
93 # export GIT_CONTRIB_SPATCHCACHE_SETCMD=default
94 # export GIT_CONTRIB_SPATCHCACHE_GETCMD=default
101 if test "$env" = "default"
103 # Avoid expensive "git config" invocation
113 ## Our own configuration & options
114 debug
=$
(env_or_config
"$GIT_CONTRIB_SPATCHCACHE_DEBUG" --bool "spatchCache.debug")
115 if test "$debug" != "true"
124 trace
=$
(env_or_config
"$GIT_CONTRIB_SPATCHCACHE_TRACE" --bool "spatchCache.trace")
125 if test "$trace" != "true"
131 # debug implies trace
135 cacheWhenStderr
=$
(env_or_config
"$GIT_CONTRIB_SPATCHCACHE_CACHEWHENSTDERR" --bool "spatchCache.cacheWhenStderr")
136 if test "$cacheWhenStderr" != "true"
149 spatch
=$
(env_or_config
"$GIT_CONTRIB_SPATCHCACHE_SPATCH" --path "spatchCache.spatch")
154 trace_it
"custom spatchCache.spatch='$spatch'"
160 dependFormat
='$dirname/.depend/${basename%.c}.o.d'
161 dependFormatCfg
=$
(env_or_config
"$GIT_CONTRIB_SPATCHCACHE_DEPENDFORMAT" "spatchCache.dependFormat")
162 if test -n "$dependFormatCfg"
164 dependFormat
="$dependFormatCfg"
167 set=$
(env_or_config
"$GIT_CONTRIB_SPATCHCACHE_SETCMD" "spatchCache.setCmd")
168 get
=$
(env_or_config
"$GIT_CONTRIB_SPATCHCACHE_GETCMD" "spatchCache.getCmd")
170 ## Parse spatch()-like command-line for caching info
187 if ! test -f "$arg_file"
193 # Parameters that should affect the cache
195 echo "config spatchCache.spatch=$spatch"
196 echo "config spatchCache.debug=$debug"
197 echo "config spatchCache.trace=$trace"
198 echo "config spatchCache.cacheWhenStderr=$cacheWhenStderr"
201 # Our target file and its dependencies
202 git hash-object
"$1" "$2" $
(grep -E -o '^[^:]+:$' "$3" |
tr -d ':')
206 if ! test -f "$arg_sp" && ! test -f "$arg_file"
208 echo $0: no idea how to cache
"$@" >&2
213 dirname=$
(dirname "$arg_file")
214 basename=$
(basename "$arg_file")
215 eval "dep=$dependFormat"
219 trace_it
"$0: CANTCACHE have no '$dep' for '$arg_file'!"
225 trace_it
"$0: The full cache input for '$arg_sp' '$arg_file' '$dep'"
226 hash_for_cache
"$arg_sp" "$arg_file" "$dep" >&2
228 sum=$
(hash_for_cache
"$arg_sp" "$arg_file" "$dep" | git hash-object
--stdin)
230 trace_it
"$0: processing '$arg_file' with '$arg_sp' rule, and got hash '$sum' for it + '$dep'"
235 if test $
(redis-cli SISMEMBER spatch-cache
"$sum") = 1
246 if test "$getret" = 0
248 trace_it
"$0: HIT for '$arg_file' with '$arg_sp'"
251 trace_it
"$0: MISS: for '$arg_file' with '$arg_sp'"
258 "$spatch" "$@" >"$out" 2>>"$err"
267 nocache
="exited non-zero: $ret"
270 nocache
="had patch output"
271 elif test -z "$cacheWhenStderr" && test -s "$err"
273 nocache
="had stderr (use --very-quiet or spatchCache.cacheWhenStderr=true?)"
276 if test -n "$nocache"
278 trace_it
"$0: NOCACHE ($nocache): for '$arg_file' with '$arg_sp'"
282 trace_it
"$0: SET: for '$arg_file' with '$arg_sp'"
287 if test $
(redis-cli SADD spatch-cache
"$sum") = 1
298 if test "$setret" != 0
300 echo "FAILED to set '$sum' in cache!" >&2