apkbuild-lint 6.5 KB
Newer Older
Leo's avatar
Leo committed
1
2
3
4
5
6
7
#!/bin/sh
# alint APKBUILD - scan APKBUILD template for common mistakes
#
# Adapted from xlint from Void Linux's xtools to Alpine Linux
# https://github.com/leahneukirchen/xtools/
#
# Required packages (names are Alpine Linux pkgs):
Leo's avatar
Leo committed
8
# busybox - for sed, tr, sort and other simple utiltiies
Leo's avatar
Leo committed
9
10
11
12
13
# grep - for grep with -P

export LC_ALL=C

scan() {
Leo's avatar
Leo committed
14
	local rx="$1" msg="$2" tag="$3" severity="$4"
Leo's avatar
Leo committed
15
	grep -P -Hn -e "$rx" "$apkbuild" |
Leo's avatar
Leo committed
16
		sed "s/^\([^:]*:[^:]*:\)\(.*\)/$severity:[$tag]:\1$msg/"
Leo's avatar
Leo committed
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
}

variables=$(echo -n "#.*
_.*
startdir
srcdir
pkgdir
subpkgdir
builddir
arch
depends
depends_dev
checkdepends
giturl
install
.*.pre-install
.*.post-install
.*.pre-upgrade
.*.post-upgrade
.*.pre-deinstall
.*.post-deinstall
install_if
license
makedepends
md5sums
sha256sums
sha512sums
options
pkgdesc
pkggroups
pkgname
pkgrel
pkgusers
pkgver
provides
provider_priority
replaces
replaces_priority
source
subpackages
triggers
ldpath
Leo's avatar
Leo committed
59
sonameprefix
Leo's avatar
Leo committed
60
61
url" | tr '\n' '|')

62
63
default_builddir_value() {
	[ "$SKIP_DEFAULT_BUILDDIR_VALUE" ] && return 0
Leo's avatar
Leo committed
64
	[ "$SKIP_AL1" ] && return 0
65
	if [ "$builddir" = "/$pkgname-$pkgver" ]; then
66
		scan '^builddir=' "builddir can be removed as it is the default value" 'AL1' 'MC'
67
68
69
70
71
	fi
}

unnecessary_return_1() {
	[ "$SKIP_UNNECESSARY_RETURN_1" ] && return 0
Leo's avatar
Leo committed
72
73
	[ "$SKIP_AL2" ] && return 0
	scan '\|\| return 1' "|| return 1 is not required as set -e is used" 'AL2' 'MC'
74
75
76
77
}

pkgname_quoted() {
	[ "$SKIP_PKGNAME_QUOTED" ] && return 0
Leo's avatar
Leo committed
78
79
	[ "$SKIP_AL3" ] && return 0
	scan '^pkgname="[^$]+"' "pkgname must not be quoted" 'AL3' 'MP'
80
81
82
83
}

pkgver_quoted() {
	[ "$SKIP_PKGVER_QUOTED" ] && return 0
Leo's avatar
Leo committed
84
85
	[ "$SKIP_AL4" ] && return 0
	scan '^pkgver="[^$]+"' "pkgver must not be quoted" 'AL4' 'MP'
86
87
88
89
}

empty_variable() {
	[ "$SKIP_EMPTY_VARIABLE" ] && return 0
Leo's avatar
Leo committed
90
91
	[ "$SKIP_AL5" ] && return 0
	scan '^[A-Za-z0-9_]*=(""|''|)$' "variable set to empty string: \2" 'AL5' 'MC'
92
93
94
95
}

custom_variable() {
	[ "$SKIP_CUSTOM_VARIABLE" ] && return 0
Leo's avatar
Leo committed
96
	[ "$SKIP_AL6" ] && return 0
97
	scan '^(?!\s*^('"$variables"'))[^\s=-]+=' \
Leo's avatar
Leo committed
98
		"prefix custom variable with _: \2" 'AL6' 'IC'
99
100
101
102
}

indent_tabs() {
	[ "$SKIP_INDENT_TABS" ] && return 0
Leo's avatar
Leo committed
103
104
	[ "$SKIP_AL7" ] && return 0
	scan '^  ' "indent with tabs" 'AL7' 'IC'
105
106
107
108
}

trailing_whitespace() {
	[ "$SKIP_TRAILING_WHITESPACE" ] && return 0
Leo's avatar
Leo committed
109
110
	[ "$SKIP_AL8" ] && return 0
	scan '[\t ]$' "trailing whitespace" 'AL8' 'IC'
111
112
113
114
}

backticks_usage() {
	[ "$SKIP_BACKTICKS_USAGE" ] && return 0
Leo's avatar
Leo committed
115
116
	[ "$SKIP_AL25" ] && return 0
	scan '[^\\]`' "use \$() instead of backticks" 'AL25' 'SP'
117
118
119
120
}

function_keyword() {
	[ "$SKIP_FUNCTION_KEYWORD" ] && return 0
Leo's avatar
Leo committed
121
122
	[ "$SKIP_AL9" ] && return 0
	scan '^\t*function\b' 'do not use the function keyword' 'AL9' 'SC'
123
124
125
126
}

space_before_function_parenthesis() {
	[ "$SKIP_SPACE_BEFORE_FUNCTION_PARENTHESIS" ] && return 0
Leo's avatar
Leo committed
127
	[ "$SKIP_AL10" ] && return 0
128
	scan '^\t*[^ ]*  *\(\)' 'do not use space before function parenthesis' 'AL10' 'IC'
129
130
131
132
}

space_after_function_parenthesis() {
	[ "$SKIP_SPACE_AFTER_FUNCTION_PARENTHESIS" ] && return 0
Leo's avatar
Leo committed
133
	[ "$SKIP_AL11" ] && return 0
134
	scan '^\t*[^ ]*\(\)(|   *){' 'use one space after function parenthesis' 'AL11' 'IC'
135
136
137
138
}

newline_opening_brace() {
	[ "$SKIP_NEWLINE_OPENING_BRACE" ] && return 0
Leo's avatar
Leo committed
139
	[ "$SKIP_AL12" ] && return 0
140
	scan '^\t*[^ ]*\(\)$' 'do not use a newline before function opening brace' 'AL12' 'IC'
141
142
143
144
}

superfluous_cd_builddir() {
	[ "$SKIP_SUPERFLUOUS_CD_BUILDDIR" ] && return 0
Leo's avatar
Leo committed
145
	[ "$SKIP_AL13" ] && return 0
146
147
148
149
150
151
152
153
	local cds= cdscount= prevcd= phase="$1"

	# All ocurrences of the 'cd' command being used
	# 1. Print file with line numbers.
	# 2. Print the function from the opening declaration up to the closing bracked
	# 3. grep for all ocurrences of the 'cd' command (ignore obviously invalid ones
	#	like matching 'cd' until the end of the line)
	cds="$(cat -n "$apkbuild" \
154
155
		   | sed -n "/^\s\+[0-9].*\t$phase() {/,/[0-9].*\t}/p" \
		   | grep '\bcd ')"
156
157
158
159
160
161
162
163
164
165
166

	# Number of ocurrences of the 'cd' command being used
	# Used to tell if we are in a phase() with a single cd statement
	# in that case we can be free to warn the user that their cd statement
	# is superfluous if it is to "$builddir", this avoids problems of previous
	# 'cd' statements to other places giving false positives
	cdscount="$(printf "%s\\n" "$cds" | wc -l)"

	# if the previous line had a 'cd "$builddir"' statement
	prevcd=0

Leo's avatar
Leo committed
167
168
169
	# If it is the first cd of the program
	firstcd=1

170
171
172
173
174
175
176
177
178
179
180
	# Use newline as our IFS delimiter, so we can iterate over lines with
	# the for construct, since the while loop will create a subshell that
	# prevents the value of the prevcd variable from being propagated
	# to future runs
	OLDIFS="$IFS"
	IFS="
"
	for line in $(printf "%s\\n" "$cds"); do
		linenum="$(printf "%s\\n" "$line" | awk '{ print $1 }')"
		statement="$(printf "%s\\n" "$line" | awk '{ $1="" ; print $0 }')"
		[ -z "$statement" ] && continue
Leo's avatar
Leo committed
181
182
		if echo "$statement" | grep -E -q 'cd ["]?\$[{]?builddir["}]?+($| )' ; then
			if [ "$prevcd" -eq 1 ] || [ "$cdscount" -eq 1 ] || [ "$firstcd" -eq 1 ]; then
183
				printf "MP:[AL13]:%s:%s:cd \"\$builddir\" can be removed in phase '%s'\\n" \
Leo's avatar
Leo committed
184
185
186
					"$apkbuild" \
					"$linenum" \
					"$phase"
187
188
189
190
191
			fi
			prevcd=1
		else
			prevcd=0
		fi
Leo's avatar
Leo committed
192
193
		# Can be set to 0 in the first loop and the re-set it to 0 in any next loops
		firstcd=0
194
	done
195
	IFS="$OLDIFS"
196
197
}

