diff options
Diffstat (limited to 'meta/classes/insane.bbclass')
| -rw-r--r-- | meta/classes/insane.bbclass | 1235 |
1 files changed, 991 insertions, 244 deletions
diff --git a/meta/classes/insane.bbclass b/meta/classes/insane.bbclass index a660a06976..dc1c2f8d41 100644 --- a/meta/classes/insane.bbclass +++ b/meta/classes/insane.bbclass @@ -11,17 +11,42 @@ # -Check if packages contains .debug directories or .so files # where they should be in -dev or -dbg # -Check if config.log contains traces to broken autoconf tests - - -# -# We need to have the scanelf utility as soon as -# possible and this is contained within the pax-utils-native. -# The package.bbclass can help us here. -# -inherit package -PACKAGE_DEPENDS += "pax-utils-native desktop-file-utils-native" -PACKAGEFUNCS += " do_package_qa " - +# -Check invalid characters (non-utf8) on some package metadata +# -Ensure that binaries in base_[bindir|sbindir|libdir] do not link +# into exec_prefix +# -Check that scripts in base_[bindir|sbindir|libdir] do not reference +# files under exec_prefix + + +# unsafe-references-in-binaries requires prelink-rtld from +# prelink-native, but we don't want this DEPENDS for -native builds +QADEPENDS = "prelink-native" +QADEPENDS_class-native = "" +QADEPENDS_class-nativesdk = "" +QA_SANE = "True" + +# Elect whether a given type of error is a warning or error, they may +# have been set by other files. +WARN_QA ?= "ldflags useless-rpaths rpaths staticdev libdir xorg-driver-abi \ + textrel already-stripped incompatible-license files-invalid \ + installed-vs-shipped compile-host-path install-host-path \ + pn-overrides infodir build-deps \ + unknown-configure-option symlink-to-sysroot multilib \ + invalid-packageconfig host-user-contaminated \ + " +ERROR_QA ?= "dev-so debug-deps dev-deps debug-files arch pkgconfig la \ + perms dep-cmp pkgvarcheck perm-config perm-line perm-link \ + split-strip packages-list pkgv-undefined var-undefined \ + version-going-backwards expanded-d invalid-chars \ + license-checksum dev-elf file-rdeps \ + " +FAKEROOT_QA = "host-user-contaminated" +FAKEROOT_QA[doc] = "QA tests which need to run under fakeroot. If any \ +enabled tests are listed here, the do_package_qa task will run under fakeroot." + +ALL_QA = "${WARN_QA} ${ERROR_QA}" + +UNKNOWN_CONFIGURE_WHITELIST ?= "--enable-nls --disable-nls --disable-silent-rules --disable-dependency-tracking --with-libtool-sysroot --disable-static" # # dictionary for elf headers @@ -29,12 +54,26 @@ PACKAGEFUNCS += " do_package_qa " # feel free to add and correct. # # TARGET_OS TARGET_ARCH MACHINE, OSABI, ABIVERSION, Little Endian, 32bit? -def package_qa_get_machine_dict(): - return { +def package_qa_get_machine_dict(d): + machdata = { "darwin9" : { "arm" : (40, 0, 0, True, 32), }, + "eabi" : { + "arm" : (40, 0, 0, True, 32), + }, + "elf" : { + "aarch64" : (183, 0, 0, True, 64), + "aarch64_be" :(183, 0, 0, False, 64), + "i586" : (3, 0, 0, True, 32), + "x86_64": (62, 0, 0, True, 64), + "epiphany": (4643, 0, 0, True, 32), + "mips": ( 8, 0, 0, False, 32), + "mipsel": ( 8, 0, 0, True, 32), + }, "linux" : { + "aarch64" : (183, 0, 0, True, 64), + "aarch64_be" :(183, 0, 0, False, 64), "arm" : (40, 97, 0, True, 32), "armeb": (40, 97, 0, False, 32), "powerpc": (20, 0, 0, False, 32), @@ -50,9 +89,19 @@ def package_qa_get_machine_dict(): "m68k": ( 4, 0, 0, False, 32), "mips": ( 8, 0, 0, False, 32), "mipsel": ( 8, 0, 0, True, 32), + "mips64": ( 8, 0, 0, False, 64), + "mips64el": ( 8, 0, 0, True, 64), + "mipsisa32r6": ( 8, 0, 0, False, 32), + "mipsisa32r6el": ( 8, 0, 0, True, 32), + "mipsisa64r6": ( 8, 0, 0, False, 64), + "mipsisa64r6el": ( 8, 0, 0, True, 64), + "nios2": (113, 0, 0, True, 32), "s390": (22, 0, 0, False, 32), "sh4": (42, 0, 0, True, 32), "sparc": ( 2, 0, 0, False, 32), + "microblaze": (189, 0, 0, False, 32), + "microblazeeb":(189, 0, 0, False, 32), + "microblazeel":(189, 0, 0, True, 32), }, "linux-uclibc" : { "arm" : ( 40, 97, 0, True, 32), @@ -65,10 +114,32 @@ def package_qa_get_machine_dict(): "x86_64": ( 62, 0, 0, True, 64), "mips": ( 8, 0, 0, False, 32), "mipsel": ( 8, 0, 0, True, 32), + "mips64": ( 8, 0, 0, False, 64), + "mips64el": ( 8, 0, 0, True, 64), "avr32": (6317, 0, 0, False, 32), - "sh4": (42, 0, 0, True, 32), + "sh4": (42, 0, 0, True, 32), }, + "linux-musl" : { + "aarch64" : (183, 0, 0, True, 64), + "aarch64_be" :(183, 0, 0, False, 64), + "arm" : ( 40, 97, 0, True, 32), + "armeb": ( 40, 97, 0, False, 32), + "powerpc": ( 20, 0, 0, False, 32), + "i386": ( 3, 0, 0, True, 32), + "i486": ( 3, 0, 0, True, 32), + "i586": ( 3, 0, 0, True, 32), + "i686": ( 3, 0, 0, True, 32), + "x86_64": ( 62, 0, 0, True, 64), + "mips": ( 8, 0, 0, False, 32), + "mipsel": ( 8, 0, 0, True, 32), + "mips64": ( 8, 0, 0, False, 64), + "mips64el": ( 8, 0, 0, True, 64), + "microblaze": (189, 0, 0, False, 32), + "microblazeeb":(189, 0, 0, False, 32), + "microblazeel":(189, 0, 0, True, 32), + "sh4": ( 42, 0, 0, True, 32), + }, "uclinux-uclibc" : { "bfin": ( 106, 0, 0, True, 32), }, @@ -76,54 +147,97 @@ def package_qa_get_machine_dict(): "arm" : (40, 0, 0, True, 32), "armeb" : (40, 0, 0, False, 32), }, + "linux-musleabi" : { + "arm" : (40, 0, 0, True, 32), + "armeb" : (40, 0, 0, False, 32), + }, "linux-uclibceabi" : { "arm" : (40, 0, 0, True, 32), "armeb" : (40, 0, 0, False, 32), }, - "linux-gnu" : { + "linux-gnuspe" : { "powerpc": (20, 0, 0, False, 32), }, - "linux-gnuspe" : { + "linux-muslspe" : { "powerpc": (20, 0, 0, False, 32), }, "linux-uclibcspe" : { "powerpc": (20, 0, 0, False, 32), }, "linux-gnu" : { - "microblaze": (47787, 0, 0, False, 32), - "microblazeel": (47787, 0, 0, True, 32), + "powerpc": (20, 0, 0, False, 32), + "sh4": (42, 0, 0, True, 32), }, "linux-gnux32" : { "x86_64": (62, 0, 0, True, 32), }, - } + "linux-gnun32" : { + "mips64": ( 8, 0, 0, False, 32), + "mips64el": ( 8, 0, 0, True, 32), + "mipsisa64r6": ( 8, 0, 0, False, 32), + "mipsisa64r6el":( 8, 0, 0, True, 32), + }, + } + + # Add in any extra user supplied data which may come from a BSP layer, removing the + # need to always change this class directly + extra_machdata = (d.getVar("PACKAGEQA_EXTRA_MACHDEFFUNCS") or "").split() + for m in extra_machdata: + call = m + "(machdata, d)" + locs = { "machdata" : machdata, "d" : d} + machdata = bb.utils.better_eval(call, locs) + return machdata -WARN_QA ?= "dev-so rpaths debug-deps dev-deps debug-files arch la2 pkgconfig desktop la ldflags perms useless-rpaths" -ERROR_QA ?= "" -#ERROR_QA ?= "rpaths debug-deps dev-deps debug-files arch pkgconfig perms" -def package_qa_clean_path(path,d): - """ Remove the common prefix from the path. In this case it is the TMPDIR""" - return path.replace(bb.data.getVar('TMPDIR',d,True),"") +def package_qa_clean_path(path, d, pkg=None): + """ + Remove redundant paths from the path for display. If pkg isn't set then + TMPDIR is stripped, otherwise PKGDEST/pkg is stripped. + """ + if pkg: + path = path.replace(os.path.join(d.getVar("PKGDEST"), pkg), "/") + return path.replace(d.getVar("TMPDIR"), "/").replace("//", "/") -def package_qa_write_error(error, d): - logfile = d.getVar('QA_LOGFILE', True) +def package_qa_write_error(type, error, d): + logfile = d.getVar('QA_LOGFILE') if logfile: - p = d.getVar('P', True) - f = file( logfile, "a+") - print >> f, "%s: %s" % (p, error) - f.close() + p = d.getVar('P') + with open(logfile, "a+") as f: + f.write("%s: %s [%s]\n" % (p, error, type)) def package_qa_handle_error(error_class, error_msg, d): - package_qa_write_error(error_msg, d) - if error_class in (d.getVar("ERROR_QA", True) or "").split(): - bb.error("QA Issue: %s" % error_msg) + package_qa_write_error(error_class, error_msg, d) + if error_class in (d.getVar("ERROR_QA") or "").split(): + bb.error("QA Issue: %s [%s]" % (error_msg, error_class)) + d.setVar("QA_SANE", False) return False + elif error_class in (d.getVar("WARN_QA") or "").split(): + bb.warn("QA Issue: %s [%s]" % (error_msg, error_class)) + else: + bb.note("QA Issue: %s [%s]" % (error_msg, error_class)) + return True + +def package_qa_add_message(messages, section, new_msg): + if section not in messages: + messages[section] = new_msg else: - bb.warn("QA Issue: %s" % error_msg) + messages[section] = messages[section] + "\n" + new_msg + +QAPATHTEST[libexec] = "package_qa_check_libexec" +def package_qa_check_libexec(path,name, d, elf, messages): + + # Skip the case where the default is explicitly /usr/libexec + libexec = d.getVar('libexecdir') + if libexec == "/usr/libexec": return True + if 'libexec' in path.split(os.path.sep): + package_qa_add_message(messages, "libexec", "%s: %s is using libexec please relocate to %s" % (name, package_qa_clean_path(path, d), libexec)) + return False + + return True + QAPATHTEST[rpaths] = "package_qa_check_rpath" def package_qa_check_rpath(file,name, d, elf, messages): """ @@ -132,46 +246,52 @@ def package_qa_check_rpath(file,name, d, elf, messages): if not elf: return - scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf') - bad_dirs = [bb.data.getVar('TMPDIR', d, True) + "/work", bb.data.getVar('STAGING_DIR_TARGET', d, True)] - bad_dir_test = bb.data.getVar('TMPDIR', d, True) - if not os.path.exists(scanelf): - bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found") + if os.path.islink(file): + return - if not bad_dirs[0] in bb.data.getVar('WORKDIR', d, True): - bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check") + bad_dirs = [d.getVar('BASE_WORKDIR'), d.getVar('STAGING_DIR_TARGET')] - output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file)) - txt = output.readline().split() - for line in txt: - for dir in bad_dirs: - if dir in line: - messages.append("package %s contains bad RPATH %s in file %s" % (name, line, file)) + phdrs = elf.run_objdump("-p", d) + + import re + rpath_re = re.compile("\s+RPATH\s+(.*)") + for line in phdrs.split("\n"): + m = rpath_re.match(line) + if m: + rpath = m.group(1) + for dir in bad_dirs: + if dir in rpath: + package_qa_add_message(messages, "rpaths", "package %s contains bad RPATH %s in file %s" % (name, rpath, file)) QAPATHTEST[useless-rpaths] = "package_qa_check_useless_rpaths" def package_qa_check_useless_rpaths(file, name, d, elf, messages): """ Check for RPATHs that are useless but not dangerous """ + def rpath_eq(a, b): + return os.path.normpath(a) == os.path.normpath(b) + if not elf: return - objdump = bb.data.getVar('OBJDUMP', d, True) - env_path = bb.data.getVar('PATH', d, True) + if os.path.islink(file): + return + + libdir = d.getVar("libdir") + base_libdir = d.getVar("base_libdir") - libdir = bb.data.getVar("libdir", d, True) - base_libdir = bb.data.getVar("base_libdir", d, True) + phdrs = elf.run_objdump("-p", d) import re rpath_re = re.compile("\s+RPATH\s+(.*)") - for line in os.popen("LC_ALL=C PATH=%s %s -p '%s' 2> /dev/null" % (env_path, objdump, file), "r"): - m = rpath_re.match(line) - if m: - rpath = m.group(1) - if rpath == libdir or rpath == base_libdir: - # The dynamic linker searches both these places anyway. There is no point in - # looking there again. - messages.append("%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d), rpath)) + for line in phdrs.split("\n"): + m = rpath_re.match(line) + if m: + rpath = m.group(1) + if rpath_eq(rpath, libdir) or rpath_eq(rpath, base_libdir): + # The dynamic linker searches both these places anyway. There is no point in + # looking there again. + package_qa_add_message(messages, "useless-rpaths", "%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d), rpath)) QAPATHTEST[dev-so] = "package_qa_check_dev" def package_qa_check_dev(path, name, d, elf, messages): @@ -179,19 +299,106 @@ def package_qa_check_dev(path, name, d, elf, messages): Check for ".so" library symlinks in non-dev packages """ - if not name.endswith("-dev") and not name.endswith("-dbg") and path.endswith(".so") and os.path.islink(path): - messages.append("non -dev/-dbg package contains symlink .so: %s path '%s'" % \ + 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): + package_qa_add_message(messages, "dev-so", "non -dev/-dbg/nativesdk- package contains symlink .so: %s path '%s'" % \ + (name, package_qa_clean_path(path,d))) + +QAPATHTEST[dev-elf] = "package_qa_check_dev_elf" +def package_qa_check_dev_elf(path, name, d, elf, messages): + """ + Check that -dev doesn't contain real shared libraries. The test has to + check that the file is not a link and is an ELF object as some recipes + install link-time .so files that are linker scripts. + """ + if name.endswith("-dev") and path.endswith(".so") and not os.path.islink(path) and elf: + package_qa_add_message(messages, "dev-elf", "-dev package contains non-symlink .so: %s path '%s'" % \ + (name, package_qa_clean_path(path,d))) + +QAPATHTEST[staticdev] = "package_qa_check_staticdev" +def package_qa_check_staticdev(path, name, d, elf, messages): + """ + Check for ".a" library in non-staticdev packages + There are a number of exceptions to this rule, -pic packages can contain + static libraries, the _nonshared.a belong with their -dev packages and + libgcc.a, libgcov.a will be skipped in their packages + """ + + 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"): + package_qa_add_message(messages, "staticdev", "non -staticdev package contains static .a library: %s path '%s'" % \ (name, package_qa_clean_path(path,d))) +def package_qa_check_libdir(d): + """ + Check for wrong library installation paths. For instance, catch + recipes installing /lib/bar.so when ${base_libdir}="lib32" or + installing in /usr/lib64 when ${libdir}="/usr/lib" + """ + import re + + pkgdest = d.getVar('PKGDEST') + base_libdir = d.getVar("base_libdir") + os.sep + libdir = d.getVar("libdir") + os.sep + libexecdir = d.getVar("libexecdir") + os.sep + exec_prefix = d.getVar("exec_prefix") + os.sep + + messages = [] + + # The re's are purposely fuzzy, as some there are some .so.x.y.z files + # that don't follow the standard naming convention. It checks later + # that they are actual ELF files + lib_re = re.compile("^/lib.+\.so(\..+)?$") + exec_re = re.compile("^%s.*/lib.+\.so(\..+)?$" % exec_prefix) + + for root, dirs, files in os.walk(pkgdest): + if root == pkgdest: + # Skip subdirectories for any packages with libdir in INSANE_SKIP + skippackages = [] + for package in dirs: + if 'libdir' in (d.getVar('INSANE_SKIP_' + package) or "").split(): + bb.note("Package %s skipping libdir QA test" % (package)) + skippackages.append(package) + elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-file-directory' and package.endswith("-dbg"): + bb.note("Package %s skipping libdir QA test for PACKAGE_DEBUG_SPLIT_STYLE equals debug-file-directory" % (package)) + skippackages.append(package) + for package in skippackages: + dirs.remove(package) + for file in files: + full_path = os.path.join(root, file) + rel_path = os.path.relpath(full_path, pkgdest) + if os.sep in rel_path: + package, rel_path = rel_path.split(os.sep, 1) + rel_path = os.sep + rel_path + if lib_re.match(rel_path): + if base_libdir not in rel_path: + # make sure it's an actual ELF file + elf = oe.qa.ELFFile(full_path) + try: + elf.open() + messages.append("%s: found library in wrong location: %s" % (package, rel_path)) + except (oe.qa.NotELFFileError): + pass + if exec_re.match(rel_path): + if libdir not in rel_path and libexecdir not in rel_path: + # make sure it's an actual ELF file + elf = oe.qa.ELFFile(full_path) + try: + elf.open() + messages.append("%s: found library in wrong location: %s" % (package, rel_path)) + except (oe.qa.NotELFFileError): + pass + + if messages: + package_qa_handle_error("libdir", "\n".join(messages), d) + QAPATHTEST[debug-files] = "package_qa_check_dbg" def package_qa_check_dbg(path, name, d, elf, messages): """ Check for ".debug" files or directories outside of the dbg package """ - if not "-dbg" in name: + if not "-dbg" in name and not "-ptest" in name: if '.debug' in path.split(os.path.sep): - messages.append("non debug package contains .debug directory: %s path %s" % \ + package_qa_add_message(messages, "debug-files", "non debug package contains .debug directory: %s path %s" % \ (name, package_qa_clean_path(path,d))) QAPATHTEST[perms] = "package_qa_check_perm" @@ -201,16 +408,90 @@ def package_qa_check_perm(path,name,d, elf, messages): """ return +QAPATHTEST[unsafe-references-in-scripts] = "package_qa_check_unsafe_references_in_scripts" +def package_qa_check_unsafe_references_in_scripts(path, name, d, elf, messages): + """ + Warn if scripts in base_[bindir|sbindir|libdir] reference files under exec_prefix + """ + if unsafe_references_skippable(path, name, d): + return + + if not elf: + import stat + import subprocess + pn = d.getVar('PN') + + # Ensure we're checking an executable script + statinfo = os.stat(path) + if bool(statinfo.st_mode & stat.S_IXUSR): + # grep shell scripts for possible references to /exec_prefix/ + exec_prefix = d.getVar('exec_prefix') + statement = "grep -e '%s/[^ :]\{1,\}/[^ :]\{1,\}' %s > /dev/null" % (exec_prefix, path) + if subprocess.call(statement, shell=True) == 0: + error_msg = pn + ": Found a reference to %s/ in %s" % (exec_prefix, path) + package_qa_handle_error("unsafe-references-in-scripts", error_msg, d) + error_msg = "Shell scripts in base_bindir and base_sbindir should not reference anything in exec_prefix" + package_qa_handle_error("unsafe-references-in-scripts", error_msg, d) + +def unsafe_references_skippable(path, name, d): + if bb.data.inherits_class('native', d) or bb.data.inherits_class('nativesdk', d): + return True + + if "-dbg" in name or "-dev" in name: + return True + + # Other package names to skip: + if name.startswith("kernel-module-"): + return True + + # Skip symlinks + if os.path.islink(path): + return True + + # Skip unusual rootfs layouts which make these tests irrelevant + exec_prefix = d.getVar('exec_prefix') + if exec_prefix == "": + return True + + pkgdest = d.getVar('PKGDEST') + pkgdest = pkgdest + "/" + name + pkgdest = os.path.abspath(pkgdest) + base_bindir = pkgdest + d.getVar('base_bindir') + base_sbindir = pkgdest + d.getVar('base_sbindir') + base_libdir = pkgdest + d.getVar('base_libdir') + bindir = pkgdest + d.getVar('bindir') + sbindir = pkgdest + d.getVar('sbindir') + libdir = pkgdest + d.getVar('libdir') + + if base_bindir == bindir and base_sbindir == sbindir and base_libdir == libdir: + return True + + # Skip files not in base_[bindir|sbindir|libdir] + path = os.path.abspath(path) + if not (base_bindir in path or base_sbindir in path or base_libdir in path): + return True + + return False + QAPATHTEST[arch] = "package_qa_check_arch" def package_qa_check_arch(path,name,d, elf, messages): """ Check if archs are compatible """ + import re + if not elf: return - target_os = bb.data.getVar('TARGET_OS', d, True) - target_arch = bb.data.getVar('TARGET_ARCH', d, True) + target_os = d.getVar('TARGET_OS') + target_arch = d.getVar('TARGET_ARCH') + provides = d.getVar('PROVIDES') + bpn = d.getVar('BPN') + + if target_arch == "allarch": + pn = d.getVar('PN') + package_qa_add_message(messages, "arch", pn + ": Recipe inherits the allarch class, but has packaged architecture-specific binaries") + return # FIXME: Cross package confuse this check, so just skip them for s in ['cross', 'nativesdk', 'cross-canadian']: @@ -224,17 +505,19 @@ def package_qa_check_arch(path,name,d, elf, messages): #if this will throw an exception, then fix the dict above (machine, osabi, abiversion, littleendian, bits) \ - = package_qa_get_machine_dict()[target_os][target_arch] + = package_qa_get_machine_dict(d)[target_os][target_arch] # Check the architecture and endiannes of the binary - if not machine == elf.machine(): - messages.append("Architecture did not match (%d to %d) on %s" % \ - (machine, elf.machine(), package_qa_clean_path(path,d))) - elif not bits == elf.abiSize(): - messages.append("Bit size did not match (%d to %d) on %s" % \ - (bits, elf.abiSize(), package_qa_clean_path(path,d))) + is_32 = (("virtual/kernel" in provides) or bb.data.inherits_class("module", d)) and \ + (target_os == "linux-gnux32" or re.match('mips64.*32', d.getVar('DEFAULTTUNE'))) + if not ((machine == elf.machine()) or is_32): + package_qa_add_message(messages, "arch", "Architecture did not match (%s, expected %s) on %s" % \ + (oe.qa.elf_machine_to_string(elf.machine()), oe.qa.elf_machine_to_string(machine), package_qa_clean_path(path,d))) + elif not ((bits == elf.abiSize()) or is_32): + package_qa_add_message(messages, "arch", "Bit size did not match (%d to %d) %s on %s" % \ + (bits, elf.abiSize(), bpn, package_qa_clean_path(path,d))) elif not littleendian == elf.isLittleEndian(): - messages.append("Endiannes did not match (%d to %d) on %s" % \ + package_qa_add_message(messages, "arch", "Endiannes did not match (%d to %d) on %s" % \ (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))) QAPATHTEST[desktop] = "package_qa_check_desktop" @@ -243,11 +526,35 @@ def package_qa_check_desktop(path, name, d, elf, messages): Run all desktop files through desktop-file-validate. """ if path.endswith(".desktop"): - desktop_file_validate = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'desktop-file-validate') + desktop_file_validate = os.path.join(d.getVar('STAGING_BINDIR_NATIVE'),'desktop-file-validate') output = os.popen("%s %s" % (desktop_file_validate, path)) # This only produces output on errors for l in output: - messages.append("Desktop file issue: " + l.strip()) + package_qa_add_message(messages, "desktop", "Desktop file issue: " + l.strip()) + +QAPATHTEST[textrel] = "package_qa_textrel" +def package_qa_textrel(path, name, d, elf, messages): + """ + Check if the binary contains relocations in .text + """ + + if not elf: + return + + if os.path.islink(path): + return + + phdrs = elf.run_objdump("-p", d) + sane = True + + import re + textrel_re = re.compile("\s+TEXTREL\s+") + for line in phdrs.split("\n"): + if textrel_re.match(line): + sane = False + + if not sane: + package_qa_add_message(messages, "textrel", "ELF binary '%s' has relocations in .text" % path) QAPATHTEST[ldflags] = "package_qa_hash_style" def package_qa_hash_style(path, name, d, elf, messages): @@ -261,29 +568,28 @@ def package_qa_hash_style(path, name, d, elf, messages): if os.path.islink(path): return - gnu_hash = "--hash-style=gnu" in bb.data.getVar('LDFLAGS', d, True) + gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS') if not gnu_hash: - gnu_hash = "--hash-style=both" in bb.data.getVar('LDFLAGS', d, True) + gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS') if not gnu_hash: return - objdump = bb.data.getVar('OBJDUMP', d, True) - env_path = bb.data.getVar('PATH', d, True) - sane = False has_syms = False + phdrs = elf.run_objdump("-p", d) + # If this binary has symbols, we expect it to have GNU_HASH too. - for line in os.popen("LC_ALL=C PATH=%s %s -p '%s' 2> /dev/null" % (env_path, objdump, path), "r"): + for line in phdrs.split("\n"): if "SYMTAB" in line: has_syms = True if "GNU_HASH" in line: sane = True if "[mips32]" in line or "[mips64]" in line: - sane = True + sane = True if has_syms and not sane: - messages.append("No GNU_HASH in the elf binary: '%s'" % path) + package_qa_add_message(messages, "ldflags", "No GNU_HASH in the elf binary: '%s'" % path) QAPATHTEST[buildpaths] = "package_qa_check_buildpaths" @@ -299,42 +605,91 @@ def package_qa_check_buildpaths(path, name, d, elf, messages): if os.path.islink(path): return - tmpdir = bb.data.getVar('TMPDIR', d, True) - file_content = open(path).read() - if tmpdir in file_content: - messages.append("File %s in package contained reference to tmpdir" % package_qa_clean_path(path,d)) + # Ignore ipk and deb's CONTROL dir + if path.find(name + "/CONTROL/") != -1 or path.find(name + "/DEBIAN/") != -1: + return + + tmpdir = d.getVar('TMPDIR') + with open(path, 'rb') as f: + file_content = f.read().decode('utf-8', errors='ignore') + if tmpdir in file_content: + package_qa_add_message(messages, "buildpaths", "File %s in package contained reference to tmpdir" % package_qa_clean_path(path,d)) + + +QAPATHTEST[xorg-driver-abi] = "package_qa_check_xorg_driver_abi" +def package_qa_check_xorg_driver_abi(path, name, d, elf, messages): + """ + Check that all packages containing Xorg drivers have ABI dependencies + """ + + # Skip dev, dbg or nativesdk packages + if name.endswith("-dev") or name.endswith("-dbg") or name.startswith("nativesdk-"): + return + + driverdir = d.expand("${libdir}/xorg/modules/drivers/") + if driverdir in path and path.endswith(".so"): + mlprefix = d.getVar('MLPREFIX') or '' + for rdep in bb.utils.explode_deps(d.getVar('RDEPENDS_' + name) or ""): + if rdep.startswith("%sxorg-abi-" % mlprefix): + return + package_qa_add_message(messages, "xorg-driver-abi", "Package %s contains Xorg driver (%s) but no xorg-abi- dependencies" % (name, os.path.basename(path))) + +QAPATHTEST[infodir] = "package_qa_check_infodir" +def package_qa_check_infodir(path, name, d, elf, messages): + """ + Check that /usr/share/info/dir isn't shipped in a particular package + """ + infodir = d.expand("${infodir}/dir") + + if infodir in path: + package_qa_add_message(messages, "infodir", "The /usr/share/info/dir file is not meant to be shipped in a particular package.") -def package_qa_check_license(workdir, d): +QAPATHTEST[symlink-to-sysroot] = "package_qa_check_symlink_to_sysroot" +def package_qa_check_symlink_to_sysroot(path, name, d, elf, messages): + """ + Check that the package doesn't contain any absolute symlinks to the sysroot. + """ + if os.path.islink(path): + target = os.readlink(path) + if os.path.isabs(target): + tmpdir = d.getVar('TMPDIR') + if target.startswith(tmpdir): + trimmed = path.replace(os.path.join (d.getVar("PKGDEST"), name), "") + package_qa_add_message(messages, "symlink-to-sysroot", "Symlink %s in %s points to TMPDIR" % (trimmed, name)) + +# Check license variables +do_populate_lic[postfuncs] += "populate_lic_qa_checksum" +python populate_lic_qa_checksum() { """ - Check for changes in the license files + Check for changes in the license files. """ import tempfile sane = True - lic_files = bb.data.getVar('LIC_FILES_CHKSUM', d, True) - lic = bb.data.getVar('LICENSE', d, True) - pn = bb.data.getVar('PN', d, True) + lic_files = d.getVar('LIC_FILES_CHKSUM') or '' + lic = d.getVar('LICENSE') + pn = d.getVar('PN') if lic == "CLOSED": - return True + return - if not lic_files: - # just throw a warning now. Once licensing data in entered for enough of the recipes, - # this will be converted into error and False will be returned. - bb.error(pn + ": Recipe file does not have license file information (LIC_FILES_CHKSUM)") - return False + if not lic_files and d.getVar('SRC_URI'): + sane = package_qa_handle_error("license-checksum", pn + ": Recipe file fetches files and does not have license file information (LIC_FILES_CHKSUM)", d) - srcdir = bb.data.getVar('S', d, True) + srcdir = d.getVar('S') for url in lic_files.split(): - (type, host, path, user, pswd, parm) = bb.decodeurl(url) + try: + (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(url) + except bb.fetch.MalformedUrl: + sane = package_qa_handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM contains an invalid URL: " + url, d) + continue srclicfile = os.path.join(srcdir, path) if not os.path.isfile(srclicfile): - raise bb.build.FuncFailed( pn + ": LIC_FILES_CHKSUM points to invalid file: " + path) + package_qa_handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM points to an invalid file: " + srclicfile, d) + continue - if 'md5' not in parm: - bb.error(pn + ": md5 checksum is not specified for ", url) - return False + recipemd5 = parm.get('md5', '') beginline, endline = 0, 0 if 'beginline' in parm: beginline = int(parm['beginline']) @@ -343,17 +698,21 @@ def package_qa_check_license(workdir, d): if (not beginline) and (not endline): md5chksum = bb.utils.md5_file(srclicfile) + with open(srclicfile, 'rb') as f: + license = f.read() else: - fi = open(srclicfile, 'r') + fi = open(srclicfile, 'rb') fo = tempfile.NamedTemporaryFile(mode='wb', prefix='poky.', suffix='.tmp', delete=False) tmplicfile = fo.name; lineno = 0 linesout = 0 + license = [] for line in fi: lineno += 1 - if (lineno >= beginline): + if (lineno >= beginline): if ((lineno <= endline) or not endline): fo.write(line) + license.append(line) linesout += 1 else: break @@ -361,33 +720,73 @@ def package_qa_check_license(workdir, d): fo.close() fi.close() md5chksum = bb.utils.md5_file(tmplicfile) + license = b''.join(license) os.unlink(tmplicfile) - if parm['md5'] == md5chksum: + if recipemd5 == md5chksum: bb.note (pn + ": md5 checksum matched for ", url) else: - bb.error (pn + ": md5 data is not matching for ", url) - bb.error (pn + ": The new md5 checksum is ", md5chksum) - bb.error (pn + ": Check if the license information has changed in") - sane = False - - return sane + if recipemd5: + msg = pn + ": The LIC_FILES_CHKSUM does not match for " + url + msg = msg + "\n" + pn + ": The new md5 checksum is " + md5chksum + try: + license_lines = license.decode('utf-8').split('\n') + except: + # License text might not be valid UTF-8, in which + # case we don't know how to include it in our output + # and have to skip it. + pass + else: + max_lines = int(d.getVar('QA_MAX_LICENSE_LINES') or 20) + if not license_lines or license_lines[-1] != '': + # Ensure that our license text ends with a line break + # (will be added with join() below). + license_lines.append('') + remove = len(license_lines) - max_lines + if remove > 0: + start = max_lines // 2 + end = start + remove - 1 + del license_lines[start:end] + license_lines.insert(start, '...') + msg = msg + "\n" + pn + ": Here is the selected license text:" + \ + "\n" + \ + "{:v^70}".format(" beginline=%d " % beginline if beginline else "") + \ + "\n" + "\n".join(license_lines) + \ + "{:^^70}".format(" endline=%d " % endline if endline else "") + if beginline: + if endline: + srcfiledesc = "%s (lines %d through to %d)" % (srclicfile, beginline, endline) + else: + srcfiledesc = "%s (beginning on line %d)" % (srclicfile, beginline) + elif endline: + srcfiledesc = "%s (ending on line %d)" % (srclicfile, endline) + else: + srcfiledesc = srclicfile + msg = msg + "\n" + pn + ": Check if the license information has changed in %s to verify that the LICENSE value \"%s\" remains valid" % (srcfiledesc, lic) + + else: + msg = pn + ": LIC_FILES_CHKSUM is not specified for " + url + msg = msg + "\n" + pn + ": The md5 checksum is " + md5chksum + sane = package_qa_handle_error("license-checksum", msg, d) + + if not sane: + bb.fatal("Fatal QA errors found, failing task.") +} def package_qa_check_staged(path,d): """ - Check staged la and pc files for sanity - -e.g. installed being false + Check staged la and pc files for common problems like references to the work + directory. - As this is run after every stage we should be able - to find the one responsible for the errors easily even - if we look at every .pc and .la file + As this is run after every stage we should be able to find the one + responsible for the errors easily even if we look at every .pc and .la file. """ sane = True - tmpdir = bb.data.getVar('TMPDIR', d, True) + tmpdir = d.getVar('TMPDIR') workdir = os.path.join(tmpdir, "work") + r |
