summaryrefslogtreecommitdiffstats
path: root/meta/classes/insane.bbclass
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes/insane.bbclass')
-rw-r--r--meta/classes/insane.bbclass1153
1 files changed, 1153 insertions, 0 deletions
diff --git a/meta/classes/insane.bbclass b/meta/classes/insane.bbclass
new file mode 100644
index 0000000000..c6dea22618
--- /dev/null
+++ b/meta/classes/insane.bbclass
@@ -0,0 +1,1153 @@
1# BB Class inspired by ebuild.sh
2#
3# This class will test files after installation for certain
4# security issues and other kind of issues.
5#
6# Checks we do:
7# -Check the ownership and permissions
8# -Check the RUNTIME path for the $TMPDIR
9# -Check if .la files wrongly point to workdir
10# -Check if .pc files wrongly point to workdir
11# -Check if packages contains .debug directories or .so files
12# where they should be in -dev or -dbg
13# -Check if config.log contains traces to broken autoconf tests
14# -Ensure that binaries in base_[bindir|sbindir|libdir] do not link
15# into exec_prefix
16# -Check that scripts in base_[bindir|sbindir|libdir] do not reference
17# files under exec_prefix
18
19
20# unsafe-references-in-binaries requires prelink-rtld from
21# prelink-native, but we don't want this DEPENDS for -native builds
22QADEPENDS = "prelink-native"
23QADEPENDS_class-native = ""
24QADEPENDS_class-nativesdk = ""
25QA_SANE = "True"
26
27# Elect whether a given type of error is a warning or error, they may
28# have been set by other files.
29WARN_QA ?= "ldflags useless-rpaths rpaths staticdev libdir xorg-driver-abi \
30 textrel already-stripped incompatible-license files-invalid \
31 installed-vs-shipped compile-host-path install-host-path \
32 pn-overrides infodir build-deps file-rdeps \
33 "
34ERROR_QA ?= "dev-so debug-deps dev-deps debug-files arch pkgconfig la \
35 perms dep-cmp pkgvarcheck perm-config perm-line perm-link \
36 split-strip packages-list pkgv-undefined var-undefined \
37 version-going-backwards \
38 "
39
40ALL_QA = "${WARN_QA} ${ERROR_QA}"
41
42UNKNOWN_CONFIGURE_WHITELIST ?= "--enable-nls --disable-nls --disable-silent-rules --disable-dependency-tracking --with-libtool-sysroot"
43
44#
45# dictionary for elf headers
46#
47# feel free to add and correct.
48#
49# TARGET_OS TARGET_ARCH MACHINE, OSABI, ABIVERSION, Little Endian, 32bit?
50def package_qa_get_machine_dict():
51 return {
52 "darwin9" : {
53 "arm" : (40, 0, 0, True, 32),
54 },
55 "linux" : {
56 "aarch64" : (183, 0, 0, True, 64),
57 "aarch64_be" :(183, 0, 0, False, 64),
58 "arm" : (40, 97, 0, True, 32),
59 "armeb": (40, 97, 0, False, 32),
60 "powerpc": (20, 0, 0, False, 32),
61 "powerpc64": (21, 0, 0, False, 64),
62 "i386": ( 3, 0, 0, True, 32),
63 "i486": ( 3, 0, 0, True, 32),
64 "i586": ( 3, 0, 0, True, 32),
65 "i686": ( 3, 0, 0, True, 32),
66 "x86_64": (62, 0, 0, True, 64),
67 "ia64": (50, 0, 0, True, 64),
68 "alpha": (36902, 0, 0, True, 64),
69 "hppa": (15, 3, 0, False, 32),
70 "m68k": ( 4, 0, 0, False, 32),
71 "mips": ( 8, 0, 0, False, 32),
72 "mipsel": ( 8, 0, 0, True, 32),
73 "mips64": ( 8, 0, 0, False, 64),
74 "mips64el": ( 8, 0, 0, True, 64),
75 "s390": (22, 0, 0, False, 32),
76 "sh4": (42, 0, 0, True, 32),
77 "sparc": ( 2, 0, 0, False, 32),
78 "microblaze": (189, 0, 0, False, 32),
79 "microblazeel":(189, 0, 0, True, 32),
80 },
81 "linux-uclibc" : {
82 "arm" : ( 40, 97, 0, True, 32),
83 "armeb": ( 40, 97, 0, False, 32),
84 "powerpc": ( 20, 0, 0, False, 32),
85 "i386": ( 3, 0, 0, True, 32),
86 "i486": ( 3, 0, 0, True, 32),
87 "i586": ( 3, 0, 0, True, 32),
88 "i686": ( 3, 0, 0, True, 32),
89 "x86_64": ( 62, 0, 0, True, 64),
90 "mips": ( 8, 0, 0, False, 32),
91 "mipsel": ( 8, 0, 0, True, 32),
92 "mips64": ( 8, 0, 0, False, 64),
93 "mips64el": ( 8, 0, 0, True, 64),
94 "avr32": (6317, 0, 0, False, 32),
95 "sh4": (42, 0, 0, True, 32),
96
97 },
98 "linux-musl" : {
99 "arm" : ( 40, 97, 0, True, 32),
100 "armeb": ( 40, 97, 0, False, 32),
101 "powerpc": ( 20, 0, 0, False, 32),
102 "i386": ( 3, 0, 0, True, 32),
103 "i486": ( 3, 0, 0, True, 32),
104 "i586": ( 3, 0, 0, True, 32),
105 "i686": ( 3, 0, 0, True, 32),
106 "x86_64": ( 62, 0, 0, True, 64),
107 "mips": ( 8, 0, 0, False, 32),
108 "mipsel": ( 8, 0, 0, True, 32),
109 "mips64": ( 8, 0, 0, False, 64),
110 "mips64el": ( 8, 0, 0, True, 64),
111 },
112 "uclinux-uclibc" : {
113 "bfin": ( 106, 0, 0, True, 32),
114 },
115 "linux-gnueabi" : {
116 "arm" : (40, 0, 0, True, 32),
117 "armeb" : (40, 0, 0, False, 32),
118 },
119 "linux-musleabi" : {
120 "arm" : (40, 0, 0, True, 32),
121 "armeb" : (40, 0, 0, False, 32),
122 },
123 "linux-uclibceabi" : {
124 "arm" : (40, 0, 0, True, 32),
125 "armeb" : (40, 0, 0, False, 32),
126 },
127 "linux-gnuspe" : {
128 "powerpc": (20, 0, 0, False, 32),
129 },
130 "linux-muslspe" : {
131 "powerpc": (20, 0, 0, False, 32),
132 },
133 "linux-uclibcspe" : {
134 "powerpc": (20, 0, 0, False, 32),
135 },
136 "linux-gnu" : {
137 "powerpc": (20, 0, 0, False, 32),
138 "sh4": (42, 0, 0, True, 32),
139 },
140 "linux-gnux32" : {
141 "x86_64": (62, 0, 0, True, 32),
142 },
143 "linux-gnun32" : {
144 "mips64": ( 8, 0, 0, False, 32),
145 "mips64el": ( 8, 0, 0, True, 32),
146 },
147 }
148
149
150def package_qa_clean_path(path,d):
151 """ Remove the common prefix from the path. In this case it is the TMPDIR"""
152 return path.replace(d.getVar('TMPDIR',True),"")
153
154def package_qa_write_error(type, error, d):
155 logfile = d.getVar('QA_LOGFILE', True)
156 if logfile:
157 p = d.getVar('P', True)
158 f = file( logfile, "a+")
159 print >> f, "%s: %s [%s]" % (p, error, type)
160 f.close()
161
162def package_qa_handle_error(error_class, error_msg, d):
163 package_qa_write_error(error_class, error_msg, d)
164 if error_class in (d.getVar("ERROR_QA", True) or "").split():
165 bb.error("QA Issue: %s [%s]" % (error_msg, error_class))
166 d.setVar("QA_SANE", False)
167 return False
168 elif error_class in (d.getVar("WARN_QA", True) or "").split():
169 bb.warn("QA Issue: %s [%s]" % (error_msg, error_class))
170 else:
171 bb.note("QA Issue: %s [%s]" % (error_msg, error_class))
172 return True
173
174QAPATHTEST[libexec] = "package_qa_check_libexec"
175def package_qa_check_libexec(path,name, d, elf, messages):
176
177 # Skip the case where the default is explicitly /usr/libexec
178 libexec = d.getVar('libexecdir', True)
179 if libexec == "/usr/libexec":
180 return True
181
182 if 'libexec' in path.split(os.path.sep):
183 messages["libexec"] = "%s: %s is using libexec please relocate to %s" % (name, package_qa_clean_path(path, d), libexec)
184 return False
185
186 return True
187
188QAPATHTEST[rpaths] = "package_qa_check_rpath"
189def package_qa_check_rpath(file,name, d, elf, messages):
190 """
191 Check for dangerous RPATHs
192 """
193 if not elf:
194 return
195
196 if os.path.islink(file):
197 return
198
199 bad_dirs = [d.getVar('BASE_WORKDIR', True), d.getVar('STAGING_DIR_TARGET', True)]
200
201 phdrs = elf.run_objdump("-p", d)
202
203 import re
204 rpath_re = re.compile("\s+RPATH\s+(.*)")
205 for line in phdrs.split("\n"):
206 m = rpath_re.match(line)
207 if m:
208 rpath = m.group(1)
209 for dir in bad_dirs:
210 if dir in rpath:
211 messages["rpaths"] = "package %s contains bad RPATH %s in file %s" % (name, rpath, file)
212
213QAPATHTEST[useless-rpaths] = "package_qa_check_useless_rpaths"
214def package_qa_check_useless_rpaths(file, name, d, elf, messages):
215 """
216 Check for RPATHs that are useless but not dangerous
217 """
218 def rpath_eq(a, b):
219 return os.path.normpath(a) == os.path.normpath(b)
220
221 if not elf:
222 return
223
224 if os.path.islink(file):
225 return
226
227 libdir = d.getVar("libdir", True)
228 base_libdir = d.getVar("base_libdir", True)
229
230 phdrs = elf.run_objdump("-p", d)
231
232 import re
233 rpath_re = re.compile("\s+RPATH\s+(.*)")
234 for line in phdrs.split("\n"):
235 m = rpath_re.match(line)
236 if m:
237 rpath = m.group(1)
238 if rpath_eq(rpath, libdir) or rpath_eq(rpath, base_libdir):
239 # The dynamic linker searches both these places anyway. There is no point in
240 # looking there again.
241 messages["useless-rpaths"] = "%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d), rpath)
242
243QAPATHTEST[dev-so] = "package_qa_check_dev"
244def package_qa_check_dev(path, name, d, elf, messages):
245 """
246 Check for ".so" library symlinks in non-dev packages
247 """
248
249 if not name.endswith("-dev") and not name.endswith("-dbg") and not name.endswith("-ptest") and not name.startswith("nativesdk-") and path.endswith(".so") and os.path.islink(path):
250 messages["dev-so"] = "non -dev/-dbg/-nativesdk package contains symlink .so: %s path '%s'" % \
251 (name, package_qa_clean_path(path,d))
252
253QAPATHTEST[staticdev] = "package_qa_check_staticdev"
254def package_qa_check_staticdev(path, name, d, elf, messages):
255 """
256 Check for ".a" library in non-staticdev packages
257 There are a number of exceptions to this rule, -pic packages can contain
258 static libraries, the _nonshared.a belong with their -dev packages and
259 libgcc.a, libgcov.a will be skipped in their packages
260 """
261
262 if not name.endswith("-pic") and not name.endswith("-staticdev") and not name.endswith("-ptest") and path.endswith(".a") and not path.endswith("_nonshared.a"):
263 messages["staticdev"] = "non -staticdev package contains static .a library: %s path '%s'" % \
264 (name, package_qa_clean_path(path,d))
265
266def package_qa_check_libdir(d):
267 """
268 Check for wrong library installation paths. For instance, catch
269 recipes installing /lib/bar.so when ${base_libdir}="lib32" or
270 installing in /usr/lib64 when ${libdir}="/usr/lib"
271 """
272 import re
273
274 pkgdest = d.getVar('PKGDEST', True)
275 base_libdir = d.getVar("base_libdir",True) + os.sep
276 libdir = d.getVar("libdir", True) + os.sep
277 exec_prefix = d.getVar("exec_prefix", True) + os.sep
278
279 messages = []
280
281 lib_re = re.compile("^/lib.+\.so(\..+)?$")
282 exec_re = re.compile("^%s.*/lib.+\.so(\..+)?$" % exec_prefix)
283
284 for root, dirs, files in os.walk(pkgdest):
285 if root == pkgdest:
286 # Skip subdirectories for any packages with libdir in INSANE_SKIP
287 skippackages = []
288 for package in dirs:
289 if 'libdir' in (d.getVar('INSANE_SKIP_' + package, True) or "").split():
290 bb.note("Package %s skipping libdir QA test" % (package))
291 skippackages.append(package)
292 for package in skippackages:
293 dirs.remove(package)
294 for file in files:
295 full_path = os.path.join(root, file)
296 rel_path = os.path.relpath(full_path, pkgdest)
297 if os.sep in rel_path:
298 package, rel_path = rel_path.split(os.sep, 1)
299 rel_path = os.sep + rel_path
300 if lib_re.match(rel_path):
301 if base_libdir not in rel_path:
302 messages.append("%s: found library in wrong location: %s" % (package, rel_path))
303 if exec_re.match(rel_path):
304 if libdir not in rel_path:
305 messages.append("%s: found library in wrong location: %s" % (package, rel_path))
306
307 if messages:
308 package_qa_handle_error("libdir", "\n".join(messages), d)
309
310QAPATHTEST[debug-files] = "package_qa_check_dbg"
311def package_qa_check_dbg(path, name, d, elf, messages):
312 """
313 Check for ".debug" files or directories outside of the dbg package
314 """
315
316 if not "-dbg" in name and not "-ptest" in name:
317 if '.debug' in path.split(os.path.sep):
318 messages["debug-files"] = "non debug package contains .debug directory: %s path %s" % \
319 (name, package_qa_clean_path(path,d))
320
321QAPATHTEST[perms] = "package_qa_check_perm"
322def package_qa_check_perm(path,name,d, elf, messages):
323 """
324 Check the permission of files
325 """
326 return
327
328QAPATHTEST[unsafe-references-in-binaries] = "package_qa_check_unsafe_references_in_binaries"
329def package_qa_check_unsafe_references_in_binaries(path, name, d, elf, messages):
330 """
331 Ensure binaries in base_[bindir|sbindir|libdir] do not link to files under exec_prefix
332 """
333 if unsafe_references_skippable(path, name, d):
334 return
335
336 if elf:
337 import subprocess as sub
338 pn = d.getVar('PN', True)
339
340 exec_prefix = d.getVar('exec_prefix', True)
341 sysroot_path = d.getVar('STAGING_DIR_TARGET', True)
342 sysroot_path_usr = sysroot_path + exec_prefix
343
344 try:
345 ldd_output = bb.process.Popen(["prelink-rtld", "--root", sysroot_path, path], stdout=sub.PIPE).stdout.read()
346 except bb.process.CmdError:
347 error_msg = pn + ": prelink-rtld aborted when processing %s" % path
348 package_qa_handle_error("unsafe-references-in-binaries", error_msg, d)
349 return False
350
351 if sysroot_path_usr in ldd_output:
352 ldd_output = ldd_output.replace(sysroot_path, "")
353
354 pkgdest = d.getVar('PKGDEST', True)
355 packages = d.getVar('PACKAGES', True)
356
357 for package in packages.split():
358 short_path = path.replace('%s/%s' % (pkgdest, package), "", 1)
359 if (short_path != path):
360 break
361
362 base_err = pn + ": %s, installed in the base_prefix, requires a shared library under exec_prefix (%s)" % (short_path, exec_prefix)
363 for line in ldd_output.split('\n'):
364 if exec_prefix in line:
365 error_msg = "%s: %s" % (base_err, line.strip())
366 package_qa_handle_error("unsafe-references-in-binaries", error_msg, d)
367
368 return False
369
370QAPATHTEST[unsafe-references-in-scripts] = "package_qa_check_unsafe_references_in_scripts"
371def package_qa_check_unsafe_references_in_scripts(path, name, d, elf, messages):
372 """
373 Warn if scripts in base_[bindir|sbindir|libdir] reference files under exec_prefix
374 """
375 if unsafe_references_skippable(path, name, d):
376 return
377
378 if not elf:
379 import stat
380 import subprocess
381 pn = d.getVar('PN', True)
382
383 # Ensure we're checking an executable script
384 statinfo = os.stat(path)
385 if bool(statinfo.st_mode & stat.S_IXUSR):
386 # grep shell scripts for possible references to /exec_prefix/
387 exec_prefix = d.getVar('exec_prefix', True)
388 statement = "grep -e '%s/' %s > /dev/null" % (exec_prefix, path)
389 if subprocess.call(statement, shell=True) == 0:
390 error_msg = pn + ": Found a reference to %s/ in %s" % (exec_prefix, path)
391 package_qa_handle_error("unsafe-references-in-scripts", error_msg, d)
392 error_msg = "Shell scripts in base_bindir and base_sbindir should not reference anything in exec_prefix"
393 package_qa_handle_error("unsafe-references-in-scripts", error_msg, d)
394
395def unsafe_references_skippable(path, name, d):
396 if bb.data.inherits_class('native', d) or bb.data.inherits_class('nativesdk', d):
397 return True
398
399 if "-dbg" in name or "-dev" in name:
400 return True
401
402 # Other package names to skip:
403 if name.startswith("kernel-module-"):
404 return True
405
406 # Skip symlinks
407 if os.path.islink(path):
408 return True
409
410 # Skip unusual rootfs layouts which make these tests irrelevant
411 exec_prefix = d.getVar('exec_prefix', True)
412 if exec_prefix == "":
413 return True
414
415 pkgdest = d.getVar('PKGDEST', True)
416 pkgdest = pkgdest + "/" + name
417 pkgdest = os.path.abspath(pkgdest)
418 base_bindir = pkgdest + d.getVar('base_bindir', True)
419 base_sbindir = pkgdest + d.getVar('base_sbindir', True)
420 base_libdir = pkgdest + d.getVar('base_libdir', True)
421 bindir = pkgdest + d.getVar('bindir', True)
422 sbindir = pkgdest + d.getVar('sbindir', True)
423 libdir = pkgdest + d.getVar('libdir', True)
424
425 if base_bindir == bindir and base_sbindir == sbindir and base_libdir == libdir:
426 return True
427
428 # Skip files not in base_[bindir|sbindir|libdir]
429 path = os.path.abspath(path)
430 if not (base_bindir in path or base_sbindir in path or base_libdir in path):
431 return True
432
433 return False
434
435QAPATHTEST[arch] = "package_qa_check_arch"
436def package_qa_check_arch(path,name,d, elf, messages):
437 """
438 Check if archs are compatible
439 """
440 if not elf:
441 return
442
443 target_os = d.getVar('TARGET_OS', True)
444 target_arch = d.getVar('TARGET_ARCH', True)
445 provides = d.getVar('PROVIDES', True)
446 bpn = d.getVar('BPN', True)
447
448 # FIXME: Cross package confuse this check, so just skip them
449 for s in ['cross', 'nativesdk', 'cross-canadian']:
450 if bb.data.inherits_class(s, d):
451 return
452
453 # avoid following links to /usr/bin (e.g. on udev builds)
454 # we will check the files pointed to anyway...
455 if os.path.islink(path):
456 return
457
458 #if this will throw an exception, then fix the dict above
459 (machine, osabi, abiversion, littleendian, bits) \
460 = package_qa_get_machine_dict()[target_os][target_arch]
461
462 # Check the architecture and endiannes of the binary
463 if not ((machine == elf.machine()) or \
464 ((("virtual/kernel" in provides) or bb.data.inherits_class("module", d) ) and (target_os == "linux-gnux32" or target_os == "linux-gnun32"))):
465 messages["arch"] = "Architecture did not match (%d to %d) on %s" % \
466 (machine, elf.machine(), package_qa_clean_path(path,d))
467 elif not ((bits == elf.abiSize()) or \
468 ((("virtual/kernel" in provides) or bb.data.inherits_class("module", d) ) and (target_os == "linux-gnux32" or target_os == "linux-gnun32"))):
469 messages["arch"] = "Bit size did not match (%d to %d) %s on %s" % \
470 (bits, elf.abiSize(), bpn, package_qa_clean_path(path,d))
471 elif not littleendian == elf.isLittleEndian():
472 messages["arch"] = "Endiannes did not match (%d to %d) on %s" % \
473 (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))
474
475QAPATHTEST[desktop] = "package_qa_check_desktop"
476def package_qa_check_desktop(path, name, d, elf, messages):
477 """
478 Run all desktop files through desktop-file-validate.
479 """
480 if path.endswith(".desktop"):
481 desktop_file_validate = os.path.join(d.getVar('STAGING_BINDIR_NATIVE',True),'desktop-file-validate')
482 output = os.popen("%s %s" % (desktop_file_validate, path))
483 # This only produces output on errors
484 for l in output:
485 messages["desktop"] = "Desktop file issue: " + l.strip()
486
487QAPATHTEST[textrel] = "package_qa_textrel"
488def package_qa_textrel(path, name, d, elf, messages):
489 """
490 Check if the binary contains relocations in .text
491 """
492
493 if not elf:
494 return
495
496 if os.path.islink(path):
497 return
498
499 phdrs = elf.run_objdump("-p", d)
500 sane = True
501
502 import re
503 textrel_re = re.compile("\s+TEXTREL\s+")
504 for line in phdrs.split("\n"):
505 if textrel_re.match(line):
506 sane = False
507
508 if not sane:
509 messages["textrel"] = "ELF binary '%s' has relocations in .text" % path
510
511QAPATHTEST[ldflags] = "package_qa_hash_style"
512def package_qa_hash_style(path, name, d, elf, messages):
513 """
514 Check if the binary has the right hash style...
515 """
516
517 if not elf:
518 return
519
520 if os.path.islink(path):
521 return
522
523 gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS', True)
524 if not gnu_hash:
525 gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS', True)
526 if not gnu_hash:
527 return
528
529 sane = False
530 has_syms = False
531
532 phdrs = elf.run_objdump("-p", d)
533
534 # If this binary has symbols, we expect it to have GNU_HASH too.
535 for line in phdrs.split("\n"):
536 if "SYMTAB" in line:
537 has_syms = True
538 if "GNU_HASH" in line:
539 sane = True
540 if "[mips32]" in line or "[mips64]" in line:
541 sane = True
542
543 if has_syms and not sane:
544 messages["ldflags"] = "No GNU_HASH in the elf binary: '%s'" % path
545
546
547QAPATHTEST[buildpaths] = "package_qa_check_buildpaths"
548def package_qa_check_buildpaths(path, name, d, elf, messages):
549 """
550 Check for build paths inside target files and error if not found in the whitelist
551 """
552 # Ignore .debug files, not interesting
553 if path.find(".debug") != -1:
554 return
555
556 # Ignore symlinks
557 if os.path.islink(path):
558 return
559
560 tmpdir = d.getVar('TMPDIR', True)
561 with open(path) as f:
562 file_content = f.read()
563 if tmpdir in file_content:
564 messages["buildpaths"] = "File %s in package contained reference to tmpdir" % package_qa_clean_path(path,d)
565
566
567QAPATHTEST[xorg-driver-abi] = "package_qa_check_xorg_driver_abi"
568def package_qa_check_xorg_driver_abi(path, name, d, elf, messages):
569 """
570 Check that all packages containing Xorg drivers have ABI dependencies
571 """
572
573 # Skip dev, dbg or nativesdk packages
574 if name.endswith("-dev") or name.endswith("-dbg") or name.startswith("nativesdk-"):
575 return
576
577 driverdir = d.expand("${libdir}/xorg/modules/drivers/")
578 if driverdir in path and path.endswith(".so"):
579 mlprefix = d.getVar('MLPREFIX', True) or ''
580 for rdep in bb.utils.explode_deps(d.getVar('RDEPENDS_' + name, True) or ""):
581 if rdep.startswith("%sxorg-abi-" % mlprefix):
582 return
583 messages["xorg-driver-abi"] = "Package %s contains Xorg driver (%s) but no xorg-abi- dependencies" % (name, os.path.basename(path))
584
585QAPATHTEST[infodir] = "package_qa_check_infodir"
586def package_qa_check_infodir(path, name, d, elf, messages):
587 """
588 Check that /usr/share/info/dir isn't shipped in a particular package
589 """
590 infodir = d.expand("${infodir}/dir")
591
592 if infodir in path:
593 messages["infodir"] = "The /usr/share/info/dir file is not meant to be shipped in a particular package."
594
595QAPATHTEST[symlink-to-sysroot] = "package_qa_check_symlink_to_sysroot"
596def package_qa_check_symlink_to_sysroot(path, name, d, elf, messages):
597 """
598 Check that the package doesn't contain any absolute symlinks to the sysroot.
599 """
600 if os.path.islink(path):
601 target = os.readlink(path)
602 if os.path.isabs(target):
603 tmpdir = d.getVar('TMPDIR', True)
604 if target.startswith(tmpdir):
605 trimmed = path.replace(os.path.join (d.getVar("PKGDEST", True), name), "")
606 messages["symlink-to-sysroot"] = "Symlink %s in %s points to TMPDIR" % (trimmed, name)
607
608def package_qa_check_license(workdir, d):
609 """
610 Check for changes in the license files
611 """
612 import tempfile
613 sane = True
614
615 lic_files = d.getVar('LIC_FILES_CHKSUM', True)
616 lic = d.getVar('LICENSE', True)
617 pn = d.getVar('PN', True)
618
619 if lic == "CLOSED":
620 return True
621
622 if not lic_files:
623 bb.error(pn + ": Recipe file does not have license file information (LIC_FILES_CHKSUM)")
624 return False
625
626 srcdir = d.getVar('S', True)
627
628 for url in lic_files.split():
629 try:
630 (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(url)
631 except bb.fetch.MalformedUrl:
632 raise bb.build.FuncFailed( pn + ": LIC_FILES_CHKSUM contains an invalid URL: " + url)
633 srclicfile = os.path.join(srcdir, path)
634 if not os.path.isfile(srclicfile):
635 raise bb.build.FuncFailed( pn + ": LIC_FILES_CHKSUM points to an invalid file: " + srclicfile)
636
637 recipemd5 = parm.get('md5', '')
638 beginline, endline = 0, 0
639 if 'beginline' in parm:
640 beginline = int(parm['beginline'])
641 if 'endline' in parm:
642 endline = int(parm['endline'])
643
644 if (not beginline) and (not endline):
645 md5chksum = bb.utils.md5_file(srclicfile)
646 else:
647 fi = open(srclicfile, 'rb')
648 fo = tempfile.NamedTemporaryFile(mode='wb', prefix='poky.', suffix='.tmp', delete=False)
649 tmplicfile = fo.name;
650 lineno = 0
651 linesout = 0
652 for line in fi:
653 lineno += 1
654 if (lineno >= beginline):
655 if ((lineno <= endline) or not endline):
656 fo.write(line)
657 linesout += 1
658 else:
659 break
660 fo.flush()
661 fo.close()
662 fi.close()
663 md5chksum = bb.utils.md5_file(tmplicfile)
664 os.unlink(tmplicfile)
665
666 if recipemd5 == md5chksum:
667 bb.note (pn + ": md5 checksum matched for ", url)
668 else:
669 if recipemd5:
670 bb.error(pn + ": md5 data is not matching for ", url)
671 bb.error(pn + ": The new md5 checksum is ", md5chksum)
672 if beginline:
673 if endline:
674 srcfiledesc = "%s (lines %d through to %d)" % (srclicfile, beginline, endline)
675 else:
676 srcfiledesc = "%s (beginning on line %d)" % (srclicfile, beginline)
677 elif endline:
678 srcfiledesc = "%s (ending on line %d)" % (srclicfile, endline)
679 else:
680 srcfiledesc = srclicfile
681 bb.error(pn + ": Check if the license information has changed in %s to verify that the LICENSE value \"%s\" remains valid" % (srcfiledesc, lic))
682 else:
683 bb.error(pn + ": md5 checksum is not specified for ", url)
684 bb.error(pn + ": The md5 checksum is ", md5chksum)
685 sane = False
686
687 return sane
688
689def package_qa_check_staged(path,d):
690 """
691 Check staged la and pc files for sanity
692 -e.g. installed being false
693
694 As this is run after every stage we should be able
695 to find the one responsible for the errors easily even
696 if we look at every .pc and .la file
697 """
698
699 sane = True
700 tmpdir = d.getVar('TMPDIR', True)
701 workdir = os.path.join(tmpdir, "work")
702
703 installed = "installed=yes"
704 if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d):
705 pkgconfigcheck = workdir
706 else:
707 pkgconfigcheck = tmpdir
708
709 # find all .la and .pc files
710 # read the content
711 # and check for stuff that looks wrong
712 for root, dirs, files in os.walk(path):
713 for file in files:
714 path = os.path.join(root,file)
715 if file.endswith(".la"):
716 with open(path) as f:
717 file_content = f.read()
718 if workdir in file_content:
719 error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
720 sane = package_qa_handle_error("la", error_msg, d)
721 elif file.endswith(".pc"):
722 with open(path) as f:
723 file_content = f.read()
724 if pkgconfigcheck in file_content:
725 error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
726 sane = package_qa_handle_error("pkgconfig", error_msg, d)
727
728 return sane
729
730# Walk over all files in a directory and call func
731def package_qa_walk(path, warnfuncs, errorfuncs, skip, package, d):
732 import oe.qa
733
734 #if this will throw an exception, then fix the dict above
735 target_os = d.getVar('TARGET_OS', True)
736 target_arch = d.getVar('TARGET_ARCH', True)
737
738 warnings = {}
739 errors = {}
740 for path in pkgfiles[package]:
741 elf = oe.qa.ELFFile(path)
742 try:
743 elf.open()
744 except:
745 elf = None
746 for func in warnfuncs:
747 func(path, package, d, elf, warnings)
748 for func in errorfuncs:
749 func(path, package, d, elf, errors)
750
751 for w in warnings:
752 package_qa_handle_error(w, warnings[w], d)
753 for e in errors:
754 package_qa_handle_error(e, errors[e], d)
755
756 return len(errors) == 0
757
758def package_qa_check_rdepends(pkg, pkgdest, skip, taskdeps, packages, d):
759 # Don't do this check for kernel/module recipes, there aren't too many debug/development
760 # packages and you can get false positives e.g. on kernel-module-lirc-dev
761 if bb.data.inherits_class("kernel", d) or bb.data.inherits_class("module-base", d):
762 return True
763
764 sane = True
765 if not "-dbg" in pkg and not "packagegroup-" in pkg and not "-image" in pkg:
766 localdata = bb.data.createCopy(d)
767 localdata.setVar('OVERRIDES', pkg)
768 bb.data.update_data(localdata)
769
770 # Now check the RDEPENDS
771 rdepends = bb.utils.explode_deps(localdata.getVar('RDEPENDS', True) or "")
772
773 # Now do the sanity check!!!
774 for rdepend in rdepends:
775 if "-dbg" in rdepend and "debug-deps" not in skip:
776 error_msg = "%s rdepends on %s" % (pkg,rdepend)
777 sane = package_qa_handle_error("debug-deps", error_msg, d)
778 if (not "-dev" in pkg and not "-staticdev" in pkg) and rdepend.endswith("-dev") and "dev-deps" not in skip:
779 error_msg = "%s rdepends on %s" % (pkg, rdepend)
780 sane = package_qa_handle_error("dev-deps", error_msg, d)
781 if rdepend not in packages:
782 rdep_data = oe.packagedata.read_subpkgdata(rdepend, d)
783 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps:
784 continue
785 if not rdep_data or not 'PN' in rdep_data:
786 pkgdata_dir = d.getVar("PKGDATA_DIR", True)
787 try:
788 possibles = os.listdir("%s/runtime-rprovides/%s/" % (pkgdata_dir, rdepend))
789 except OSError:
790 possibles = []
791 for p in possibles:
792 rdep_data = oe.packagedata.read_subpkgdata(p, d)
793 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps:
794 break
795 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps:
796 continue
797 error_msg = "%s rdepends on %s, but it isn't a build dependency?" % (pkg, rdepend)
798 sane = package_qa_handle_error("build-deps", error_msg, d)
799
800 if "file-rdeps" not in skip:
801 ignored_file_rdeps = set(['/bin/sh', '/usr/bin/env', 'rtld(GNU_HASH)'])
802 if bb.data.inherits_class('nativesdk', d):
803 ignored_file_rdeps |= set(['/bin/bash', '/usr/bin/perl'])
804 # For Saving the FILERDEPENDS
805 filerdepends = set()
806 rdep_data = oe.packagedata.read_subpkgdata(pkg, d)
807 for key in rdep_data:
808 if key.startswith("FILERDEPENDS_"):
809 for subkey in rdep_data[key].split():
810 filerdepends.add(subkey)
811 filerdepends -= ignored_file_rdeps
812
813 if filerdepends:
814 next = rdepends
815 done = rdepends[:]
816 # Find all the rdepends on the dependency chain
817 while next:
818 new = []
819 for rdep in next:
820 rdep_data = oe.packagedata.read_subpkgdata(rdep, d)
821 sub_rdeps = rdep_data.get("RDEPENDS_" + rdep)
822 if not sub_rdeps:
823 continue
824 for sub_rdep in sub_rdeps.split():
825 if sub_rdep in done:
826 continue
827 if not sub_rdep.startswith('(') and \
828 oe.packagedata.has_subpkgdata(sub_rdep, d):
829 # It's a new rdep
830 done.append(sub_rdep)
831 new.append(sub_rdep)
832 next = new
833
834 # Add the rprovides of itself
835 if pkg not in done:
836 done.insert(0, pkg)
837
838 # The python is not a package, but python-core provides it, so
839 # skip checking /usr/bin/python if python is in the rdeps, in
840 # case there is a RDEPENDS_pkg = "python" in the recipe.
841 for py in [ d.getVar('MLPREFIX', True) + "python", "python" ]:
842 if py in done:
843 filerdepends.discard("/usr/bin/python")
844 done.remove(py)
845 for rdep in done:
846 # For Saving the FILERPROVIDES, RPROVIDES and FILES_INFO
847 rdep_rprovides = set()
848 rdep_data = oe.packagedata.read_subpkgdata(rdep, d)
849 for key in rdep_data:
850 if key.startswith("FILERPROVIDES_") or key.startswith("RPROVIDES_"):
851 for subkey in rdep_data[key].split():
852 rdep_rprovides.add(subkey)
853 # Add the files list to the rprovides
854 if key == "FILES_INFO":
855 # Use eval() to make it as a dict
856 for subkey in eval(rdep_data[key]):
857 rdep_rprovides.add(subkey)
858 filerdepends -= rdep_rprovides
859 if not filerdepends:
860 # Break if all the file rdepends are met
861 break
862 else:
863 # Clear it for the next loop
864 rdep_rprovides.clear()
865 if filerdepends:
866 error_msg = "%s requires %s, but no providers in its RDEPENDS" % \
867 (pkg, ', '.join(str(e) for e in filerdepends))
868 sane = package_qa_handle_error("file-rdeps", error_msg, d)
869
870 return sane
871
872def package_qa_check_deps(pkg, pkgdest, skip, d):
873 sane = True
874
875 localdata = bb.data.createCopy(d)
876 localdata.setVar('OVERRIDES', pkg)
877 bb.data.update_data(localdata)
878
879 def check_valid_deps(var):
880 sane = True
881 try:
882 rvar = bb.utils.explode_dep_versions2(localdata.getVar(var, True) or "")
883 except ValueError as e:
884 bb.fatal("%s_%s: %s" % (var, pkg, e))
885 for dep in rvar:
886 for v in rvar[dep]:
887 if v and not v.startswith(('< ', '= ', '> ', '<= ', '>=')):
888 error_msg = "%s_%s is invalid: %s (%s) only comparisons <, =, >, <=, and >= are allowed" % (var, pkg, dep, v)
889 sane = package_qa_handle_error("dep-cmp", error_msg, d)
890 return sane
891
892 sane = True
893 if not check_valid_deps('RDEPENDS'):
894 sane = False
895 if not check_valid_deps('RRECOMMENDS'):
896 sane = False
897 if not check_valid_deps('RSUGGESTS'):
898 sane = False
899 if not check_valid_deps('RPROVIDES'):
900 sane = False
901 if not check_valid_deps('RREPLACES'):
902 sane = False
903 if not check_valid_deps('RCONFLICTS'):
904 sane = False
905
906 return sane
907
908# The PACKAGE FUNC to scan each package
909python do_package_qa () {
910 import subprocess
911 import oe.packagedata
912
913 bb.note("DO PACKAGE QA")
914
915 bb.build.exec_func("read_subpackage_metadata", d)
916
917 logdir = d.getVar('T', True)
918 pkg = d.getVar('PN', True)
919
920 # Check the compile log for host contamination
921 compilelog = os.path.join(logdir,"log.do_compile")
922
923 if os.path.exists(compilelog):
924 statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % compilelog
925 if subprocess.call(statement, shell=True) == 0:
926 msg = "%s: The compile log indicates that host include and/or library paths were used.\n \
927 Please check the log '%s' for more information." % (pkg, compilelog)
928 package_qa_handle_error("compile-host-path", msg, d)
929
930 # Check the install log for host contamination
931 installlog = os.path.join(logdir,"log.do_install")
932
933 if os.path.exists(installlog):
934 statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % installlog
935 if subprocess.call(statement, shell=True) == 0:
936 msg = "%s: The install log indicates that host include and/or library paths were used.\n \
937 Please check the log '%s' for more information." % (pkg, installlog)
938 package_qa_handle_error("install-host-path", msg, d)
939
940 # Scan the packages...
941 pkgdest = d.getVar('PKGDEST', True)
942 packages = d.getVar('PACKAGES', True)
943
944 cpath = oe.cachedpath.CachedPath()
945 global pkgfiles
946 pkgfiles = {}
947 for pkg in (packages or "").split():
948 pkgfiles[pkg] = []
949 for walkroot, dirs, files in cpath.walk(pkgdest + "/" + pkg):
950 for file in files:
951 pkgfiles[pkg].append(walkroot + os.sep + file)
952
953 # no packages should be scanned
954 if not packages:
955 return
956
957 testmatrix = d.getVarFlags("QAPATHTEST")
958 import re
959 # The package name matches the [a-z0-9.+-]+ regular expression
960 pkgname_pattern = re.compile("^[a-z0-9.+-]+$")
961
962 taskdepdata = d.getVar("BB_TASKDEPDATA", False)
963 taskdeps = set()
964 for dep in taskdepdata:
965 taskdeps.add(taskdepdata[dep][0])
966
967 g = globals()
968 walk_sane = True
969 rdepends_sane = True
970 deps_sane = True
971 for package in packages.split():
972 skip = (d.getVar('INSANE_SKIP_' + package, True) or "").split()
973 if skip:
974 bb.note("Package %s skipping QA tests: %s" % (package, str(skip)))
975 warnchecks = []
976 for w in (d.getVar("WARN_QA", True) or "").split():
977 if w in skip:
978 continue
979 if w in testmatrix and testmatrix[w] in g:
980 warnchecks.append(g[testmatrix[w]])
981 errorchecks = []
982 for e in (d.getVar("ERROR_QA", True) or "").split():
983 if e in skip:
984 continue
985 if e in testmatrix and testmatrix[e] in g:
986 errorchecks.append(g[testmatrix[e]])
987
988 bb.note("Checking Package: %s" % package)
989 # Check package name
990 if not pkgname_pattern.match(package):
991 package_qa_handle_error("pkgname",
992 "%s doesn't match the [a-z0-9.+-]+ regex\n" % package, d)
993
994 path = "%s/%s" % (pkgdest, package)
995 if not package_qa_walk(path, warnchecks, errorchecks, skip, package, d):
996 walk_sane = False
997 if not package_qa_check_rdepends(package, pkgdest, skip, taskdeps, packages, d):
998 rdepends_sane = False
999 if not package_qa_check_deps(package, pkgdest, skip, d):
1000 deps_sane = False
1001
1002
1003 if 'libdir' in d.getVar("ALL_QA", True).split():
1004 package_qa_check_libdir(d)
1005
1006 qa_sane = d.getVar("QA_SANE", True)
1007 if not walk_sane or not rdepends_sane or not deps_sane or not qa_sane:
1008 bb.fatal("QA run found fatal errors. Please consider fixing them.")
1009 bb.note("DONE with PACKAGE QA")
1010}
1011
1012do_package_qa[rdeptask] = "do_packagedata"
1013addtask do_package_qa after do_packagedata do_package before do_build
1014
1015SSTATETASKS += "do_package_qa"
1016do_package_qa[sstate-inputdirs] = ""
1017do_package_qa[sstate-outputdirs] = ""
1018python do_package_qa_setscene () {
1019 sstate_setscene(d)
1020}
1021addtask do_package_qa_setscene
1022
1023python do_qa_staging() {
1024 bb.note("QA checking staging")
1025
1026 if not package_qa_check_staged(d.expand('${SYSROOT_DESTDIR}${STAGING_LIBDIR}'), d):
1027 bb.fatal("QA staging was broken by the package built above")
1028}
1029
1030python do_qa_configure() {
1031 import subprocess
1032
1033 ###########################################################################
1034 # Check config.log for cross compile issues
1035 ###########################################################################
1036
1037 configs = []
1038 workdir = d.getVar('WORKDIR', True)
1039 bb.note("Checking autotools environment for common misconfiguration")
1040 for root, dirs, files in os.walk(workdir):
1041 statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % \
1042 os.path.join(root,"config.log")
1043 if "config.log" in files:
1044 if subprocess.call(statement, shell=True) == 0:
1045 bb.fatal("""This autoconf log indicates errors, it looked at host include and/or library paths while determining system capabilities.
1046Rerun configure task after fixing this. The path was '%s'""" % root)
1047
1048 if "configure.ac" in files:
1049 configs.append(os.path.join(root,"configure.ac"))
1050 if "configure.in" in files:
1051 configs.append(os.path.join(root, "configure.in"))
1052
1053 ###########################################################################
1054 # Check gettext configuration and dependencies are correct
1055 ###########################################################################
1056
1057 cnf = d.getVar('EXTRA_OECONF', True) or ""
1058 if "gettext" not in d.getVar('P', True) and "gcc-runtime" not in d.getVar('P', True) and "--disable-nls" not in cnf:
1059 ml = d.getVar("MLPREFIX", True) or ""
1060 if bb.data.inherits_class('native', d) or bb.data.inherits_class('cross', d) or bb.data.inherits_class('crosssdk', d) or bb.data.inherits_class('nativesdk', d):
1061 gt = "gettext-native"
1062 elif bb.data.inherits_class('cross-canadian', d):
1063 gt = "nativesdk-gettext"
1064 else:
1065 gt = "virtual/" + ml + "gettext"
1066 deps = bb.utils.explode_deps(d.getVar('DEPENDS', True) or "")
1067 if gt not in deps:
1068 for config in configs:
1069 gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config
1070 if subprocess.call(gnu, shell=True) == 0:
1071 bb.fatal("""%s required but not in DEPENDS for file %s.
1072Missing inherit gettext?""" % (gt, config))
1073
1074 ###########################################################################
1075 # Check license variables
1076 ###########################################################################
1077
1078 if not package_qa_check_license(workdir, d):
1079 bb.fatal("Licensing Error: LIC_FILES_CHKSUM does not match, please fix")
1080
1081 ###########################################################################
1082 # Check unrecognised configure options (with a white list)
1083 ###########################################################################
1084 if bb.data.inherits_class("autotools", d):
1085 bb.note("Checking configure output for unrecognised options")
1086 try:
1087 flag = "WARNING: unrecognized options:"
1088 log = os.path.join(d.getVar('B', True), 'config.log')
1089 output = subprocess.check_output(['grep', '-F', flag, log]).replace(', ', ' ')
1090 options = set()
1091 for line in output.splitlines():
1092 options |= set(line.partition(flag)[2].split())
1093 whitelist = set(d.getVar("UNKNOWN_CONFIGURE_WHITELIST", True).split())
1094 options -= whitelist
1095 if options:
1096 pn = d.getVar('PN', True)
1097 error_msg = pn + ": configure was passed unrecognised options: " + " ".join(options)
1098 package_qa_handle_error("unknown-configure-option", error_msg, d)
1099 except subprocess.CalledProcessError:
1100 pass
1101}
1102# The Staging Func, to check all staging
1103#addtask qa_staging after do_populate_sysroot before do_build
1104do_populate_sysroot[postfuncs] += "do_qa_staging "
1105
1106# Check broken config.log files, for packages requiring Gettext which don't
1107# have it in DEPENDS and for correct LIC_FILES_CHKSUM
1108#addtask qa_configure after do_configure before do_compile
1109do_configure[postfuncs] += "do_qa_configure "
1110
1111python () {
1112 tests = d.getVar('ALL_QA', True).split()
1113 if "desktop" in tests:
1114 d.appendVar("PACKAGE_DEPENDS", "desktop-file-utils-native")
1115
1116 ###########################################################################
1117 # Check various variables
1118 ###########################################################################
1119
1120 # Checking ${FILESEXTRAPATHS}
1121 extrapaths = (d.getVar("FILESEXTRAPATHS", True) or "")
1122 if '__default' not in extrapaths.split(":"):
1123 msg = "FILESEXTRAPATHS-variable, must always use _prepend (or _append)\n"
1124 msg += "type of assignment, and don't forget the colon.\n"
1125 msg += "Please assign it with the format of:\n"
1126 msg += " FILESEXTRAPATHS_append := \":${THISDIR}/Your_Files_Path\" or\n"
1127 msg += " FILESEXTRAPATHS_prepend := \"${THISDIR}/Your_Files_Path:\"\n"
1128 msg += "in your bbappend file\n\n"
1129 msg += "Your incorrect assignment is:\n"
1130 msg += "%s\n" % extrapaths
1131 bb.warn(msg)
1132
1133 if d.getVar('do_stage', True) is not None:
1134 bb.fatal("Legacy staging found for %s as it has a do_stage function. This will need conversion to a do_install or often simply removal to work with OE-core" % d.getVar("FILE", True))
1135
1136 overrides = d.getVar('OVERRIDES', True).split(':')
1137 pn = d.getVar('PN', True)
1138 if pn in overrides:
1139 msg = 'Recipe %s has PN of "%s" which is in OVERRIDES, this can result in unexpected behaviour.' % (d.getVar("FILE", True), pn)
1140 package_qa_handle_error("pn-overrides", msg, d)
1141
1142 issues = []
1143 if (d.getVar('PACKAGES', True) or "").split():
1144 for dep in (d.getVar('QADEPENDS', True) or "").split():
1145 d.appendVarFlag('do_package_qa', 'depends', " %s:do_populate_sysroot" % dep)
1146 for var in 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RCONFLICTS', 'RPROVIDES', 'RREPLACES', 'FILES', 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', 'ALLOW_EMPTY':
1147 if d.getVar(var):
1148 issues.append(var)
1149 else:
1150 d.setVarFlag('do_package_qa', 'rdeptask', '')
1151 for i in issues:
1152 package_qa_handle_error("pkgvarcheck", "%s: Variable %s is set as not being package specific, please fix this." % (d.getVar("FILE", True), i), d)
1153}