198
199
pkgname_has_uppercase() {
	[ "$SKIP_PKGNAME_HAS_UPPERCASE" ] && return 0
Leo's avatar
Leo committed
200
	[ "$SKIP_AL14" ] && return 0
201
	scan '^pkgname=[a-z0-9\._\-]*[A-Z]' 'pkgname must not have uppercase characters' 'AL14' 'SC'
202
203
204
205
}

pkgver_has_pkgrel() {
	[ "$SKIP_PKGVER_HAS_PKGREL" ] && return 0
Leo's avatar
Leo committed
206
	[ "$SKIP_AL15" ] && return 0
207
	scan '^pkgver=[A-Za-z0-9_\-\.]*(-r|_r(?!c))' 'pkgver must not have -r or _r' 'AL15' 'SC'
208
209
}

Leo's avatar
Leo committed
210
211
212
213
214
215
216
217
_builddir_is_set() {
	[ "$SKIP__BUILDDIR_IS_SET" ] && return 0
	[ "$SKIP_AL26" ] && return 0
	if [ -z "$builddir" ] && [ -n "$_builddir" ]; then
		scan '^_builddir=' 'rename _builddir to builddir' 'AL26' 'SP'
	fi
}

Leo's avatar
Leo committed
218
219
220
ret=0
for apkbuild; do
	if [ -f "$apkbuild" ]; then
