#!/bin/sh # xlint TEMPLATE - scan XBPS template for common mistakes # xlint :PKGNAME - lint template as staged in the git index # xlint : - lint all templates staged in the git index export LC_ALL=C scan() { local rx="$1" msg="$2" grep -P -Hn -e "$rx" "$template" | grep -v -P -e "[^:]*:[^:]*:\s*#" | sed "s/^\([^:]*:[^:]*:\)\(.*\)/\1 $msg/" } once() { head -n 1 } header() { if [ "$(head -n1 "$template")" != "# Template file for '$pkgname'" ]; then echo "$argument:1: Header should be: # Template file for '$pkgname'" fi } exists_once() { for var in pkgname version revision short_desc maintainer license \ homepage; do case "$(grep -c "^${var}=" "$template")" in 0) echo "$argument: '$var' missing!";; 1) ;; *) echo "$argument: '$var' defined more than once";; esac done } variables_order() { local curr_index max_index max_index_line variables_end message line max_index=0 while IFS="" read -r line; do case "$line" in pkgname=*) curr_index=1;; reverts=*) curr_index=2;; version=*) curr_index=3;; revision=*) curr_index=4;; archs=*) curr_index=5;; wrksrc=*) curr_index=7;; create_wrksrc=*) curr_index=8;; build_wrksrc=*) curr_index=9;; build_style=*) curr_index=11;; build_helper=*) curr_index=11;; cmake_args=*) curr_index=12;; cmake_builddir=*) curr_index=12;; configure_args=*) curr_index=12;; configure_script=*) curr_index=12;; go_build_tags=*) curr_index=12;; go_get=*) curr_index=12;; go_import_path=*) curr_index=12;; go_ldflags=*) curr_index=12;; go_package=*) curr_index=12;; go_mod_mode=*) curr_index=12;; make_build_args=*) curr_index=12;; make_build_target=*) curr_index=12;; make_check_args=*) curr_index=12;; make_check_target=*) curr_index=12;; make_cmd=*) curr_index=12;; make_install_args=*) curr_index=12;; make_install_target=*) curr_index=12;; make_use_env=*) curr_index=12;; meson_builddir=*) curr_index=12;; meson_cmd=*) curr_index=12;; meson_crossfile=*) curr_index=12;; perl_configure_dirs=*) curr_index=12;; pycompile_dirs=*) curr_index=12;; pycompile_module=*) curr_index=12;; python_versions=*) curr_index=12;; stackage=*) curr_index=12;; conf_files=*) continue;; make_dirs=*) continue;; hostmakedepends=*) curr_index=15;; makedepends=*) curr_index=16;; depends=*) curr_index=17;; checkdepends=*) curr_index=18;; short_desc=*) curr_index=19;; maintainer=*) curr_index=20;; license=*) curr_index=21;; homepage=*) curr_index=22;; changelog=*) curr_index=23;; distfiles=*) curr_index=24;; checksum=*) curr_index=25;; alternatives=*) curr_index=26;; fetch_cmd=*) curr_index=26;; bootstrap=*) continue;; "#"*) continue;; " "*) continue;; " "*) continue;; "_"*"="*) continue;; *"="*) curr_index=27;; "") variables_end=1;; *"{") variables_end=1;; *) continue;; esac if [ "$variables_end" ]; then break elif [ "$curr_index" -lt "$max_index" ]; then message="$argument: Place $max_index_line= after ${line%%=*}=" elif [ "$curr_index" -gt "$max_index" ]; then max_index="$curr_index" max_index_line="${line%%=*}" if [ "$message" ]; then echo "$message" message= fi fi done < "$template" [ "$message" ] && echo "$message" } file_end() { if [ "$(tail -c 1 $template)" ]; then echo "$template:$(( $(wc -l < $template) + 1 )): File does not end with newline character" elif [ -z "$(tail -c 2 $template)" ]; then echo "$template:$(wc -l < $template): Last line is empty" fi } variables=$(echo -n "#.* _.* .*_descr .*_groups .*_homedir .*_pgroup .*_shell desc_option_.* AR AS CC CFLAGS CPP CPPFLAGS CXX CXXFLAGS GCC LD LDFLAGS LD_LIBRARY_PATH NM OBJCOPY OBJDUMP RANLIB READELF STRIP XBPS_FETCH_CMD allow_unknown_shlibs alternatives archs binfmts bootstrap broken build_options build_options_default build_style build_helper build_wrksrc changelog checkdepends checksum cmake_builddir conf_files configure_args configure_script conflicts create_wrksrc depends disable_parallel_build distfiles dkms_modules fetch_cmd font_dirs force_debug_pkgs go_build_tags go_get go_import_path go_ldflags go_package go_mod_mode homepage hostmakedepends ignore_elf_dirs ignore_elf_files keep_libtool_archives kernel_hooks_version lib32depends lib32disabled lib32files lib32mode lib32symlinks license maintainer make_build_args make_build_target make_check make_check_args make_check_target make_cmd make_dirs make_install_args make_install_target make_use_env makedepends meson_builddir meson_cmd meson_crossfile mutable_files nofixperms nocheckperms no_generic_pkgconfig_link nocross nodebug nopie noshlibprovides nostrip nostrip_files noverifyrdeps patch_args pkgname preserve provides pycompile_dirs pycompile_module python_version register_shell replaces repository restricted reverts revision run_depends sgml_catalogs sgml_entries shlib_provides shlib_requires short_desc skip_extraction skiprdeps stackage subpackages system_accounts system_groups tags triggers version wrksrc xml_catalogs xml_entries" | tr '\n' '|') old_accounts=$(echo -n "at avahi bitlbee boinc caddy chrony clocksd colord couchpotato cups dbus deluge dictd dnscrypt_proxy dnsmasq elastic elog etcd fcron ftp gdm gerbera gitolite gogs gpsd h2o haproxy icinga inadyn inspircd jenkins kube ldap libvirt lidarr lightdm mail minidlna monero mopidy mpd munge mysql named nbd ndhc nethack nginx nsd nslcd ntpd nut openntpd orientdb polkitd postfix postgres privoxy prosody pulse puppet quassel radicale redis relaysrv rmilter rpc rspamd rsvlog rtkit sddm shadowsocks sndiod squid storm suricata synapse taskd tomcat tor transmission tss tuntox umurmur usbmux voidupdates x2gouser xbmc znc" | tr '\n' '|') void_packages="$(xdistdir 2>/dev/null)/" ret=0 if [ "$1" = ":" ]; then # get a list of all templates staged in the git index set -- $(git -C "$void_packages" diff --cached --name-only | sed -ne 's|^srcpkgs/\([^/]*\)/template$|:\1|p') fi for argument; do template= if [ -f "$argument" ]; then template="$argument" elif [ "${argument#:}" != "$argument" ]; then trap "rm -- ${tmpfile:=$(mktemp)}" EXIT INT TERM git -C "$void_packages" show ":srcpkgs/${argument#:}/template" \ > ${template:=$tmpfile} || continue else _template="${void_packages}srcpkgs/$argument/template" [ -f "$_template" ] && template="$_template" fi if [ "$template" ]; then exists_once "$template" scan 'short_desc=.*\."' "unwanted trailing dot in short_desc" scan 'short_desc=["'\''][a-z]' "short_desc should start uppercase" scan 'short_desc=["'\''](An?|The) ' "short_desc should not start with an article" scan 'short_desc=["'\''][\t ]' "short_desc should not start with whitespace" scan 'short_desc=["'\''].{73}' "short_desc should be less than 72 chars" scan 'license=.*[^NL]GPL[^-]' "license GPL without version" scan 'license=.*SSPL' "Uses the SSPL license, which is not packageable" scan 'license=.*LGPL[^-]' "license LGPL without version" if ! grep -q vlicense "$template"; then for l in custom AGPL MIT BSD ISC X11; do scan "license=.*$l" "license '$l', but no use of vlicense" done else if ! grep license= "$template" | grep -Pqv -e 'license="(\b(LGPL|GPL|GFDL|Apache)-\S+(, )?)+"'; then scan "vlicense" 'license '"$(grep -Po license='\K.+' "$template" | tr -d '",' | tr '\n' ' ')"'should not be installed' | once fi fi : "${LICENSE_LIST:=/usr/share/spdx/license.lst}" if [ -f "${LICENSE_LIST}" ]; then sed -n 's/license="\(.*\)"/\1/p' "$template" | sed 's/ WITH /,/g' | tr , "\n" | while read -r l; do case "$l" in custom:*|'Public Domain') continue ;; esac if ! grep -q "^${l}$" "${LICENSE_LIST}"; then scan "license=.*$l" "use SPDX id for '$l' license or see Manual.md" fi done fi if ! sed -n '/^version=/{n;/revision=/b;q1}' "$template"; then scan 'revision=' "revision does not appear immediately after version" fi scan 'vinstall.* 0?755.*usr/bin' "use vbin" scan 'vinstall.* usr/share/man' "use vman" scan 'vinstall.* usr/share/licenses' "use vlicense" scan '^ ' "please indent with tabs" | once scan '[\t ]$' "trailing whitespace" scan '[^\\]`' "use \$() instead of backticks" scan '^pkgname="[^$]+"' "pkgname must not be quoted" scan 'revision=0' "revision must not be zero" scan '^version=.*[:-].*' "version must not contain the characters : or -" scan '^version=.*\${.*[:!#%/^,@].*}.*' "version must not use shell variable substitution mechanism" scan '^version="[^$]+"' "version must not be quoted" scan '^reverts=.*-.*' "reverts must not contain package name" scan '^reverts=(?!.*_.*).*' "reverts without revision" scan 'archs=.?noarch.?' "noarch is deprecated and should no longer be used" scan 'replaces=(?=.*\w)[^<>]*$' "replaces needs depname with version" scan 'homepage=.*\$' "homepage should not use variables" scan 'maintainer=(?!.*<.*@.*>).*' "maintainer needs email address" scan 'maintainer=.*<.*@users.noreply.github.com>.*' "maintainer needs a valid address for sending mail" scan '^(?!\s*('"$variables"'))[^\s=-]+=' \ "custom variables should use _ prefix: \2" scan '^[^ =]*=(""|''|)$' "variable set to empty string: \2" scan '^(.*)-docs_package().*' 'use -doc subpackage for documentation' scan 'distfiles=.*github.com.*/archive/.*\.zip[\"]?$' 'Use the distfile .tar.gz instead of .zip' scan 'distfiles=.*downloads\.sourceforge\.net' 'use $SOURCEFORGE_SITE' scan 'distfiles=.*savannah.nongnu\.org' 'use $NONGNU_SITE' scan 'distfiles=.*archive\.ubuntu\.com' 'use $UBUNTU_SITE' scan 'distfiles=.*x\.org/releases/individual' 'use $XORG_SITE' scan 'distfiles=.*ftp.*debian\.org' 'use $DEBIAN_SITE' scan 'distfiles=.*gnome\.org/pub' 'use $GNOME_SITE' scan 'distfiles=.*www\.kernel\.org/pub/linux' 'use $KERNEL_SITE' scan 'distfiles=.*cpan\.org/modules/by-module' 'use $CPAN_SITE' scan 'distfiles=.*files\.pythonhosted\.org/packages' 'use $PYPI_SITE' scan 'distfiles=.*ftp\.mozilla\.org' 'use $MOZILLA_SITE' scan 'distfiles=.*ftp\.gnu\.org/(pub/)?gnu' 'use $GNU_SITE' scan 'distfiles=.*freedesktop\.org/software' 'use $FREEDESKTOP_SITE' scan 'distfiles=.*download.kde.org/stable' 'use $KDE_SITE' scan 'distfiles=.*xorg\.freedesktop\.org/wiki/' 'use $XORG_HOME' scan 'usr/lib/python3.[0-9]/site-packages' 'use $py3_sitelib' scan 'pycompile_module=' 'do not set pycompile_module, it is autodetected' scan '^wrksrc=(\$\{[^}]+\}|[^${}/])*/.+' 'wrksrc should be a top-level directory' 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' scan 'python_version=.*#[[:space:]]*unverified' 'verify python_version and remove "#unverified"' pkgname=$(grep -Po "^pkgname=\K.*" "$template") version=$(grep -Po "^version=\K.*" "$template") scan "distfiles=.*\Q$version\E" 'use ${version} in distfiles instead' scan "system_accounts=.*\b(?!($old_accounts))[a-zA-Z]" 'new accounts should be prefixed with underscore' variables_order header file_end else echo no such template "$argument" 1>&2 fi | sort -t: -n -k2 | grep . && ret=1 done exit $ret