summaryrefslogtreecommitdiffstats
path: root/contrib/coccinelle/spatchcache
blob: 29e9352d8a278a98406d653c05ec768f84e9719e (plain)
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"