305 lines
7.3 KiB
Bash
Executable File
305 lines
7.3 KiB
Bash
Executable File
#!/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"
|