#!/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="$(printf '%s\n' "$2" | sed 's,/,\\/,g')" grep -P -hn -e "$rx" "$template" | grep -v -P -e "[^:]*:\s*#" | sed "s/^\([^:]*\):\(.*\)/\1: $msg/" | while read line; do echo "$argument:$line" done } check_old_vars() { sed -E 's/^([^:]+:[0-9]+:).*: ('"$old_variables"')=.*$/\1 \2 is deprecated and should not be used/' } regex_escape() { sed 's/\([.*+]\)/\\\1/g' } 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:1: '$var' missing!";; 1) ;; *) lines="$(grep -n "^${var}=" "$template" | awk -F: 'NR>1 { printf ", " } { printf "%s", $1 }')" echo "$argument:${lines##*, }: '$var' defined more than once: previously on line(s) ${lines%,*}" ;; esac done } explain_make_check() { awk "-vargument=$argument" -vOFS=: ' /make_check=[^#]*$/ && prev !~ /^[[:blank:]]*#/ {print(argument, FNR, " explain why the tests fail")} {prev=$0} ' $template } variables_order() { local curr_index max_index max_index_line variables_end message line local line_number max_index_line_number max_index=0 line_number=0 while IFS="" read -r line; do line_number=$((line_number + 1)) case "$line" in pkgname=*) curr_index=1;; reverts=*) curr_index=2;; version=*) curr_index=3;; revision=*) curr_index=4;; archs=*) curr_index=5;; 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:$max_index_line_number: Place $max_index_line= after ${line%%=*}=" elif [ "$curr_index" -gt "$max_index" ]; then max_index="$curr_index" max_index_line="${line%%=*}" max_index_line_number=$line_number if [ "$message" ]; then echo "$message" message= fi fi done < "$template" [ "$message" ] && echo "$message" } file_end() { if [ "$(tail -c 1 $template)" ]; then echo "$argument:$(( $(wc -l < $template) + 1 )): File does not end with newline character" elif [ -z "$(tail -c 2 $template)" ]; then echo "$argument:$(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 disable_parallel_check 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 xml_catalogs xml_entries" | tr '\n' '|') old_variables=$(echo -n "long_desc wrksrc" | 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 argument="${argument#:}" trap "rm -- ${tmpfile:=$(mktemp)}" EXIT INT TERM # get template as staged in the git index 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=.*[[:space:]]"' "unwanted trailing space 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=["'\''].{74}' "short_desc should be at most 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 -E 's/ (WITH|OR|AND) /,/g' | sed 's/[()]//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' "bad indentation: space before tabs" 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 : 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" | check_old_vars 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 'distfiles=.*crates.io/api/' 'use https://static.crates.io/crates/pkgname/pkgname-${version}.crate' scan 'usr/lib/python3.[0-9]/site-packages' 'use $py3_sitelib' scan 'pycompile_module=' 'do not set pycompile_module, it is autodetected' 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" | once) pkgname_re=$(echo "$pkgname" | regex_escape) version=$(grep -Po "^version=\K.*" "$template" | once) 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' scan "cargo update (--package|-p) [A-Za-z_][A-Za-z0-9_]*(?!\:[0-9]+\.[0-9]+\.[0-9]+)(\s.+)?$" '"cargo update" commands should include the specific version we are updating from in the --package SPEC' scan "cargo update (--package|-p) [A-Za-z_][A-Za-z0-9_-]*\:[0-9]+\.[0-9]+\.[0-9]+(?!--precise [0-9]+\.[0-9]+\.[0-9]+)$" '"cargo update" commands should include the specific version we are updating to, using --precise' scan '-D([[:alnum:]_-]+)=\$\(vopt_if \1 true false\)' 'use $(vopt_bool ...) instead of -D...=$(vopt_if ...)' variables_order | grep -Pv "($old_variables)=" header file_end explain_make_check else echo no such template "$argument" >&2 fi | sort -t: -n -k2 -k3 | grep . && ret=1 done exit $ret