about summary refs log tree commit diff
path: root/xlint
blob: f247377c7ffa71ce93b94d38478ad47dc0f1ad23 (plain) (blame)
1
2
3
4
5
6
7
8
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
#!/bin/sh
# xlint TEMPLATE - scan XBPS template for common mistakes

export LC_ALL=C

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

once() {
	head -n 1
}

header() {
	if [ "$(head -n1 "$template")" != "# Template file for '$pkgname'" ]; then
		echo "$template: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 "$template: '$var' missing!";;
			1) ;;
			*) echo "$template: '$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;;
			only_for_archs=*) continue;;
			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="$template: 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_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
nocross
nodebug
nopie
noshlibprovides
nostrip
nostrip_files
noverifyrdeps
only_for_archs
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' '|')

void_packages="$(xdistdir 2>/dev/null)/"
ret=0
for argument; do
	template=
	if [ -f "$argument" ]; then
		template="$argument"
	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; 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" | 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 '^  ' "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 <pkgname>-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'
	variables_order
	header
	file_end
	else
	echo no such template "$argument" 1>&2
	fi | sort -t: -n -k2 | grep . && ret=1
done
exit $ret