1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
|
#!/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"
|