Leo's avatar
Leo committed
221
222

	# Source apkbuild, we need some nice values
223
224
225
226
	srcdir="" . "$apkbuild" || {
		echo "Failed to source APKBUILD in '$apkbuild'" ;
		continue
	}
Leo's avatar
Leo committed
227
	default_builddir_value &
Leo's avatar
Leo committed
228
	_builddir_is_set &
Leo's avatar
Leo committed
229

Leo's avatar
Leo committed
230
231
232
233
234
235
236
237
238
239
240
241
	unnecessary_return_1 &
	pkgname_quoted &
	pkgver_quoted &
	empty_variable &
	custom_variable &
	indent_tabs &
	trailing_whitespace &
	backticks_usage &
	function_keyword &
	space_before_function_parenthesis &
	space_after_function_parenthesis &
	newline_opening_brace &
242
	pkgname_has_uppercase &
243
	pkgver_has_pkgrel &
244

Leo's avatar
Leo committed
245
246
	# Don't perform these checks on packages from main
	if ! [ -z "${apkbuild##*main/*}" ]; then
247
	for phase in prepare build check package; do
Leo's avatar
Leo committed
248
		superfluous_cd_builddir "$phase" &
Leo's avatar
Leo committed
249
250
	done
	fi
Leo's avatar
Leo committed
251
	wait
Leo's avatar
Leo committed
252
253
	else
	echo no such apkbuild "$apkbuild" 1>&2
Leo's avatar
Leo committed
254
	fi | sort -t: -V | grep . && ret=1
Leo's avatar
Leo committed
255
256
done
exit $ret