alint 5.8 KB
Newer Older
Leo's avatar
Leo committed
1 2 3 4 5 6 7 8 9 10 11 12
#!/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):
# busybox - for sed, tr, sort, head and other simple utiltiies
# grep - for grep with -P

export LC_ALL=C

Leo's avatar
Leo committed
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
# Finds from which repo a package comes from
# it can return multiple values if it finds multiple matches
find_repo() {
	[ -z "$1" ] || [ -z "$2" ] && return 0

	local pkgname="$1"
	# Repo the package we are linting currently is. We want it
	# for avoiding checks on repos we don't want
	local targetrepo="$2"

	# Unmaintained is the top of the ladder, it can depend on any
	# of the steps below
	if [ "$targetrepo" = "unmaintained" ]; then
		return 0
	fi

	# Perform some transformations that can be done easily and cheaply
	# and are common.
	#
	# This is a hack until apk has something like xpkg -m or aports adopt
	# the xbps-src symlinks
	pkgname="${pkgname%-dev}"
	pkgname="${pkgname%-doc}"
	pkgname="${pkgname%-openrc}"
	pkgname="${pkgname%-bash-completion}"
	pkgname="${pkgname%-zsh-completion}"
	pkgname="${pkgname%-fish-completion}"
	# Disabled because it can cause conflicts with -dev packages, there is glade and libglade
	# which are separate packages but end up causing false-postiives
	# pkgname="${pkgname#lib}"
	pkgname="${pkgname%-static}"
	pkgname="${pkgname%-lang}"

	check_in_repo() { test -d "$1"/"$2" && echo "$1" ; }

	case "$targetrepo" in
		testing) 
			check_in_repo unmaintained "$pkgname"
			;;
		community)
			check_in_repo unmaintained "$pkgname"
			check_in_repo testing "$pkgname"
			;;
		main)
			check_in_repo unmaintained "$pkgname"
			check_in_repo testing "$pkgname"
			check_in_repo community "$pkgname"
			;;
	esac
}

64 65 66 67 68 69 70 71 72 73 74 75
find_dupe() {
	local pkgname="$1"
	local repo="$2"

	check_in_repo() { test -d "$1"/"$2" && echo "$1" ; }

	for r in unmaintained testing community main; do
		[ "$r" = "$repo" ] && continue
		check_in_repo "$r" "$pkgname"
	done
}

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

once() {
	head -n 1
}

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
url" | tr '\n' '|')

ret=0
for apkbuild; do
	if [ -f "$apkbuild" ]; then
Leo's avatar
Leo committed
131 132 133 134 135 136 137 138 139 140 141

	# Try to guess the repo, first see if our working directory is where
	# the repo is located
	_repo="$(basename "$(dirname "$(dirname $PWD)")")"
	case "$repo" in
		main|community|testing|unmaintained) ;;
		# Then have the path given to use be used
		*) _repo="$(basename "$(dirname "$(dirname $apkbuild)")")" ;;
	esac

	# Source apkbuild, we need some nice values
Leo's avatar
Leo committed
142 143 144 145 146
	srcdir="" . "$apkbuild" 2>/dev/null
	if [ "$builddir" = "/$pkgname-$pkgver" ]; then
		scan '^builddir=' "builddir can be removed as it is the default value"
	fi

Leo's avatar
Leo committed
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
	if [ ! -z "$depends" ]; then
		printf "%s\n" "$depends" | tr " " "\n" | sort -u | while read -r pkg; do
			for p in $(find_repo "$pkg" "$_repo"); do
				printf "$apkbuild:: depends '$pkg' is in upper repo '$p'\n"
			done
		done
		# Find duplicates in
		printf "%s\n" "$depends" | tr " " "\n" | sort | uniq -d | while read -r dup; do
			[ -z "$dup" ] && continue
			printf "$apkbuild:: duplicate '$dup' in depends\n"
		done
	fi

	if [ ! -z "$makedepends" ]; then
		printf "%s\n" "$makedepends" | tr " " "\n" | sort -u | while read -r pkg; do
			for p in $(find_repo "$pkg" "$_repo"); do
				printf "$apkbuild:: makedepends '$pkg' is in upper repo '$p'\n"
			done
		done
		printf "%s\n" "$makedepends" | tr " " "\n" | sort | uniq -d | while read -r dup; do
			[ -z "$dup" ] && continue
			printf "$apkbuild:: duplicate '$dup' in makedepends\n"
		done
	fi

	if [ ! -z "$checkdepends" ]; then
173 174 175 176 177 178 179
		# We don't want to check for packages in upper repos for checkdepends if we
		# don't have 'check' enabled.
		if [ ! -z "${options##*!check*}" ]; then
			printf "%s\n" "$checkdepends" | tr " " "\n" | sort -u | while read -r pkg; do
				for p in $(find_repo "$pkg" "$_repo"); do
					printf "$apkbuild:: checkdepends '$pkg' is in upper repo '$p'\n"
				done
Leo's avatar
Leo committed
180
			done
181
		fi
Leo's avatar
Leo committed
182 183 184 185 186 187
		printf "%s\n" "$checkdepends" | tr " " "\n" | sort | uniq -d | while read -r dup; do
			[ -z "$dup" ] && continue
			printf "$apkbuild:: duplicate '$dup' in checkdepends\n"
		done
	fi

Leo's avatar
Leo committed
188 189 190
	scan '\|\| return 1' "|| return 1 is not required as set -e is used"
	scan '^pkgname="[^$]+"' "pkgname must not be quoted"
	scan '^pkgver="[^$]+"' "pkgver must not be quoted"
Leo's avatar
Leo committed
191
	scan '^[ =]*=(|""|''|)$' "variable set to empty string: \2"
Leo's avatar
Leo committed
192 193 194 195 196 197 198 199 200
	scan '^(?!\s*^('"$variables"'))[^\s=-]+=' \
		 "prefix custom variable with _: \2"
	scan '^  ' "indent with tabs"
	scan '[\t ]$' "trailing whitespace"
	scan '[^\\]`' "use \$() instead of backticks"
	scan '^\t*function\b' 'do not use the function keyword'
	scan '^\t*[^ ]*  *\(\)' 'do not use space before function parenthesis'
	scan '^\t*[^ ]*\(\)(|   *){' 'use one space after function parenthesis'
	scan '^\t*[^ ]*\(\)$' 'do not use a newline before function opening brace'
201 202 203 204 205

	for _r in $(find_dupe "$pkgname" "$_repo"); do
		printf "$apkbuild:: package is already present in $_r\n"
	done

Leo's avatar
Leo committed
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
	# Don't perform these checks on packages from main
	if ! [ -z "${apkbuild##*main/*}" ]; then
	for phase in build check package; do
		sed -n "/^$phase() {/,/^}/{p;=}" "$apkbuild" | grep -m 1 -B 1 'cd "$builddir"$' | head -1 | while read -r l; do
			if [ -z "$l" ]; then
				continue
			fi
			printf "$apkbuild:$l: cd \"\$builddir\" can be removed\n"
		done
	done
	fi
	else
	echo no such apkbuild "$apkbuild" 1>&2
	fi | sort -t: -n -k2 | grep . && ret=1
done
exit $ret