| #!/bin/sh |
| # |
| # spatchcache: a poor-man's "ccache"-alike for "spatch" in git.git |
| # |
| # This caching command relies on the peculiarities of the Makefile |
| # driving "spatch" in git.git, in particular if we invoke: |
| # |
| # make |
| # # See "spatchCache.cacheWhenStderr" for why "--very-quiet" is |
| # # used |
| # make coccicheck SPATCH_FLAGS=--very-quiet |
| # |
| # We can with COMPUTE_HEADER_DEPENDENCIES (auto-detected as true with |
| # "gcc" and "clang") write e.g. a .depend/grep.o.d for grep.c, when we |
| # compile grep.o. |
| # |
| # The .depend/grep.o.d will have the full header dependency tree of |
| # grep.c, and we can thus cache the output of "spatch" by: |
| # |
| # 1. Hashing all of those files |
| # 2. Hashing our source file, and the *.cocci rule we're |
| # applying |
| # 3. Running spatch, if suggests no changes (by far the common |
| # case) we invoke "spatchCache.getCmd" and |
| # "spatchCache.setCmd" with a hash SHA-256 to ask "does this |
| # ID have no changes" or "say that ID had no changes> |
| # 4. If no "spatchCache.{set,get}Cmd" is specified we'll use |
| # "redis-cli" and maintain a SET called "spatch-cache". Set |
| # appropriate redis memory policies to keep it from growing |
| # out of control. |
| # |
| # This along with the general incremental "make" support for |
| # "contrib/coccinelle" makes it viable to (re-)run coccicheck |
| # e.g. when merging integration branches. |
| # |
| # Note that the "--very-quiet" flag is currently critical. The cache |
| # will refuse to cache anything that has output on STDERR (which might |
| # be errors from spatch), but see spatchCache.cacheWhenStderr below. |
| # |
| # The STDERR (and exit code) could in principle be cached (as with |
| # ccache), but then the simple structure in the Redis cache would need |
| # to change, so just supply "--very-quiet" for now. |
| # |
| # To use this, simply set SPATCH to |
| # contrib/coccinelle/spatchcache. Then optionally set: |
| # |
| # [spatchCache] |
| # # Optional: path to a custom spatch |
| # spatch = ~/g/coccicheck/spatch.opt |
| # |
| # As well as this trace config (debug implies trace): |
| # |
| # cacheWhenStderr = true |
| # trace = false |
| # debug = false |
| # |
| # The ".depend/grep.o.d" can also be customized, as a string that will |
| # be eval'd, it has access to a "$dirname" and "$basename": |
| # |
| # [spatchCache] |
| # dependFormat = "$dirname/.depend/${basename%.c}.o.d" |
| # |
| # Setting "trace" to "true" allows for seeing when we have a cache HIT |
| # or MISS. To debug whether the cache is working do that, and run e.g.: |
| # |
| # redis-cli FLUSHALL |
| # <make && make coccicheck, as above> |
| # grep -hore HIT -e MISS -e SET -e NOCACHE -e CANTCACHE .build/contrib/coccinelle | sort | uniq -c |
| # 600 CANTCACHE |
| # 7365 MISS |
| # 7365 SET |
| # |
| # A subsequent "make cocciclean && make coccicheck" should then have |
| # all "HIT"'s and "CANTCACHE"'s. |
| # |
| # The "spatchCache.cacheWhenStderr" option is critical when using |
| # spatchCache.{trace,debug} to debug whether something is set in the |
| # cache, as we'll write to the spatch logs in .build/* we'd otherwise |
| # always emit a NOCACHE. |
| # |
| # Reading the config can make the command much slower, to work around |
| # this the config can be set in the environment, with environment |
| # variable name corresponding to the config key. "default" can be used |
| # to use whatever's the script default, e.g. setting |
| # spatchCache.cacheWhenStderr=true and deferring to the defaults for |
| # the rest is: |
| # |
| # export GIT_CONTRIB_SPATCHCACHE_DEBUG=default |
| # export GIT_CONTRIB_SPATCHCACHE_TRACE=default |
| # export GIT_CONTRIB_SPATCHCACHE_CACHEWHENSTDERR=true |
| # export GIT_CONTRIB_SPATCHCACHE_SPATCH=default |
| # export GIT_CONTRIB_SPATCHCACHE_DEPENDFORMAT=default |
| # export GIT_CONTRIB_SPATCHCACHE_SETCMD=default |
| # export GIT_CONTRIB_SPATCHCACHE_GETCMD=default |
| |
| set -e |
| |
| env_or_config () { |
| env="$1" |
| shift |
| if test "$env" = "default" |
| then |
| # Avoid expensive "git config" invocation |
| return |
| elif test -n "$env" |
| then |
| echo "$env" |
| else |
| git config $@ || : |
| fi |
| } |
| |
| ## Our own configuration & options |
| debug=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_DEBUG" --bool "spatchCache.debug") |
| if test "$debug" != "true" |
| then |
| debug= |
| fi |
| if test -n "$debug" |
| then |
| set -x |
| fi |
| |
| trace=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_TRACE" --bool "spatchCache.trace") |
| if test "$trace" != "true" |
| then |
| trace= |
| fi |
| if test -n "$debug" |
| then |
| # debug implies trace |
| trace=true |
| fi |
| |
| cacheWhenStderr=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_CACHEWHENSTDERR" --bool "spatchCache.cacheWhenStderr") |
| if test "$cacheWhenStderr" != "true" |
| then |
| cacheWhenStderr= |
| fi |
| |
| trace_it () { |
| if test -z "$trace" |
| then |
| return |
| fi |
| echo "$@" >&2 |
| } |
| |
| spatch=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_SPATCH" --path "spatchCache.spatch") |
| if test -n "$spatch" |
| then |
| if test -n "$debug" |
| then |
| trace_it "custom spatchCache.spatch='$spatch'" |
| fi |
| else |
| spatch=spatch |
| fi |
| |
| dependFormat='$dirname/.depend/${basename%.c}.o.d' |
| dependFormatCfg=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_DEPENDFORMAT" "spatchCache.dependFormat") |
| if test -n "$dependFormatCfg" |
| then |
| dependFormat="$dependFormatCfg" |
| fi |
| |
| set=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_SETCMD" "spatchCache.setCmd") |
| get=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_GETCMD" "spatchCache.getCmd") |
| |
| ## Parse spatch()-like command-line for caching info |
| arg_sp= |
| arg_file= |
| args="$@" |
| spatch_opts() { |
| while test $# != 0 |
| do |
| arg_file="$1" |
| case "$1" in |
| --sp-file) |
| arg_sp="$2" |
| ;; |
| esac |
| shift |
| done |
| } |
| spatch_opts "$@" |
| if ! test -f "$arg_file" |
| then |
| arg_file= |
| fi |
| |
| hash_for_cache() { |
| # Parameters that should affect the cache |
| echo "args=$args" |
| echo "config spatchCache.spatch=$spatch" |
| echo "config spatchCache.debug=$debug" |
| echo "config spatchCache.trace=$trace" |
| echo "config spatchCache.cacheWhenStderr=$cacheWhenStderr" |
| echo |
| |
| # Our target file and its dependencies |
| git hash-object "$1" "$2" $(grep -E -o '^[^:]+:$' "$3" | tr -d ':') |
| } |
| |
| # Sanity checks |
| if ! test -f "$arg_sp" && ! test -f "$arg_file" |
| then |
| echo $0: no idea how to cache "$@" >&2 |
| exit 128 |
| fi |
| |
| # Main logic |
| dirname=$(dirname "$arg_file") |
| basename=$(basename "$arg_file") |
| eval "dep=$dependFormat" |
| |
| if ! test -f "$dep" |
| then |
| trace_it "$0: CANTCACHE have no '$dep' for '$arg_file'!" |
| exec "$spatch" "$@" |
| fi |
| |
| if test -n "$debug" |
| then |
| trace_it "$0: The full cache input for '$arg_sp' '$arg_file' '$dep'" |
| hash_for_cache "$arg_sp" "$arg_file" "$dep" >&2 |
| fi |
| sum=$(hash_for_cache "$arg_sp" "$arg_file" "$dep" | git hash-object --stdin) |
| |
| trace_it "$0: processing '$arg_file' with '$arg_sp' rule, and got hash '$sum' for it + '$dep'" |
| |
| getret= |
| if test -z "$get" |
| then |
| if test $(redis-cli SISMEMBER spatch-cache "$sum") = 1 |
| then |
| getret=0 |
| else |
| getret=1 |
| fi |
| else |
| $set "$sum" |
| getret=$? |
| fi |
| |
| if test "$getret" = 0 |
| then |
| trace_it "$0: HIT for '$arg_file' with '$arg_sp'" |
| exit 0 |
| else |
| trace_it "$0: MISS: for '$arg_file' with '$arg_sp'" |
| fi |
| |
| out="$(mktemp)" |
| err="$(mktemp)" |
| |
| set +e |
| "$spatch" "$@" >"$out" 2>>"$err" |
| ret=$? |
| cat "$out" |
| cat "$err" >&2 |
| set -e |
| |
| nocache= |
| if test $ret != 0 |
| then |
| nocache="exited non-zero: $ret" |
| elif test -s "$out" |
| then |
| nocache="had patch output" |
| elif test -z "$cacheWhenStderr" && test -s "$err" |
| then |
| nocache="had stderr (use --very-quiet or spatchCache.cacheWhenStderr=true?)" |
| fi |
| |
| if test -n "$nocache" |
| then |
| trace_it "$0: NOCACHE ($nocache): for '$arg_file' with '$arg_sp'" |
| exit "$ret" |
| fi |
| |
| trace_it "$0: SET: for '$arg_file' with '$arg_sp'" |
| |
| setret= |
| if test -z "$set" |
| then |
| if test $(redis-cli SADD spatch-cache "$sum") = 1 |
| then |
| setret=0 |
| else |
| setret=1 |
| fi |
| else |
| "$set" "$sum" |
| setret=$? |
| fi |
| |
| if test "$setret" != 0 |
| then |
| echo "FAILED to set '$sum' in cache!" >&2 |
| exit 128 |
| fi |
| |
| exit "$ret" |