#!/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 # 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 } 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 } 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 # 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 srcdir="" . "$apkbuild" 2>/dev/null if [ "$builddir" = "/$pkgname-$pkgver" ]; then scan '^builddir=' "builddir can be removed as it is the default value" fi 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 # 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 done fi 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 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" scan '^[ =]*=(|""|''|)$' "variable set to empty string: \2" 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' for _r in $(find_dupe "$pkgname" "$_repo"); do printf "$apkbuild:: package is already present in $_r\n" done # 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