diff options
Diffstat (limited to 'meta/classes/insane.bbclass')
| -rw-r--r-- | meta/classes/insane.bbclass | 809 |
1 files changed, 517 insertions, 292 deletions
diff --git a/meta/classes/insane.bbclass b/meta/classes/insane.bbclass index 2b0c28477..8c4a83a34 100644 --- a/meta/classes/insane.bbclass +++ b/meta/classes/insane.bbclass @@ -11,6 +11,10 @@ # -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 +# -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 # @@ -19,9 +23,14 @@ # The package.bbclass can help us here. # inherit package -PACKAGE_DEPENDS += "pax-utils-native desktop-file-utils-native" +PACKAGE_DEPENDS += "pax-utils-native ${QADEPENDS}" PACKAGEFUNCS += " do_package_qa " +# unsafe-references-in-binaries requires prelink-rtld from +# prelink-native, but we don't want this DEPENDS for -native builds +QADEPENDS = "prelink-native" +QADEPENDS_virtclass-native = "" +QADEPENDS_virtclass-nativesdk = "" # # dictionary for elf headers @@ -31,328 +40,457 @@ PACKAGEFUNCS += " do_package_qa " # TARGET_OS TARGET_ARCH MACHINE, OSABI, ABIVERSION, Little Endian, 32bit? def package_qa_get_machine_dict(): return { + "darwin9" : { + "arm" : (40, 0, 0, True, 32), + }, "linux" : { - "arm" : (40, 97, 0, True, True), - "armeb": (40, 97, 0, False, True), - "powerpc": (20, 0, 0, False, True), - "i386": ( 3, 0, 0, True, True), - "i486": ( 3, 0, 0, True, True), - "i586": ( 3, 0, 0, True, True), - "i686": ( 3, 0, 0, True, True), - "x86_64": (62, 0, 0, True, False), - "ia64": (50, 0, 0, True, False), - "alpha": (36902, 0, 0, True, False), - "hppa": (15, 3, 0, False, True), - "m68k": ( 4, 0, 0, False, True), - "mips": ( 8, 0, 0, False, True), - "mipsel": ( 8, 0, 0, True, True), - "s390": (22, 0, 0, False, True), - "sh4": (42, 0, 0, True, True), - "sparc": ( 2, 0, 0, False, True), + "arm" : (40, 97, 0, True, 32), + "armeb": (40, 97, 0, False, 32), + "powerpc": (20, 0, 0, False, 32), + "powerpc64": (21, 0, 0, False, 64), + "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), + "ia64": (50, 0, 0, True, 64), + "alpha": (36902, 0, 0, True, 64), + "hppa": (15, 3, 0, False, 32), + "m68k": ( 4, 0, 0, False, 32), + "mips": ( 8, 0, 0, False, 32), + "mipsel": ( 8, 0, 0, True, 32), + "s390": (22, 0, 0, False, 32), + "sh4": (42, 0, 0, True, 32), + "sparc": ( 2, 0, 0, False, 32), }, "linux-uclibc" : { - "arm" : ( 40, 97, 0, True, True), - "armeb": ( 40, 97, 0, False, True), - "powerpc": ( 20, 0, 0, False, True), - "i386": ( 3, 0, 0, True, True), - "i486": ( 3, 0, 0, True, True), - "i586": ( 3, 0, 0, True, True), - "i686": ( 3, 0, 0, True, True), - "mipsel": ( 8, 0, 0, True, True), - "avr32": (6317, 0, 0, False, True), + "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), + "avr32": (6317, 0, 0, False, 32), + "sh4": (42, 0, 0, True, 32), + }, "uclinux-uclibc" : { - "bfin": ( 106, 0, 0, True, True), + "bfin": ( 106, 0, 0, True, 32), }, "linux-gnueabi" : { - "arm" : (40, 0, 0, True, True), - "armeb" : (40, 0, 0, False, True), + "arm" : (40, 0, 0, True, 32), + "armeb" : (40, 0, 0, False, 32), }, - "linux-uclibcgnueabi" : { - "arm" : (40, 0, 0, True, True), - "armeb" : (40, 0, 0, False, True), + "linux-uclibceabi" : { + "arm" : (40, 0, 0, True, 32), + "armeb" : (40, 0, 0, False, 32), + }, + "linux-gnu" : { + "powerpc": (20, 0, 0, False, 32), + }, + "linux-gnuspe" : { + "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), + }, + "linux-gnux32" : { + "x86_64": (62, 0, 0, True, 32), }, - } -# factory for a class, embedded in a method -def package_qa_get_elf(path, bits32): - class ELFFile: - EI_NIDENT = 16 - - EI_CLASS = 4 - EI_DATA = 5 - EI_VERSION = 6 - EI_OSABI = 7 - EI_ABIVERSION = 8 - # possible values for EI_CLASS - ELFCLASSNONE = 0 - ELFCLASS32 = 1 - ELFCLASS64 = 2 +# Currently not being used by default "desktop" +WARN_QA ?= "ldflags useless-rpaths rpaths unsafe-references-in-binaries unsafe-references-in-scripts staticdev" +ERROR_QA ?= "dev-so debug-deps dev-deps debug-files arch la2 pkgconfig la perms" - # possible value for EI_VERSION - EV_CURRENT = 1 +def package_qa_clean_path(path,d): + """ Remove the common prefix from the path. In this case it is the TMPDIR""" + return path.replace(d.getVar('TMPDIR',True),"") - # possible values for EI_DATA - ELFDATANONE = 0 - ELFDATA2LSB = 1 - ELFDATA2MSB = 2 +def package_qa_write_error(error, d): + logfile = d.getVar('QA_LOGFILE', True) + if logfile: + p = d.getVar('P', True) + f = file( logfile, "a+") + print >> f, "%s: %s" % (p, error) + f.close() - def my_assert(self, expectation, result): - if not expectation == result: - #print "'%x','%x' %s" % (ord(expectation), ord(result), self.name) - raise Exception("This does not work as expected") +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) + return False + else: + bb.warn("QA Issue: %s" % error_msg) + return True - def __init__(self, name): - self.name = name +QAPATHTEST[rpaths] = "package_qa_check_rpath" +def package_qa_check_rpath(file,name, d, elf, messages): + """ + Check for dangerous RPATHs + """ + if not elf: + return - def open(self): - self.file = file(self.name, "r") - self.data = self.file.read(ELFFile.EI_NIDENT+4) + scanelf = os.path.join(d.getVar('STAGING_BINDIR_NATIVE',True),'scanelf') + bad_dirs = [d.getVar('TMPDIR', True) + "/work", d.getVar('STAGING_DIR_TARGET', True)] + bad_dir_test = d.getVar('TMPDIR', True) + if not os.path.exists(scanelf): + bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found") - self.my_assert(len(self.data), ELFFile.EI_NIDENT+4) - self.my_assert(self.data[0], chr(0x7f) ) - self.my_assert(self.data[1], 'E') - self.my_assert(self.data[2], 'L') - self.my_assert(self.data[3], 'F') - if bits32 : - self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS32)) - else: - self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS64)) - self.my_assert(self.data[ELFFile.EI_VERSION], chr(ELFFile.EV_CURRENT) ) + if not bad_dirs[0] in d.getVar('WORKDIR', True): + bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check") - self.sex = self.data[ELFFile.EI_DATA] - if self.sex == chr(ELFFile.ELFDATANONE): - raise Exception("self.sex == ELFDATANONE") - elif self.sex == chr(ELFFile.ELFDATA2LSB): - self.sex = "<" - elif self.sex == chr(ELFFile.ELFDATA2MSB): - self.sex = ">" - else: - raise Exception("Unknown self.sex") + 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)) - def osAbi(self): - return ord(self.data[ELFFile.EI_OSABI]) +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 + """ + if not elf: + return - def abiVersion(self): - return ord(self.data[ELFFile.EI_ABIVERSION]) + objdump = d.getVar('OBJDUMP', True) + env_path = d.getVar('PATH', True) - def isLittleEndian(self): - return self.sex == "<" + libdir = d.getVar("libdir", True) + base_libdir = d.getVar("base_libdir", True) - def isBigEngian(self): - return self.sex == ">" + 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)) - def machine(self): - """ - We know the sex stored in self.sex and we - know the position - """ - import struct - (a,) = struct.unpack(self.sex+"H", self.data[18:20]) - return a +QAPATHTEST[dev-so] = "package_qa_check_dev" +def package_qa_check_dev(path, name, d, elf, messages): + """ + Check for ".so" library symlinks in non-dev packages + """ - return ELFFile(path) + if not name.endswith("-dev") and not name.endswith("-dbg") and not name.endswith("-nativesdk") and path.endswith(".so") and os.path.islink(path): + messages.append("non -dev/-dbg/-nativesdk package contains 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 + """ -# Known Error classes -# 0 - non dev contains .so -# 1 - package contains a dangerous RPATH -# 2 - package depends on debug package -# 3 - non dbg contains .so -# 4 - wrong architecture -# 5 - .la contains installed=yes or reference to the workdir -# 6 - .pc contains reference to /usr/include or workdir -# 7 - the desktop file is not valid -# 8 - .la contains reference to the workdir + if not name.endswith("-pic") and not name.endswith("-staticdev") and path.endswith(".a") and not path.endswith("_nonshared.a"): + messages.append("non -staticdev package contains static .a library: %s path '%s'" % \ + (name, package_qa_clean_path(path,d))) -def package_qa_clean_path(path,d): - """ Remove the common prefix from the path. In this case it is the TMPDIR""" - import bb - return path.replace(bb.data.getVar('TMPDIR',d,True),"") - -def package_qa_make_fatal_error(error_class, name, path,d): +QAPATHTEST[debug-files] = "package_qa_check_dbg" +def package_qa_check_dbg(path, name, d, elf, messages): """ - decide if an error is fatal - - TODO: Load a whitelist of known errors + Check for ".debug" files or directories outside of the dbg package """ - return not error_class in [0, 5, 7, 8, 9] -def package_qa_write_error(error_class, name, path, d): + if not "-dbg" in name: + if '.debug' in path.split(os.path.sep): + messages.append("non debug package contains .debug directory: %s path %s" % \ + (name, package_qa_clean_path(path,d))) + +QAPATHTEST[perms] = "package_qa_check_perm" +def package_qa_check_perm(path,name,d, elf, messages): """ - Log the error + Check the permission of files """ - import bb, os + return - ERROR_NAMES =[ - "non dev contains .so", - "package contains RPATH", - "package depends on debug package", - "non dbg contains .debug", - "wrong architecture", - "evil hides inside the .la", - "evil hides inside the .pc", - "the desktop file is not valid", - ".la contains reference to the workdir", - "package contains reference to tmpdir paths", - ] +QAPATHTEST[unsafe-references-in-binaries] = "package_qa_check_unsafe_references_in_binaries" +def package_qa_check_unsafe_references_in_binaries(path, name, d, elf, messages): + """ + Ensure binaries in base_[bindir|sbindir|libdir] do not link to files under exec_prefix + """ + if unsafe_references_skippable(path, name, d): + return - log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" ) - f = file( log_path, "a+") - print >> f, "%s, %s, %s" % \ - (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d)) - f.close() + if elf: + import subprocess as sub + pn = d.getVar('PN', True) - logfile = bb.data.getVar('QA_LOGFILE', d, True) - if logfile: - p = bb.data.getVar('P', d, True) - f = file( logfile, "a+") - print >> f, "%s, %s, %s, %s" % \ - (p, ERROR_NAMES[error_class], name, package_qa_clean_path(path,d)) - f.close() + exec_prefix = d.getVar('exec_prefix', True) + sysroot_path = d.getVar('STAGING_DIR_TARGET', True) + sysroot_path_usr = sysroot_path + exec_prefix -def package_qa_handle_error(error_class, error_msg, name, path, d): - import bb - fatal = package_qa_make_fatal_error(error_class, name, path, d) - if fatal: - bb.error("QA Issue: %s" % error_msg) - else: - # Use bb.warn here when it works - bb.note("QA Issue: %s" % error_msg) - package_qa_write_error(error_class, name, path, d) + try: + ldd_output = bb.process.Popen(["prelink-rtld", "--root", sysroot_path, path], stdout=sub.PIPE).stdout.read() + except bb.process.CmdError: + error_msg = pn + ": prelink-rtld aborted when processing %s" % path + package_qa_handle_error("unsafe-references-in-binaries", error_msg, d) + return False - return not fatal + if sysroot_path_usr in ldd_output: + error_msg = pn + ": %s links to something under exec_prefix" % path + package_qa_handle_error("unsafe-references-in-binaries", error_msg, d) + error_msg = "ldd reports: %s" % ldd_output + package_qa_handle_error("unsafe-references-in-binaries", error_msg, d) + return False -def package_qa_check_rpath(file,name,d): - """ - Check for dangerous RPATHs - """ - import bb, os - sane = True - scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf') - bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work" - 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") +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 bad_dir in bb.data.getVar('WORKDIR', d, True): - bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check") + if not elf: + import stat + pn = d.getVar('PN', True) - output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file)) - txt = output.readline().split() - for line in txt: - if bad_dir in line: - error_msg = "package %s contains bad RPATH %s in file %s" % (name, line, file) - sane = package_qa_handle_error(1, error_msg, name, file, d) + # 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', True) + statement = "grep -e '%s/' %s > /dev/null" % (exec_prefix, path) + if os.system(statement) == 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) - return sane +def unsafe_references_skippable(path, name, d): + if bb.data.inherits_class('native', d) or bb.data.inherits_class('nativesdk', d): + return True -def package_qa_check_devdbg(path, name,d): - """ - Check for debug remains inside the binary or - non dev packages containing - """ + if "-dbg" in name or "-dev" in name: + return True - import bb, os - sane = True + # Other package names to skip: + if name.startswith("kernel-module-"): + return True - if not "-dev" in name: - if path[-3:] == ".so" and os.path.islink(path): - error_msg = "non -dev package contains symlink .so: %s path '%s'" % \ - (name, package_qa_clean_path(path,d)) - sane = package_qa_handle_error(0, error_msg, name, path, d) + # Skip symlinks + if os.path.islink(path): + return True - if not "-dbg" in name: - if '.debug' in path: - error_msg = "non debug package contains .debug directory: %s path %s" % \ - (name, package_qa_clean_path(path,d)) - sane = package_qa_handle_error(3, error_msg, name, path, d) + # Skip unusual rootfs layouts which make these tests irrelevant + exec_prefix = d.getVar('exec_prefix', True) + if exec_prefix == "": + return True - return sane + pkgdest = d.getVar('PKGDEST', True) + pkgdest = pkgdest + "/" + name + pkgdest = os.path.abspath(pkgdest) + base_bindir = pkgdest + d.getVar('base_bindir', True) + base_sbindir = pkgdest + d.getVar('base_sbindir', True) + base_libdir = pkgdest + d.getVar('base_libdir', True) + bindir = pkgdest + d.getVar('bindir', True) + sbindir = pkgdest + d.getVar('sbindir', True) + libdir = pkgdest + d.getVar('libdir', True) -def package_qa_check_perm(path,name,d): - """ - Check the permission of files - """ - sane = True - return sane + 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 -def package_qa_check_arch(path,name,d): +QAPATHTEST[arch] = "package_qa_check_arch" +def package_qa_check_arch(path,name,d, elf, messages): """ Check if archs are compatible """ - import bb, os - sane = True - target_os = bb.data.getVar('TARGET_OS', d, True) - target_arch = bb.data.getVar('TARGET_ARCH', d, True) + if not elf: + return + + target_os = d.getVar('TARGET_OS', True) + target_arch = d.getVar('TARGET_ARCH', True) + provides = d.getVar('PROVIDES', d, True) + bpn = d.getVar('BPN', True) # FIXME: Cross package confuse this check, so just skip them - if bb.data.inherits_class('cross', d) or bb.data.inherits_class('sdk', d): - return True + for s in ['cross', 'nativesdk', 'cross-canadian']: + if bb.data.inherits_class(s, d): + return # avoid following links to /usr/bin (e.g. on udev builds) # we will check the files pointed to anyway... if os.path.islink(path): - return True + return #if this will throw an exception, then fix the dict above - (machine, osabi, abiversion, littleendian, bits32) \ + (machine, osabi, abiversion, littleendian, bits) \ = package_qa_get_machine_dict()[target_os][target_arch] - elf = package_qa_get_elf(path, bits32) - try: - elf.open() - except: - return True # Check the architecture and endiannes of the binary - if not machine == elf.machine(): - error_msg = "Architecture did not match (%d to %d) on %s" % \ - (machine, elf.machine(), package_qa_clean_path(path,d)) - sane = package_qa_handle_error(4, error_msg, name, path, d) + if not ((machine == elf.machine()) or \ + ("virtual/kernel" in provides) and (target_os == "linux-gnux32")): + 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()) or \ + ("virtual/kernel" in provides) and (target_os == "linux-gnux32")): + messages.append("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(): - error_msg = "Endiannes did not match (%d to %d) on %s" % \ - (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d)) - sane = package_qa_handle_error(4, error_msg, name, path, d) - - return sane + messages.append("Endiannes did not match (%d to %d) on %s" % \ + (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))) -def package_qa_check_desktop(path, name, d): +QAPATHTEST[desktop] = "package_qa_check_desktop" +def package_qa_check_desktop(path, name, d, elf, messages): """ Run all desktop files through desktop-file-validate. """ - import bb, os - sane = True 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',True),'desktop-file-validate') output = os.popen("%s %s" % (desktop_file_validate, path)) # This only produces output on errors for l in output: - sane = package_qa_handle_error(7, l.strip(), name, path, d) + messages.append("Desktop file issue: " + l.strip()) - return sane +QAPATHTEST[ldflags] = "package_qa_hash_style" +def package_qa_hash_style(path, name, d, elf, messages): + """ + Check if the binary has the right hash style... + """ + + if not elf: + return + + if os.path.islink(path): + return + + gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS', True) + if not gnu_hash: + gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS', True) + if not gnu_hash: + return + + objdump = d.getVar('OBJDUMP', True) + env_path = d.getVar('PATH', True) + + sane = False + has_syms = False -def package_qa_check_buildpaths(path, name, 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"): + if "SYMTAB" in line: + has_syms = True + if "GNU_HASH" in line: + sane = True + if "[mips32]" in line or "[mips64]" in line: + sane = True + + if has_syms and not sane: + messages.append("No GNU_HASH in the elf binary: '%s'" % path) + + +QAPATHTEST[buildpaths] = "package_qa_check_buildpaths" +def package_qa_check_buildpaths(path, name, d, elf, messages): """ Check for build paths inside target files and error if not found in the whitelist """ - import bb, os - sane = True - # Ignore .debug files, not interesting if path.find(".debug") != -1: - return True + return # Ignore symlinks if os.path.islink(path): - return True + return - tmpdir = bb.data.getVar('TMPDIR', d, True) + tmpdir = d.getVar('TMPDIR', True) file_content = open(path).read() if tmpdir in file_content: - error_msg = "File %s in package contained reference to tmpdir" % package_qa_clean_path(path,d) - sane = package_qa_handle_error(9, error_msg, name, path, d) + messages.append("File %s in package contained reference to tmpdir" % package_qa_clean_path(path,d)) + +def package_qa_check_license(workdir, d): + """ + Check for changes in the license files + """ + import tempfile + sane = True + + lic_files = d.getVar('LIC_FILES_CHKSUM', True) + lic = d.getVar('LICENSE', True) + pn = d.getVar('PN', True) + + if lic == "CLOSED": + return True + + 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 + + srcdir = d.getVar('S', True) + + for url in lic_files.split(): + (type, host, path, user, pswd, parm) = bb.decodeurl(url) + srclicfile = os.path.join(srcdir, path) + if not os.path.isfile(srclicfile): + raise bb.build.FuncFailed( pn + ": LIC_FILES_CHKSUM points to an invalid file: " + srclicfile) + + if 'md5' not in parm: + bb.error(pn + ": md5 checksum is not specified for ", url) + return False + beginline, endline = 0, 0 + if 'beginline' in parm: + beginline = int(parm['beginline']) + if 'endline' in parm: + endline = int(parm['endline']) + + if (not beginline) and (not endline): + md5chksum = bb.utils.md5_file(srclicfile) + else: + fi = open(srclicfile, 'r') + fo = tempfile.NamedTemporaryFile(mode='wb', prefix='poky.', suffix='.tmp', delete=False) + tmplicfile = fo.name; + lineno = 0 + linesout = 0 + for line in fi: + lineno += 1 + if (lineno >= beginline): + if ((lineno <= endline) or not endline): + fo.write(line) + linesout += 1 + else: + break + fo.flush() + fo.close() + fi.close() + md5chksum = bb.utils.md5_file(tmplicfile) + os.unlink(tmplicfile) + + if parm['md5'] == 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 def package_qa_check_staged(path,d): @@ -364,10 +502,9 @@ def package_qa_check_staged(path,d): to find the one responsible for the errors easily even if we look at every .pc and .la file """ - import os, bb sane = True - tmpdir = bb.data.getVar('TMPDIR', d, True) + tmpdir = d.getVar('TMPDIR', True) workdir = os.path.join(tmpdir, "work") installed = "installed=yes" @@ -382,126 +519,214 @@ def package_qa_check_staged(path,d): for root, dirs, files in os.walk(path): for file in files: path = os.path.join(root,file) - if file[-2:] == "la": + if file.endswith(".la"): file_content = open(path).read() - # Don't check installed status for native/cross packages - if not bb.data.inherits_class("native", d) and not bb.data.inherits_class("cross", d): - if installed in file_content: - error_msg = "%s failed sanity test (installed) in path %s" % (file,root) - sane = package_qa_handle_error(5, error_msg, "staging", path, d) if workdir in file_content: error_msg = "%s failed sanity test (workdir) in path %s" % (file,root) - sane = package_qa_handle_error(8, error_msg, "staging", path, d) - elif file[-2:] == "pc": + sane = package_qa_handle_error("la", error_msg, d) + elif file.endswith(".pc"): file_content = open(path).read() if pkgconfigcheck in file_content: error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root) - sane = package_qa_handle_error(6, error_msg, "staging", path, d) + sane = package_qa_handle_error("pkgconfig", error_msg, d) return sane # Walk over all files in a directory and call func -def package_qa_walk(path, funcs, package,d): - import os - sane = True +def package_qa_walk(path, warnfuncs, errorfuncs, skip, package, d): + import oe.qa + + #if this will throw an exception, then fix the dict above + target_os = d.getVar('TARGET_OS', True) + target_arch = d.getVar('TARGET_ARCH', True) + warnings = [] + errors = [] for root, dirs, files in os.walk(path): for file in files: path = os.path.join(root,file) - for func in funcs: - if not func(path, package,d): - sane = False + elf = oe.qa.ELFFile(path) + try: + elf.open() + except: + elf = None + for func in warnfuncs: + func(path, package, d, elf, warnings) + for func in errorfuncs: + func(path, package, d, elf, errors) - return sane + for w in warnings: + bb.warn("QA Issue: %s" % w) + package_qa_write_error(w, d) + for e in errors: + bb.error("QA Issue: %s" % e) + package_qa_write_error(e, d) + + return len(errors) == 0 + +def package_qa_check_rdepends(pkg, pkgdest, skip, d): + # Don't do this check for kernel/module recipes, there aren't too many debug/development + # packages and you can get false positives e.g. on kernel-module-lirc-dev + if bb.data.inherits_class("kernel", d) or bb.data.inherits_class("module-base", d): + return True -def package_qa_check_rdepends(pkg, workdir, d): - import bb sane = True if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg: # Copied from package_ipk.bbclass # boiler plate to update the data localdata = bb.data.createCopy(d) - root = "%s/install/%s" % (workdir, pkg) + root = "%s/%s" % (pkgdest, pkg) - bb.data.setVar('ROOT', '', localdata) - bb.data.setVar('ROOT_%s' % pkg, root, localdata) - pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True) + localdata.setVar('ROOT', '') + localdata.setVar('ROOT_%s' % pkg, root) + pkgname = localdata.getVar('PKG_%s' % pkg, True) if not pkgname: pkgname = pkg - bb.data.setVar('PKG', pkgname, localdata) + localdata.setVar('PKG', pkgname) - overrides = bb.data.getVar('OVERRIDES', localdata) - if not overrides: - raise bb.build.FuncFailed('OVERRIDES not defined') - overrides = bb.data.expand(overrides, localdata) - bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata) + localdata.setVar('OVERRIDES', pkg) bb.data.update_data(localdata) # Now check the RDEPENDS - rdepends = bb.utils.explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "") + rdepends = bb.utils.explode_deps(localdata.getVar('RDEPENDS', True) or "") # Now do the sanity check!!! for rdepend in rdepends: - if "-dbg" in rdepend: + if "-dbg" in rdepend and "debug-deps" not in skip: error_msg = "%s rdepends on %s" % (pkgname,rdepend) - sane = package_qa_handle_error(2, error_msg, pkgname, rdepend, d) + sane = package_qa_handle_error("debug-deps", error_msg, d) + if (not "-dev" in pkg and not "-staticdev" in pkg) and rdepend.endswith("-dev") and "dev-deps" not in skip: + error_msg = "%s rdepends on %s" % (pkgname, rdepend) + sane = package_qa_handle_error("dev-deps", error_msg, d) return sane # The PACKAGE FUNC to scan each package python do_package_qa () { bb.note("DO PACKAGE QA") - workdir = bb.data.getVar('WORKDIR', d, True) - packages = bb.data.getVar('PACKAGES',d, True) + + logdir = d.getVar('T', True) + pkg = d.getVar('PN', True) + + # Check the compile log for host contamination + compilelog = os.path.join(logdir,"log.do_compile") + + if os.path.exists(compilelog): + statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % compilelog + if os.system(statement) == 0: + bb.warn("%s: The compile log indicates that host include and/or library paths were used.\n \ + Please check the log '%s' for more information." % (pkg, compilelog)) + + # Check the install log for host contamination + installlog = os.path.join(logdir,"log.do_install") + + if os.path.exists(installlog): + statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % installlog + if os.system(statement) == 0: + bb.warn("%s: The install log indicates that host include and/or library paths were used.\n \ + Please check the log '%s' for more information." % (pkg, installlog)) + + # Scan the packages... + pkgdest = d.getVar('PKGDEST', True) + packages = d.getVar('PACKAGES', True) # no packages should be scanned if not packages: return - checks = [package_qa_check_rpath, package_qa_check_devdbg, - package_qa_check_perm, package_qa_check_arch, - package_qa_check_desktop, package_qa_check_buildpaths] + testmatrix = d.getVarFlags("QAPATHTEST") + + g = globals() walk_sane = True rdepends_sane = True for package in packages.split(): - if bb.data.getVar('INSANE_SKIP_' + package, d, True): - bb.note("Package: %s (skipped)" % package) - continue + skip = (d.getVar('INSANE_SKIP_' + package, True) or "").split() + if skip: + bb.note("Package %s skipping QA tests: %s" % (package, str(skip))) + warnchecks = [] + for w in (d.getVar("WARN_QA", True) or "").split(): + if w in skip: + continue + if w in testmatrix and testmatrix[w] in g: + warnchecks.append(g[testmatrix[w]]) + errorchecks = [] + for e in (d.getVar("ERROR_QA", True) or "").split(): + if e in skip: + continue + if e in testmatrix and testmatrix[e] in g: + errorchecks.append(g[testmatrix[e]]) bb.note("Checking Package: %s" % package) - path = "%s/install/%s" % (workdir, package) - if not package_qa_walk(path, checks, package, d): + path = "%s/%s" % (pkgdest, package) + if not package_qa_walk(path, warnchecks, errorchecks, skip, package, d): walk_sane = False - if not package_qa_check_rdepends(package, workdir, d): + if not package_qa_check_rdepends(package, pkgdest, skip, d): rdepends_sane = False + if not walk_sane or not rdepends_sane: bb.fatal("QA run found fatal errors. Please consider fixing them.") bb.note("DONE with PACKAGE QA") } -# The Staging Func, to check all staging -addtask qa_staging after do_populate_staging before do_build python do_qa_staging() { bb.note("QA checking staging") - if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d): + if not package_qa_check_staged(d.expand('${SYSROOT_DESTDIR}/${STAGING_LIBDIR}'), d): bb.fatal("QA staging was broken by the package built above") } -# Check broken config.log files -addtask qa_configure after do_configure before do_compile python do_qa_configure() { - bb.note("Checking sanity of the config.log file") - import os - for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)): - statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \ + configs = [] + workdir = d.getVar('WORKDIR', True) + bb.note("Checking autotools environment for common misconfiguration") + for root, dirs, files in os.walk(workdir): + statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % \ os.path.join(root,"config.log") if "config.log" in files: if os.system(statement) == 0: - bb.fatal("""This autoconf log indicates errors, it looked at host includes. + bb.fatal("""This autoconf log indicates errors, it looked at host include and/or library paths while determining system capabilities. Rerun configure task after fixing this. The path was '%s'""" % root) + + if "configure.ac" in files: + configs.append(os.path.join(root,"configure.ac")) + if "configure.in" in files: + configs.append(os.path.join(root, "configure.in")) + + cnf = d.getVar('EXTRA_OECONF', True) or "" + if "gettext" not in d.getVar('P', True) and "gcc-runtime" not in d.getVar('P', True) and "--disable-nls" not in cnf: + ml = d.getVar("MLPREFIX", True) or "" + 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): + gt = "gettext-native" + elif bb.data.inherits_class('cross-canadian', d): + gt = "gettext-nativesdk" + else: + gt = "virtual/" + ml + "gettext" + deps = bb.utils.explode_deps(d.getVar('DEPENDS', True) or "") + if gt not in deps: + for config in configs: + gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config + if os.system(gnu) == 0: + bb.fatal("""%s required but not in DEPENDS for file %s. +Missing inherit gettext?""" % (gt, config)) + + if not package_qa_check_license(workdir, d): + bb.fatal("Licensing Error: LIC_FILES_CHKSUM does not match, please fix") +} +# The Staging Func, to check all staging +#addtask qa_staging after do_populate_sysroot before do_build +do_populate_sysroot[postfuncs] += "do_qa_staging " + +# Check broken config.log files, for packages requiring Gettext which don't +# have it in DEPENDS and for correct LIC_FILES_CHKSUM +#addtask qa_configure after do_configure before do_compile +do_configure[postfuncs] += "do_qa_configure " + +python () { + tests = d.getVar('WARN_QA', True) + " " + d.getVar('ERROR_QA', True) + if tests.find("desktop") != -1: + d.appendVar("PACKAGE_DEPENDS", "desktop-file-utils-native") } |
