apkbuild-lint 5.51 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 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
# grep - for grep with -P

export LC_ALL=C

scan() {
	local rx="$1" msg="$2"
	grep -P -Hn -e "$rx" "$apkbuild" |
		sed "s/^\([^:]*:[^:]*:\)\(.*\)/\1 $msg/"
}

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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
default_builddir_value() {
	[ "$SKIP_DEFAULT_BUILDDIR_VALUE" ] && return 0
	if [ "$builddir" = "/$pkgname-$pkgver" ]; then
		scan '^builddir=' "builddir can be removed as it is the default value"
	fi
}

unnecessary_return_1() {
	[ "$SKIP_UNNECESSARY_RETURN_1" ] && return 0
	scan '\|\| return 1' "|| return 1 is not required as set -e is used"
}

pkgname_quoted() {
	[ "$SKIP_PKGNAME_QUOTED" ] && return 0
	scan '^pkgname="[^$]+"' "pkgname must not be quoted"
}

pkgver_quoted() {
	[ "$SKIP_PKGVER_QUOTED" ] && return 0
	scan '^pkgver="[^$]+"' "pkgver must not be quoted"
}

empty_variable() {
	[ "$SKIP_EMPTY_VARIABLE" ] && return 0
86
	scan '^[A-Za-z0-9_]*=(""|''|)$' "variable set to empty string: \2"
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
}

custom_variable() {
	[ "$SKIP_CUSTOM_VARIABLE" ] && return 0
	scan '^(?!\s*^('"$variables"'))[^\s=-]+=' \
		"prefix custom variable with _: \2"
}

indent_tabs() {
	[ "$SKIP_INDENT_TABS" ] && return 0
	scan '^  ' "indent with tabs"
}

trailing_whitespace() {
	[ "$SKIP_TRAILING_WHITESPACE" ] && return 0
	scan '[\t ]$' "trailing whitespace"
}

backticks_usage() {
	[ "$SKIP_BACKTICKS_USAGE" ] && return 0
	scan '[^\\]`' "use \$() instead of backticks"
}

function_keyword() {
	[ "$SKIP_FUNCTION_KEYWORD" ] && return 0
	scan '^\t*function\b' 'do not use the function keyword'
}

space_before_function_parenthesis() {
	[ "$SKIP_SPACE_BEFORE_FUNCTION_PARENTHESIS" ] && return 0
	scan '^\t*[^ ]*  *\(\)' 'do not use space before function parenthesis'
}

space_after_function_parenthesis() {
	[ "$SKIP_SPACE_AFTER_FUNCTION_PARENTHESIS" ] && return 0
	scan '^\t*[^ ]*\(\)(|   *){' 'use one space after function parenthesis'
}

newline_opening_brace() {
	[ "$SKIP_NEWLINE_OPENING_BRACE" ] && return 0
	scan '^\t*[^ ]*\(\)$' 'do not use a newline before function opening brace'
}

superfluous_cd_builddir() {
	[ "$SKIP_SUPERFLUOUS_CD_BUILDDIR" ] && return 0
132 133 134 135 136 137 138 139
	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" \
140 141
		   | sed -n "/^\s\+[0-9].*\t$phase() {/,/[0-9].*\t}/p" \
		   | grep '\bcd ')"
142 143 144 145 146 147 148 149 150 151 152

	# 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
153 154 155
	# If it is the first cd of the program
	firstcd=1

156 157 158 159 160 161 162 163 164 165 166
	# 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
167 168
		if echo "$statement" | grep -E -q 'cd ["]?\$[{]?builddir["}]?+($| )' ; then
			if [ "$prevcd" -eq 1 ] || [ "$cdscount" -eq 1 ] || [ "$firstcd" -eq 1 ]; then
169
				printf "%s:%s: cd \"\$builddir\" can be removed in phase '%s'\\n" "$apkbuild" "$linenum" "$phase"
170 171 172 173 174
			fi
			prevcd=1
		else
			prevcd=0
		fi
Leo's avatar
Leo committed
175 176
		# Can be set to 0 in the first loop and the re-set it to 0 in any next loops
		firstcd=0
177
	done
178
	IFS="$OLDIFS"
179 180
}

181 182
pkgname_has_uppercase() {
	[ "$SKIP_PKGNAME_HAS_UPPERCASE" ] && return 0
183 184 185 186 187
	scan '^pkgname=[a-z0-9\._\-]*[A-Z]' 'pkgname must not have uppercase characters'
}

pkgver_has_pkgrel() {
	[ "$SKIP_PKGVER_HAS_PKGREL" ] && return 0
188
	scan '^pkgver=[A-Za-z0-9_\-\.]*(-r|_r(?!c))' 'pkgver must not have -r or _r'
189 190
}

Leo's avatar
Leo committed
191 192 193
ret=0
for apkbuild; do
	if [ -f "$apkbuild" ]; then
Leo's avatar
Leo committed
194 195

	# Source apkbuild, we need some nice values
Leo's avatar
Leo committed
196
	srcdir="" . "$apkbuild"
Leo's avatar
Leo committed
197
	default_builddir_value &
Leo's avatar
Leo committed
198

Leo's avatar
Leo committed
199 200 201 202 203 204 205 206 207 208 209 210
	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 &
211
	pkgname_has_uppercase &
212
	pkgver_has_pkgrel &
213

Leo's avatar
Leo committed
214 215
	# Don't perform these checks on packages from main
	if ! [ -z "${apkbuild##*main/*}" ]; then
216
	for phase in prepare build check package; do
Leo's avatar
Leo committed
217
		superfluous_cd_builddir "$phase" &
Leo's avatar
Leo committed
218 219
	done
	fi
Leo's avatar
Leo committed
220
	wait
Leo's avatar
Leo committed
221 222 223 224 225
	else
	echo no such apkbuild "$apkbuild" 1>&2
	fi | sort -t: -n -k2 | grep . && ret=1
done
exit $ret