1*4882a593Smuzhiyun# BB Class inspired by ebuild.sh 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# This class will test files after installation for certain 4*4882a593Smuzhiyun# security issues and other kind of issues. 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun# Checks we do: 7*4882a593Smuzhiyun# -Check the ownership and permissions 8*4882a593Smuzhiyun# -Check the RUNTIME path for the $TMPDIR 9*4882a593Smuzhiyun# -Check if .la files wrongly point to workdir 10*4882a593Smuzhiyun# -Check if .pc files wrongly point to workdir 11*4882a593Smuzhiyun# -Check if packages contains .debug directories or .so files 12*4882a593Smuzhiyun# where they should be in -dev or -dbg 13*4882a593Smuzhiyun# -Check if config.log contains traces to broken autoconf tests 14*4882a593Smuzhiyun# -Check invalid characters (non-utf8) on some package metadata 15*4882a593Smuzhiyun# -Ensure that binaries in base_[bindir|sbindir|libdir] do not link 16*4882a593Smuzhiyun# into exec_prefix 17*4882a593Smuzhiyun# -Check that scripts in base_[bindir|sbindir|libdir] do not reference 18*4882a593Smuzhiyun# files under exec_prefix 19*4882a593Smuzhiyun# -Check if the package name is upper case 20*4882a593Smuzhiyun 21*4882a593Smuzhiyun# Elect whether a given type of error is a warning or error, they may 22*4882a593Smuzhiyun# have been set by other files. 23*4882a593SmuzhiyunWARN_QA ?= " libdir xorg-driver-abi \ 24*4882a593Smuzhiyun textrel incompatible-license files-invalid \ 25*4882a593Smuzhiyun infodir build-deps src-uri-bad symlink-to-sysroot multilib \ 26*4882a593Smuzhiyun invalid-packageconfig host-user-contaminated uppercase-pn patch-fuzz \ 27*4882a593Smuzhiyun mime mime-xdg unlisted-pkg-lics unhandled-features-check \ 28*4882a593Smuzhiyun missing-update-alternatives native-last missing-ptest \ 29*4882a593Smuzhiyun license-exists license-no-generic license-syntax license-format \ 30*4882a593Smuzhiyun license-incompatible license-file-missing obsolete-license \ 31*4882a593Smuzhiyun " 32*4882a593SmuzhiyunERROR_QA ?= "dev-so debug-deps dev-deps debug-files arch pkgconfig la \ 33*4882a593Smuzhiyun perms dep-cmp pkgvarcheck perm-config perm-line perm-link \ 34*4882a593Smuzhiyun split-strip packages-list pkgv-undefined var-undefined \ 35*4882a593Smuzhiyun version-going-backwards expanded-d invalid-chars \ 36*4882a593Smuzhiyun license-checksum dev-elf file-rdeps configure-unsafe \ 37*4882a593Smuzhiyun configure-gettext perllocalpod shebang-size \ 38*4882a593Smuzhiyun already-stripped installed-vs-shipped ldflags compile-host-path \ 39*4882a593Smuzhiyun install-host-path pn-overrides unknown-configure-option \ 40*4882a593Smuzhiyun useless-rpaths rpaths staticdev empty-dirs \ 41*4882a593Smuzhiyun " 42*4882a593Smuzhiyun# Add usrmerge QA check based on distro feature 43*4882a593SmuzhiyunERROR_QA:append = "${@bb.utils.contains('DISTRO_FEATURES', 'usrmerge', ' usrmerge', '', d)}" 44*4882a593Smuzhiyun 45*4882a593SmuzhiyunFAKEROOT_QA = "host-user-contaminated" 46*4882a593SmuzhiyunFAKEROOT_QA[doc] = "QA tests which need to run under fakeroot. If any \ 47*4882a593Smuzhiyunenabled tests are listed here, the do_package_qa task will run under fakeroot." 48*4882a593Smuzhiyun 49*4882a593SmuzhiyunALL_QA = "${WARN_QA} ${ERROR_QA}" 50*4882a593Smuzhiyun 51*4882a593SmuzhiyunUNKNOWN_CONFIGURE_OPT_IGNORE ?= "--enable-nls --disable-nls --disable-silent-rules --disable-dependency-tracking --with-libtool-sysroot --disable-static" 52*4882a593Smuzhiyun 53*4882a593Smuzhiyun# This is a list of directories that are expected to be empty. 54*4882a593SmuzhiyunQA_EMPTY_DIRS ?= " \ 55*4882a593Smuzhiyun /dev/pts \ 56*4882a593Smuzhiyun /media \ 57*4882a593Smuzhiyun /proc \ 58*4882a593Smuzhiyun /run \ 59*4882a593Smuzhiyun /tmp \ 60*4882a593Smuzhiyun ${localstatedir}/run \ 61*4882a593Smuzhiyun ${localstatedir}/volatile \ 62*4882a593Smuzhiyun" 63*4882a593Smuzhiyun# It is possible to specify why a directory is expected to be empty by defining 64*4882a593Smuzhiyun# QA_EMPTY_DIRS_RECOMMENDATION:<path>, which will then be included in the error 65*4882a593Smuzhiyun# message if the directory is not empty. If it is not specified for a directory, 66*4882a593Smuzhiyun# then "but it is expected to be empty" will be used. 67*4882a593Smuzhiyun 68*4882a593Smuzhiyundef package_qa_clean_path(path, d, pkg=None): 69*4882a593Smuzhiyun """ 70*4882a593Smuzhiyun Remove redundant paths from the path for display. If pkg isn't set then 71*4882a593Smuzhiyun TMPDIR is stripped, otherwise PKGDEST/pkg is stripped. 72*4882a593Smuzhiyun """ 73*4882a593Smuzhiyun if pkg: 74*4882a593Smuzhiyun path = path.replace(os.path.join(d.getVar("PKGDEST"), pkg), "/") 75*4882a593Smuzhiyun return path.replace(d.getVar("TMPDIR"), "/").replace("//", "/") 76*4882a593Smuzhiyun 77*4882a593SmuzhiyunQAPATHTEST[shebang-size] = "package_qa_check_shebang_size" 78*4882a593Smuzhiyundef package_qa_check_shebang_size(path, name, d, elf, messages): 79*4882a593Smuzhiyun import stat 80*4882a593Smuzhiyun if os.path.islink(path) or stat.S_ISFIFO(os.stat(path).st_mode) or elf: 81*4882a593Smuzhiyun return 82*4882a593Smuzhiyun 83*4882a593Smuzhiyun try: 84*4882a593Smuzhiyun with open(path, 'rb') as f: 85*4882a593Smuzhiyun stanza = f.readline(130) 86*4882a593Smuzhiyun except IOError: 87*4882a593Smuzhiyun return 88*4882a593Smuzhiyun 89*4882a593Smuzhiyun if stanza.startswith(b'#!'): 90*4882a593Smuzhiyun #Shebang not found 91*4882a593Smuzhiyun try: 92*4882a593Smuzhiyun stanza = stanza.decode("utf-8") 93*4882a593Smuzhiyun except UnicodeDecodeError: 94*4882a593Smuzhiyun #If it is not a text file, it is not a script 95*4882a593Smuzhiyun return 96*4882a593Smuzhiyun 97*4882a593Smuzhiyun if len(stanza) > 129: 98*4882a593Smuzhiyun oe.qa.add_message(messages, "shebang-size", "%s: %s maximum shebang size exceeded, the maximum size is 128." % (name, package_qa_clean_path(path, d))) 99*4882a593Smuzhiyun return 100*4882a593Smuzhiyun 101*4882a593SmuzhiyunQAPATHTEST[libexec] = "package_qa_check_libexec" 102*4882a593Smuzhiyundef package_qa_check_libexec(path,name, d, elf, messages): 103*4882a593Smuzhiyun 104*4882a593Smuzhiyun # Skip the case where the default is explicitly /usr/libexec 105*4882a593Smuzhiyun libexec = d.getVar('libexecdir') 106*4882a593Smuzhiyun if libexec == "/usr/libexec": 107*4882a593Smuzhiyun return True 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun if 'libexec' in path.split(os.path.sep): 110*4882a593Smuzhiyun oe.qa.add_message(messages, "libexec", "%s: %s is using libexec please relocate to %s" % (name, package_qa_clean_path(path, d), libexec)) 111*4882a593Smuzhiyun return False 112*4882a593Smuzhiyun 113*4882a593Smuzhiyun return True 114*4882a593Smuzhiyun 115*4882a593SmuzhiyunQAPATHTEST[rpaths] = "package_qa_check_rpath" 116*4882a593Smuzhiyundef package_qa_check_rpath(file,name, d, elf, messages): 117*4882a593Smuzhiyun """ 118*4882a593Smuzhiyun Check for dangerous RPATHs 119*4882a593Smuzhiyun """ 120*4882a593Smuzhiyun if not elf: 121*4882a593Smuzhiyun return 122*4882a593Smuzhiyun 123*4882a593Smuzhiyun if os.path.islink(file): 124*4882a593Smuzhiyun return 125*4882a593Smuzhiyun 126*4882a593Smuzhiyun bad_dirs = [d.getVar('BASE_WORKDIR'), d.getVar('STAGING_DIR_TARGET')] 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun phdrs = elf.run_objdump("-p", d) 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun import re 131*4882a593Smuzhiyun rpath_re = re.compile(r"\s+RPATH\s+(.*)") 132*4882a593Smuzhiyun for line in phdrs.split("\n"): 133*4882a593Smuzhiyun m = rpath_re.match(line) 134*4882a593Smuzhiyun if m: 135*4882a593Smuzhiyun rpath = m.group(1) 136*4882a593Smuzhiyun for dir in bad_dirs: 137*4882a593Smuzhiyun if dir in rpath: 138*4882a593Smuzhiyun oe.qa.add_message(messages, "rpaths", "package %s contains bad RPATH %s in file %s" % (name, rpath, file)) 139*4882a593Smuzhiyun 140*4882a593SmuzhiyunQAPATHTEST[useless-rpaths] = "package_qa_check_useless_rpaths" 141*4882a593Smuzhiyundef package_qa_check_useless_rpaths(file, name, d, elf, messages): 142*4882a593Smuzhiyun """ 143*4882a593Smuzhiyun Check for RPATHs that are useless but not dangerous 144*4882a593Smuzhiyun """ 145*4882a593Smuzhiyun def rpath_eq(a, b): 146*4882a593Smuzhiyun return os.path.normpath(a) == os.path.normpath(b) 147*4882a593Smuzhiyun 148*4882a593Smuzhiyun if not elf: 149*4882a593Smuzhiyun return 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun if os.path.islink(file): 152*4882a593Smuzhiyun return 153*4882a593Smuzhiyun 154*4882a593Smuzhiyun libdir = d.getVar("libdir") 155*4882a593Smuzhiyun base_libdir = d.getVar("base_libdir") 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun phdrs = elf.run_objdump("-p", d) 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun import re 160*4882a593Smuzhiyun rpath_re = re.compile(r"\s+RPATH\s+(.*)") 161*4882a593Smuzhiyun for line in phdrs.split("\n"): 162*4882a593Smuzhiyun m = rpath_re.match(line) 163*4882a593Smuzhiyun if m: 164*4882a593Smuzhiyun rpath = m.group(1) 165*4882a593Smuzhiyun if rpath_eq(rpath, libdir) or rpath_eq(rpath, base_libdir): 166*4882a593Smuzhiyun # The dynamic linker searches both these places anyway. There is no point in 167*4882a593Smuzhiyun # looking there again. 168*4882a593Smuzhiyun oe.qa.add_message(messages, "useless-rpaths", "%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d, name), rpath)) 169*4882a593Smuzhiyun 170*4882a593SmuzhiyunQAPATHTEST[dev-so] = "package_qa_check_dev" 171*4882a593Smuzhiyundef package_qa_check_dev(path, name, d, elf, messages): 172*4882a593Smuzhiyun """ 173*4882a593Smuzhiyun Check for ".so" library symlinks in non-dev packages 174*4882a593Smuzhiyun """ 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun 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): 177*4882a593Smuzhiyun oe.qa.add_message(messages, "dev-so", "non -dev/-dbg/nativesdk- package %s contains symlink .so '%s'" % \ 178*4882a593Smuzhiyun (name, package_qa_clean_path(path, d, name))) 179*4882a593Smuzhiyun 180*4882a593SmuzhiyunQAPATHTEST[dev-elf] = "package_qa_check_dev_elf" 181*4882a593Smuzhiyundef package_qa_check_dev_elf(path, name, d, elf, messages): 182*4882a593Smuzhiyun """ 183*4882a593Smuzhiyun Check that -dev doesn't contain real shared libraries. The test has to 184*4882a593Smuzhiyun check that the file is not a link and is an ELF object as some recipes 185*4882a593Smuzhiyun install link-time .so files that are linker scripts. 186*4882a593Smuzhiyun """ 187*4882a593Smuzhiyun if name.endswith("-dev") and path.endswith(".so") and not os.path.islink(path) and elf: 188*4882a593Smuzhiyun oe.qa.add_message(messages, "dev-elf", "-dev package %s contains non-symlink .so '%s'" % \ 189*4882a593Smuzhiyun (name, package_qa_clean_path(path, d, name))) 190*4882a593Smuzhiyun 191*4882a593SmuzhiyunQAPATHTEST[staticdev] = "package_qa_check_staticdev" 192*4882a593Smuzhiyundef package_qa_check_staticdev(path, name, d, elf, messages): 193*4882a593Smuzhiyun """ 194*4882a593Smuzhiyun Check for ".a" library in non-staticdev packages 195*4882a593Smuzhiyun There are a number of exceptions to this rule, -pic packages can contain 196*4882a593Smuzhiyun static libraries, the _nonshared.a belong with their -dev packages and 197*4882a593Smuzhiyun libgcc.a, libgcov.a will be skipped in their packages 198*4882a593Smuzhiyun """ 199*4882a593Smuzhiyun 200*4882a593Smuzhiyun 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") and not '/usr/lib/debug-static/' in path and not '/.debug-static/' in path: 201*4882a593Smuzhiyun oe.qa.add_message(messages, "staticdev", "non -staticdev package contains static .a library: %s path '%s'" % \ 202*4882a593Smuzhiyun (name, package_qa_clean_path(path,d, name))) 203*4882a593Smuzhiyun 204*4882a593SmuzhiyunQAPATHTEST[mime] = "package_qa_check_mime" 205*4882a593Smuzhiyundef package_qa_check_mime(path, name, d, elf, messages): 206*4882a593Smuzhiyun """ 207*4882a593Smuzhiyun Check if package installs mime types to /usr/share/mime/packages 208*4882a593Smuzhiyun while no inheriting mime.bbclass 209*4882a593Smuzhiyun """ 210*4882a593Smuzhiyun 211*4882a593Smuzhiyun if d.getVar("datadir") + "/mime/packages" in path and path.endswith('.xml') and not bb.data.inherits_class("mime", d): 212*4882a593Smuzhiyun oe.qa.add_message(messages, "mime", "package contains mime types but does not inherit mime: %s path '%s'" % \ 213*4882a593Smuzhiyun (name, package_qa_clean_path(path,d))) 214*4882a593Smuzhiyun 215*4882a593SmuzhiyunQAPATHTEST[mime-xdg] = "package_qa_check_mime_xdg" 216*4882a593Smuzhiyundef package_qa_check_mime_xdg(path, name, d, elf, messages): 217*4882a593Smuzhiyun """ 218*4882a593Smuzhiyun Check if package installs desktop file containing MimeType and requires 219*4882a593Smuzhiyun mime-types.bbclass to create /usr/share/applications/mimeinfo.cache 220*4882a593Smuzhiyun """ 221*4882a593Smuzhiyun 222*4882a593Smuzhiyun if d.getVar("datadir") + "/applications" in path and path.endswith('.desktop') and not bb.data.inherits_class("mime-xdg", d): 223*4882a593Smuzhiyun mime_type_found = False 224*4882a593Smuzhiyun try: 225*4882a593Smuzhiyun with open(path, 'r') as f: 226*4882a593Smuzhiyun for line in f.read().split('\n'): 227*4882a593Smuzhiyun if 'MimeType' in line: 228*4882a593Smuzhiyun mime_type_found = True 229*4882a593Smuzhiyun break; 230*4882a593Smuzhiyun except: 231*4882a593Smuzhiyun # At least libreoffice installs symlinks with absolute paths that are dangling here. 232*4882a593Smuzhiyun # We could implement some magic but for few (one) recipes it is not worth the effort so just warn: 233*4882a593Smuzhiyun wstr = "%s cannot open %s - is it a symlink with absolute path?\n" % (name, package_qa_clean_path(path,d)) 234*4882a593Smuzhiyun wstr += "Please check if (linked) file contains key 'MimeType'.\n" 235*4882a593Smuzhiyun pkgname = name 236*4882a593Smuzhiyun if name == d.getVar('PN'): 237*4882a593Smuzhiyun pkgname = '${PN}' 238*4882a593Smuzhiyun wstr += "If yes: add \'inhert mime-xdg\' and \'MIME_XDG_PACKAGES += \"%s\"\' / if no add \'INSANE_SKIP:%s += \"mime-xdg\"\' to recipe." % (pkgname, pkgname) 239*4882a593Smuzhiyun oe.qa.add_message(messages, "mime-xdg", wstr) 240*4882a593Smuzhiyun if mime_type_found: 241*4882a593Smuzhiyun oe.qa.add_message(messages, "mime-xdg", "package contains desktop file with key 'MimeType' but does not inhert mime-xdg: %s path '%s'" % \ 242*4882a593Smuzhiyun (name, package_qa_clean_path(path,d))) 243*4882a593Smuzhiyun 244*4882a593Smuzhiyundef package_qa_check_libdir(d): 245*4882a593Smuzhiyun """ 246*4882a593Smuzhiyun Check for wrong library installation paths. For instance, catch 247*4882a593Smuzhiyun recipes installing /lib/bar.so when ${base_libdir}="lib32" or 248*4882a593Smuzhiyun installing in /usr/lib64 when ${libdir}="/usr/lib" 249*4882a593Smuzhiyun """ 250*4882a593Smuzhiyun import re 251*4882a593Smuzhiyun 252*4882a593Smuzhiyun pkgdest = d.getVar('PKGDEST') 253*4882a593Smuzhiyun base_libdir = d.getVar("base_libdir") + os.sep 254*4882a593Smuzhiyun libdir = d.getVar("libdir") + os.sep 255*4882a593Smuzhiyun libexecdir = d.getVar("libexecdir") + os.sep 256*4882a593Smuzhiyun exec_prefix = d.getVar("exec_prefix") + os.sep 257*4882a593Smuzhiyun 258*4882a593Smuzhiyun messages = [] 259*4882a593Smuzhiyun 260*4882a593Smuzhiyun # The re's are purposely fuzzy, as some there are some .so.x.y.z files 261*4882a593Smuzhiyun # that don't follow the standard naming convention. It checks later 262*4882a593Smuzhiyun # that they are actual ELF files 263*4882a593Smuzhiyun lib_re = re.compile(r"^/lib.+\.so(\..+)?$") 264*4882a593Smuzhiyun exec_re = re.compile(r"^%s.*/lib.+\.so(\..+)?$" % exec_prefix) 265*4882a593Smuzhiyun 266*4882a593Smuzhiyun for root, dirs, files in os.walk(pkgdest): 267*4882a593Smuzhiyun if root == pkgdest: 268*4882a593Smuzhiyun # Skip subdirectories for any packages with libdir in INSANE_SKIP 269*4882a593Smuzhiyun skippackages = [] 270*4882a593Smuzhiyun for package in dirs: 271*4882a593Smuzhiyun if 'libdir' in (d.getVar('INSANE_SKIP:' + package) or "").split(): 272*4882a593Smuzhiyun bb.note("Package %s skipping libdir QA test" % (package)) 273*4882a593Smuzhiyun skippackages.append(package) 274*4882a593Smuzhiyun elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-file-directory' and package.endswith("-dbg"): 275*4882a593Smuzhiyun bb.note("Package %s skipping libdir QA test for PACKAGE_DEBUG_SPLIT_STYLE equals debug-file-directory" % (package)) 276*4882a593Smuzhiyun skippackages.append(package) 277*4882a593Smuzhiyun for package in skippackages: 278*4882a593Smuzhiyun dirs.remove(package) 279*4882a593Smuzhiyun for file in files: 280*4882a593Smuzhiyun full_path = os.path.join(root, file) 281*4882a593Smuzhiyun rel_path = os.path.relpath(full_path, pkgdest) 282*4882a593Smuzhiyun if os.sep in rel_path: 283*4882a593Smuzhiyun package, rel_path = rel_path.split(os.sep, 1) 284*4882a593Smuzhiyun rel_path = os.sep + rel_path 285*4882a593Smuzhiyun if lib_re.match(rel_path): 286*4882a593Smuzhiyun if base_libdir not in rel_path: 287*4882a593Smuzhiyun # make sure it's an actual ELF file 288*4882a593Smuzhiyun elf = oe.qa.ELFFile(full_path) 289*4882a593Smuzhiyun try: 290*4882a593Smuzhiyun elf.open() 291*4882a593Smuzhiyun messages.append("%s: found library in wrong location: %s" % (package, rel_path)) 292*4882a593Smuzhiyun except (oe.qa.NotELFFileError): 293*4882a593Smuzhiyun pass 294*4882a593Smuzhiyun if exec_re.match(rel_path): 295*4882a593Smuzhiyun if libdir not in rel_path and libexecdir not in rel_path: 296*4882a593Smuzhiyun # make sure it's an actual ELF file 297*4882a593Smuzhiyun elf = oe.qa.ELFFile(full_path) 298*4882a593Smuzhiyun try: 299*4882a593Smuzhiyun elf.open() 300*4882a593Smuzhiyun messages.append("%s: found library in wrong location: %s" % (package, rel_path)) 301*4882a593Smuzhiyun except (oe.qa.NotELFFileError): 302*4882a593Smuzhiyun pass 303*4882a593Smuzhiyun 304*4882a593Smuzhiyun if messages: 305*4882a593Smuzhiyun oe.qa.handle_error("libdir", "\n".join(messages), d) 306*4882a593Smuzhiyun 307*4882a593SmuzhiyunQAPATHTEST[debug-files] = "package_qa_check_dbg" 308*4882a593Smuzhiyundef package_qa_check_dbg(path, name, d, elf, messages): 309*4882a593Smuzhiyun """ 310*4882a593Smuzhiyun Check for ".debug" files or directories outside of the dbg package 311*4882a593Smuzhiyun """ 312*4882a593Smuzhiyun 313*4882a593Smuzhiyun if not "-dbg" in name and not "-ptest" in name: 314*4882a593Smuzhiyun if '.debug' in path.split(os.path.sep): 315*4882a593Smuzhiyun oe.qa.add_message(messages, "debug-files", "non debug package contains .debug directory: %s path %s" % \ 316*4882a593Smuzhiyun (name, package_qa_clean_path(path,d))) 317*4882a593Smuzhiyun 318*4882a593SmuzhiyunQAPATHTEST[arch] = "package_qa_check_arch" 319*4882a593Smuzhiyundef package_qa_check_arch(path,name,d, elf, messages): 320*4882a593Smuzhiyun """ 321*4882a593Smuzhiyun Check if archs are compatible 322*4882a593Smuzhiyun """ 323*4882a593Smuzhiyun import re, oe.elf 324*4882a593Smuzhiyun 325*4882a593Smuzhiyun if not elf: 326*4882a593Smuzhiyun return 327*4882a593Smuzhiyun 328*4882a593Smuzhiyun target_os = d.getVar('HOST_OS') 329*4882a593Smuzhiyun target_arch = d.getVar('HOST_ARCH') 330*4882a593Smuzhiyun provides = d.getVar('PROVIDES') 331*4882a593Smuzhiyun bpn = d.getVar('BPN') 332*4882a593Smuzhiyun 333*4882a593Smuzhiyun if target_arch == "allarch": 334*4882a593Smuzhiyun pn = d.getVar('PN') 335*4882a593Smuzhiyun oe.qa.add_message(messages, "arch", pn + ": Recipe inherits the allarch class, but has packaged architecture-specific binaries") 336*4882a593Smuzhiyun return 337*4882a593Smuzhiyun 338*4882a593Smuzhiyun # FIXME: Cross package confuse this check, so just skip them 339*4882a593Smuzhiyun for s in ['cross', 'nativesdk', 'cross-canadian']: 340*4882a593Smuzhiyun if bb.data.inherits_class(s, d): 341*4882a593Smuzhiyun return 342*4882a593Smuzhiyun 343*4882a593Smuzhiyun # avoid following links to /usr/bin (e.g. on udev builds) 344*4882a593Smuzhiyun # we will check the files pointed to anyway... 345*4882a593Smuzhiyun if os.path.islink(path): 346*4882a593Smuzhiyun return 347*4882a593Smuzhiyun 348*4882a593Smuzhiyun #if this will throw an exception, then fix the dict above 349*4882a593Smuzhiyun (machine, osabi, abiversion, littleendian, bits) \ 350*4882a593Smuzhiyun = oe.elf.machine_dict(d)[target_os][target_arch] 351*4882a593Smuzhiyun 352*4882a593Smuzhiyun # Check the architecture and endiannes of the binary 353*4882a593Smuzhiyun is_32 = (("virtual/kernel" in provides) or bb.data.inherits_class("module", d)) and \ 354*4882a593Smuzhiyun (target_os == "linux-gnux32" or target_os == "linux-muslx32" or \ 355*4882a593Smuzhiyun target_os == "linux-gnu_ilp32" or re.match(r'mips64.*32', d.getVar('DEFAULTTUNE'))) 356*4882a593Smuzhiyun is_bpf = (oe.qa.elf_machine_to_string(elf.machine()) == "BPF") 357*4882a593Smuzhiyun if not ((machine == elf.machine()) or is_32 or is_bpf): 358*4882a593Smuzhiyun oe.qa.add_message(messages, "arch", "Architecture did not match (%s, expected %s) in %s" % \ 359*4882a593Smuzhiyun (oe.qa.elf_machine_to_string(elf.machine()), oe.qa.elf_machine_to_string(machine), package_qa_clean_path(path, d, name))) 360*4882a593Smuzhiyun elif not ((bits == elf.abiSize()) or is_32 or is_bpf): 361*4882a593Smuzhiyun oe.qa.add_message(messages, "arch", "Bit size did not match (%d, expected %d) in %s" % \ 362*4882a593Smuzhiyun (elf.abiSize(), bits, package_qa_clean_path(path, d, name))) 363*4882a593Smuzhiyun elif not ((littleendian == elf.isLittleEndian()) or is_bpf): 364*4882a593Smuzhiyun oe.qa.add_message(messages, "arch", "Endiannes did not match (%d, expected %d) in %s" % \ 365*4882a593Smuzhiyun (elf.isLittleEndian(), littleendian, package_qa_clean_path(path,d, name))) 366*4882a593Smuzhiyun 367*4882a593SmuzhiyunQAPATHTEST[desktop] = "package_qa_check_desktop" 368*4882a593Smuzhiyundef package_qa_check_desktop(path, name, d, elf, messages): 369*4882a593Smuzhiyun """ 370*4882a593Smuzhiyun Run all desktop files through desktop-file-validate. 371*4882a593Smuzhiyun """ 372*4882a593Smuzhiyun if path.endswith(".desktop"): 373*4882a593Smuzhiyun desktop_file_validate = os.path.join(d.getVar('STAGING_BINDIR_NATIVE'),'desktop-file-validate') 374*4882a593Smuzhiyun output = os.popen("%s %s" % (desktop_file_validate, path)) 375*4882a593Smuzhiyun # This only produces output on errors 376*4882a593Smuzhiyun for l in output: 377*4882a593Smuzhiyun oe.qa.add_message(messages, "desktop", "Desktop file issue: " + l.strip()) 378*4882a593Smuzhiyun 379*4882a593SmuzhiyunQAPATHTEST[textrel] = "package_qa_textrel" 380*4882a593Smuzhiyundef package_qa_textrel(path, name, d, elf, messages): 381*4882a593Smuzhiyun """ 382*4882a593Smuzhiyun Check if the binary contains relocations in .text 383*4882a593Smuzhiyun """ 384*4882a593Smuzhiyun 385*4882a593Smuzhiyun if not elf: 386*4882a593Smuzhiyun return 387*4882a593Smuzhiyun 388*4882a593Smuzhiyun if os.path.islink(path): 389*4882a593Smuzhiyun return 390*4882a593Smuzhiyun 391*4882a593Smuzhiyun phdrs = elf.run_objdump("-p", d) 392*4882a593Smuzhiyun sane = True 393*4882a593Smuzhiyun 394*4882a593Smuzhiyun import re 395*4882a593Smuzhiyun textrel_re = re.compile(r"\s+TEXTREL\s+") 396*4882a593Smuzhiyun for line in phdrs.split("\n"): 397*4882a593Smuzhiyun if textrel_re.match(line): 398*4882a593Smuzhiyun sane = False 399*4882a593Smuzhiyun break 400*4882a593Smuzhiyun 401*4882a593Smuzhiyun if not sane: 402*4882a593Smuzhiyun path = package_qa_clean_path(path, d, name) 403*4882a593Smuzhiyun oe.qa.add_message(messages, "textrel", "%s: ELF binary %s has relocations in .text" % (name, path)) 404*4882a593Smuzhiyun 405*4882a593SmuzhiyunQAPATHTEST[ldflags] = "package_qa_hash_style" 406*4882a593Smuzhiyundef package_qa_hash_style(path, name, d, elf, messages): 407*4882a593Smuzhiyun """ 408*4882a593Smuzhiyun Check if the binary has the right hash style... 409*4882a593Smuzhiyun """ 410*4882a593Smuzhiyun 411*4882a593Smuzhiyun if not elf: 412*4882a593Smuzhiyun return 413*4882a593Smuzhiyun 414*4882a593Smuzhiyun if os.path.islink(path): 415*4882a593Smuzhiyun return 416*4882a593Smuzhiyun 417*4882a593Smuzhiyun gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS') 418*4882a593Smuzhiyun if not gnu_hash: 419*4882a593Smuzhiyun gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS') 420*4882a593Smuzhiyun if not gnu_hash: 421*4882a593Smuzhiyun return 422*4882a593Smuzhiyun 423*4882a593Smuzhiyun sane = False 424*4882a593Smuzhiyun has_syms = False 425*4882a593Smuzhiyun 426*4882a593Smuzhiyun phdrs = elf.run_objdump("-p", d) 427*4882a593Smuzhiyun 428*4882a593Smuzhiyun # If this binary has symbols, we expect it to have GNU_HASH too. 429*4882a593Smuzhiyun for line in phdrs.split("\n"): 430*4882a593Smuzhiyun if "SYMTAB" in line: 431*4882a593Smuzhiyun has_syms = True 432*4882a593Smuzhiyun if "GNU_HASH" in line or "MIPS_XHASH" in line: 433*4882a593Smuzhiyun sane = True 434*4882a593Smuzhiyun if ("[mips32]" in line or "[mips64]" in line) and d.getVar('TCLIBC') == "musl": 435*4882a593Smuzhiyun sane = True 436*4882a593Smuzhiyun if has_syms and not sane: 437*4882a593Smuzhiyun path = package_qa_clean_path(path, d, name) 438*4882a593Smuzhiyun oe.qa.add_message(messages, "ldflags", "File %s in package %s doesn't have GNU_HASH (didn't pass LDFLAGS?)" % (path, name)) 439*4882a593Smuzhiyun 440*4882a593Smuzhiyun 441*4882a593SmuzhiyunQAPATHTEST[buildpaths] = "package_qa_check_buildpaths" 442*4882a593Smuzhiyundef package_qa_check_buildpaths(path, name, d, elf, messages): 443*4882a593Smuzhiyun """ 444*4882a593Smuzhiyun Check for build paths inside target files and error if paths are not 445*4882a593Smuzhiyun explicitly ignored. 446*4882a593Smuzhiyun """ 447*4882a593Smuzhiyun import stat 448*4882a593Smuzhiyun # Ignore .debug files, not interesting 449*4882a593Smuzhiyun if path.find(".debug") != -1: 450*4882a593Smuzhiyun return 451*4882a593Smuzhiyun 452*4882a593Smuzhiyun # Ignore symlinks/devs/fifos 453*4882a593Smuzhiyun mode = os.lstat(path).st_mode 454*4882a593Smuzhiyun if stat.S_ISLNK(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode) or stat.S_ISCHR(mode) or stat.S_ISSOCK(mode): 455*4882a593Smuzhiyun return 456*4882a593Smuzhiyun 457*4882a593Smuzhiyun tmpdir = bytes(d.getVar('TMPDIR'), encoding="utf-8") 458*4882a593Smuzhiyun with open(path, 'rb') as f: 459*4882a593Smuzhiyun file_content = f.read() 460*4882a593Smuzhiyun if tmpdir in file_content: 461*4882a593Smuzhiyun trimmed = path.replace(os.path.join (d.getVar("PKGDEST"), name), "") 462*4882a593Smuzhiyun oe.qa.add_message(messages, "buildpaths", "File %s in package %s contains reference to TMPDIR" % (trimmed, name)) 463*4882a593Smuzhiyun 464*4882a593Smuzhiyun 465*4882a593SmuzhiyunQAPATHTEST[xorg-driver-abi] = "package_qa_check_xorg_driver_abi" 466*4882a593Smuzhiyundef package_qa_check_xorg_driver_abi(path, name, d, elf, messages): 467*4882a593Smuzhiyun """ 468*4882a593Smuzhiyun Check that all packages containing Xorg drivers have ABI dependencies 469*4882a593Smuzhiyun """ 470*4882a593Smuzhiyun 471*4882a593Smuzhiyun # Skip dev, dbg or nativesdk packages 472*4882a593Smuzhiyun if name.endswith("-dev") or name.endswith("-dbg") or name.startswith("nativesdk-"): 473*4882a593Smuzhiyun return 474*4882a593Smuzhiyun 475*4882a593Smuzhiyun driverdir = d.expand("${libdir}/xorg/modules/drivers/") 476*4882a593Smuzhiyun if driverdir in path and path.endswith(".so"): 477*4882a593Smuzhiyun mlprefix = d.getVar('MLPREFIX') or '' 478*4882a593Smuzhiyun for rdep in bb.utils.explode_deps(d.getVar('RDEPENDS:' + name) or ""): 479*4882a593Smuzhiyun if rdep.startswith("%sxorg-abi-" % mlprefix): 480*4882a593Smuzhiyun return 481*4882a593Smuzhiyun oe.qa.add_message(messages, "xorg-driver-abi", "Package %s contains Xorg driver (%s) but no xorg-abi- dependencies" % (name, os.path.basename(path))) 482*4882a593Smuzhiyun 483*4882a593SmuzhiyunQAPATHTEST[infodir] = "package_qa_check_infodir" 484*4882a593Smuzhiyundef package_qa_check_infodir(path, name, d, elf, messages): 485*4882a593Smuzhiyun """ 486*4882a593Smuzhiyun Check that /usr/share/info/dir isn't shipped in a particular package 487*4882a593Smuzhiyun """ 488*4882a593Smuzhiyun infodir = d.expand("${infodir}/dir") 489*4882a593Smuzhiyun 490*4882a593Smuzhiyun if infodir in path: 491*4882a593Smuzhiyun oe.qa.add_message(messages, "infodir", "The /usr/share/info/dir file is not meant to be shipped in a particular package.") 492*4882a593Smuzhiyun 493*4882a593SmuzhiyunQAPATHTEST[symlink-to-sysroot] = "package_qa_check_symlink_to_sysroot" 494*4882a593Smuzhiyundef package_qa_check_symlink_to_sysroot(path, name, d, elf, messages): 495*4882a593Smuzhiyun """ 496*4882a593Smuzhiyun Check that the package doesn't contain any absolute symlinks to the sysroot. 497*4882a593Smuzhiyun """ 498*4882a593Smuzhiyun if os.path.islink(path): 499*4882a593Smuzhiyun target = os.readlink(path) 500*4882a593Smuzhiyun if os.path.isabs(target): 501*4882a593Smuzhiyun tmpdir = d.getVar('TMPDIR') 502*4882a593Smuzhiyun if target.startswith(tmpdir): 503*4882a593Smuzhiyun trimmed = path.replace(os.path.join (d.getVar("PKGDEST"), name), "") 504*4882a593Smuzhiyun oe.qa.add_message(messages, "symlink-to-sysroot", "Symlink %s in %s points to TMPDIR" % (trimmed, name)) 505*4882a593Smuzhiyun 506*4882a593Smuzhiyun# Check license variables 507*4882a593Smuzhiyundo_populate_lic[postfuncs] += "populate_lic_qa_checksum" 508*4882a593Smuzhiyunpython populate_lic_qa_checksum() { 509*4882a593Smuzhiyun """ 510*4882a593Smuzhiyun Check for changes in the license files. 511*4882a593Smuzhiyun """ 512*4882a593Smuzhiyun 513*4882a593Smuzhiyun lic_files = d.getVar('LIC_FILES_CHKSUM') or '' 514*4882a593Smuzhiyun lic = d.getVar('LICENSE') 515*4882a593Smuzhiyun pn = d.getVar('PN') 516*4882a593Smuzhiyun 517*4882a593Smuzhiyun if lic == "CLOSED": 518*4882a593Smuzhiyun return 519*4882a593Smuzhiyun 520*4882a593Smuzhiyun if not lic_files and d.getVar('SRC_URI'): 521*4882a593Smuzhiyun oe.qa.handle_error("license-checksum", pn + ": Recipe file fetches files and does not have license file information (LIC_FILES_CHKSUM)", d) 522*4882a593Smuzhiyun 523*4882a593Smuzhiyun srcdir = d.getVar('S') 524*4882a593Smuzhiyun corebase_licensefile = d.getVar('COREBASE') + "/LICENSE" 525*4882a593Smuzhiyun for url in lic_files.split(): 526*4882a593Smuzhiyun try: 527*4882a593Smuzhiyun (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(url) 528*4882a593Smuzhiyun except bb.fetch.MalformedUrl: 529*4882a593Smuzhiyun oe.qa.handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM contains an invalid URL: " + url, d) 530*4882a593Smuzhiyun continue 531*4882a593Smuzhiyun srclicfile = os.path.join(srcdir, path) 532*4882a593Smuzhiyun if not os.path.isfile(srclicfile): 533*4882a593Smuzhiyun oe.qa.handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM points to an invalid file: " + srclicfile, d) 534*4882a593Smuzhiyun continue 535*4882a593Smuzhiyun 536*4882a593Smuzhiyun if (srclicfile == corebase_licensefile): 537*4882a593Smuzhiyun bb.warn("${COREBASE}/LICENSE is not a valid license file, please use '${COMMON_LICENSE_DIR}/MIT' for a MIT License file in LIC_FILES_CHKSUM. This will become an error in the future") 538*4882a593Smuzhiyun 539*4882a593Smuzhiyun recipemd5 = parm.get('md5', '') 540*4882a593Smuzhiyun beginline, endline = 0, 0 541*4882a593Smuzhiyun if 'beginline' in parm: 542*4882a593Smuzhiyun beginline = int(parm['beginline']) 543*4882a593Smuzhiyun if 'endline' in parm: 544*4882a593Smuzhiyun endline = int(parm['endline']) 545*4882a593Smuzhiyun 546*4882a593Smuzhiyun if (not beginline) and (not endline): 547*4882a593Smuzhiyun md5chksum = bb.utils.md5_file(srclicfile) 548*4882a593Smuzhiyun with open(srclicfile, 'r', errors='replace') as f: 549*4882a593Smuzhiyun license = f.read().splitlines() 550*4882a593Smuzhiyun else: 551*4882a593Smuzhiyun with open(srclicfile, 'rb') as f: 552*4882a593Smuzhiyun import hashlib 553*4882a593Smuzhiyun lineno = 0 554*4882a593Smuzhiyun license = [] 555*4882a593Smuzhiyun try: 556*4882a593Smuzhiyun m = hashlib.new('MD5', usedforsecurity=False) 557*4882a593Smuzhiyun except TypeError: 558*4882a593Smuzhiyun m = hashlib.new('MD5') 559*4882a593Smuzhiyun for line in f: 560*4882a593Smuzhiyun lineno += 1 561*4882a593Smuzhiyun if (lineno >= beginline): 562*4882a593Smuzhiyun if ((lineno <= endline) or not endline): 563*4882a593Smuzhiyun m.update(line) 564*4882a593Smuzhiyun license.append(line.decode('utf-8', errors='replace').rstrip()) 565*4882a593Smuzhiyun else: 566*4882a593Smuzhiyun break 567*4882a593Smuzhiyun md5chksum = m.hexdigest() 568*4882a593Smuzhiyun if recipemd5 == md5chksum: 569*4882a593Smuzhiyun bb.note (pn + ": md5 checksum matched for ", url) 570*4882a593Smuzhiyun else: 571*4882a593Smuzhiyun if recipemd5: 572*4882a593Smuzhiyun msg = pn + ": The LIC_FILES_CHKSUM does not match for " + url 573*4882a593Smuzhiyun msg = msg + "\n" + pn + ": The new md5 checksum is " + md5chksum 574*4882a593Smuzhiyun max_lines = int(d.getVar('QA_MAX_LICENSE_LINES') or 20) 575*4882a593Smuzhiyun if not license or license[-1] != '': 576*4882a593Smuzhiyun # Ensure that our license text ends with a line break 577*4882a593Smuzhiyun # (will be added with join() below). 578*4882a593Smuzhiyun license.append('') 579*4882a593Smuzhiyun remove = len(license) - max_lines 580*4882a593Smuzhiyun if remove > 0: 581*4882a593Smuzhiyun start = max_lines // 2 582*4882a593Smuzhiyun end = start + remove - 1 583*4882a593Smuzhiyun del license[start:end] 584*4882a593Smuzhiyun license.insert(start, '...') 585*4882a593Smuzhiyun msg = msg + "\n" + pn + ": Here is the selected license text:" + \ 586*4882a593Smuzhiyun "\n" + \ 587*4882a593Smuzhiyun "{:v^70}".format(" beginline=%d " % beginline if beginline else "") + \ 588*4882a593Smuzhiyun "\n" + "\n".join(license) + \ 589*4882a593Smuzhiyun "{:^^70}".format(" endline=%d " % endline if endline else "") 590*4882a593Smuzhiyun if beginline: 591*4882a593Smuzhiyun if endline: 592*4882a593Smuzhiyun srcfiledesc = "%s (lines %d through to %d)" % (srclicfile, beginline, endline) 593*4882a593Smuzhiyun else: 594*4882a593Smuzhiyun srcfiledesc = "%s (beginning on line %d)" % (srclicfile, beginline) 595*4882a593Smuzhiyun elif endline: 596*4882a593Smuzhiyun srcfiledesc = "%s (ending on line %d)" % (srclicfile, endline) 597*4882a593Smuzhiyun else: 598*4882a593Smuzhiyun srcfiledesc = srclicfile 599*4882a593Smuzhiyun msg = msg + "\n" + pn + ": Check if the license information has changed in %s to verify that the LICENSE value \"%s\" remains valid" % (srcfiledesc, lic) 600*4882a593Smuzhiyun 601*4882a593Smuzhiyun else: 602*4882a593Smuzhiyun msg = pn + ": LIC_FILES_CHKSUM is not specified for " + url 603*4882a593Smuzhiyun msg = msg + "\n" + pn + ": The md5 checksum is " + md5chksum 604*4882a593Smuzhiyun oe.qa.handle_error("license-checksum", msg, d) 605*4882a593Smuzhiyun 606*4882a593Smuzhiyun oe.qa.exit_if_errors(d) 607*4882a593Smuzhiyun} 608*4882a593Smuzhiyun 609*4882a593Smuzhiyundef qa_check_staged(path,d): 610*4882a593Smuzhiyun """ 611*4882a593Smuzhiyun Check staged la and pc files for common problems like references to the work 612*4882a593Smuzhiyun directory. 613*4882a593Smuzhiyun 614*4882a593Smuzhiyun As this is run after every stage we should be able to find the one 615*4882a593Smuzhiyun responsible for the errors easily even if we look at every .pc and .la file. 616*4882a593Smuzhiyun """ 617*4882a593Smuzhiyun 618*4882a593Smuzhiyun tmpdir = d.getVar('TMPDIR') 619*4882a593Smuzhiyun workdir = os.path.join(tmpdir, "work") 620*4882a593Smuzhiyun recipesysroot = d.getVar("RECIPE_SYSROOT") 621*4882a593Smuzhiyun 622*4882a593Smuzhiyun if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d): 623*4882a593Smuzhiyun pkgconfigcheck = workdir 624*4882a593Smuzhiyun else: 625*4882a593Smuzhiyun pkgconfigcheck = tmpdir 626*4882a593Smuzhiyun 627*4882a593Smuzhiyun skip = (d.getVar('INSANE_SKIP') or "").split() 628*4882a593Smuzhiyun skip_la = False 629*4882a593Smuzhiyun if 'la' in skip: 630*4882a593Smuzhiyun bb.note("Recipe %s skipping qa checking: la" % d.getVar('PN')) 631*4882a593Smuzhiyun skip_la = True 632*4882a593Smuzhiyun 633*4882a593Smuzhiyun skip_pkgconfig = False 634*4882a593Smuzhiyun if 'pkgconfig' in skip: 635*4882a593Smuzhiyun bb.note("Recipe %s skipping qa checking: pkgconfig" % d.getVar('PN')) 636*4882a593Smuzhiyun skip_pkgconfig = True 637*4882a593Smuzhiyun 638*4882a593Smuzhiyun # find all .la and .pc files 639*4882a593Smuzhiyun # read the content 640*4882a593Smuzhiyun # and check for stuff that looks wrong 641*4882a593Smuzhiyun for root, dirs, files in os.walk(path): 642*4882a593Smuzhiyun for file in files: 643*4882a593Smuzhiyun path = os.path.join(root,file) 644*4882a593Smuzhiyun if file.endswith(".la") and not skip_la: 645*4882a593Smuzhiyun with open(path) as f: 646*4882a593Smuzhiyun file_content = f.read() 647*4882a593Smuzhiyun file_content = file_content.replace(recipesysroot, "") 648*4882a593Smuzhiyun if workdir in file_content: 649*4882a593Smuzhiyun error_msg = "%s failed sanity test (workdir) in path %s" % (file,root) 650*4882a593Smuzhiyun oe.qa.handle_error("la", error_msg, d) 651*4882a593Smuzhiyun elif file.endswith(".pc") and not skip_pkgconfig: 652*4882a593Smuzhiyun with open(path) as f: 653*4882a593Smuzhiyun file_content = f.read() 654*4882a593Smuzhiyun file_content = file_content.replace(recipesysroot, "") 655*4882a593Smuzhiyun if pkgconfigcheck in file_content: 656*4882a593Smuzhiyun error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root) 657*4882a593Smuzhiyun oe.qa.handle_error("pkgconfig", error_msg, d) 658*4882a593Smuzhiyun 659*4882a593Smuzhiyun# Run all package-wide warnfuncs and errorfuncs 660*4882a593Smuzhiyundef package_qa_package(warnfuncs, errorfuncs, package, d): 661*4882a593Smuzhiyun warnings = {} 662*4882a593Smuzhiyun errors = {} 663*4882a593Smuzhiyun 664*4882a593Smuzhiyun for func in warnfuncs: 665*4882a593Smuzhiyun func(package, d, warnings) 666*4882a593Smuzhiyun for func in errorfuncs: 667*4882a593Smuzhiyun func(package, d, errors) 668*4882a593Smuzhiyun 669*4882a593Smuzhiyun for w in warnings: 670*4882a593Smuzhiyun oe.qa.handle_error(w, warnings[w], d) 671*4882a593Smuzhiyun for e in errors: 672*4882a593Smuzhiyun oe.qa.handle_error(e, errors[e], d) 673*4882a593Smuzhiyun 674*4882a593Smuzhiyun return len(errors) == 0 675*4882a593Smuzhiyun 676*4882a593Smuzhiyun# Run all recipe-wide warnfuncs and errorfuncs 677*4882a593Smuzhiyundef package_qa_recipe(warnfuncs, errorfuncs, pn, d): 678*4882a593Smuzhiyun warnings = {} 679*4882a593Smuzhiyun errors = {} 680*4882a593Smuzhiyun 681*4882a593Smuzhiyun for func in warnfuncs: 682*4882a593Smuzhiyun func(pn, d, warnings) 683*4882a593Smuzhiyun for func in errorfuncs: 684*4882a593Smuzhiyun func(pn, d, errors) 685*4882a593Smuzhiyun 686*4882a593Smuzhiyun for w in warnings: 687*4882a593Smuzhiyun oe.qa.handle_error(w, warnings[w], d) 688*4882a593Smuzhiyun for e in errors: 689*4882a593Smuzhiyun oe.qa.handle_error(e, errors[e], d) 690*4882a593Smuzhiyun 691*4882a593Smuzhiyun return len(errors) == 0 692*4882a593Smuzhiyun 693*4882a593Smuzhiyundef prepopulate_objdump_p(elf, d): 694*4882a593Smuzhiyun output = elf.run_objdump("-p", d) 695*4882a593Smuzhiyun return (elf.name, output) 696*4882a593Smuzhiyun 697*4882a593Smuzhiyun# Walk over all files in a directory and call func 698*4882a593Smuzhiyundef package_qa_walk(warnfuncs, errorfuncs, package, d): 699*4882a593Smuzhiyun #if this will throw an exception, then fix the dict above 700*4882a593Smuzhiyun target_os = d.getVar('HOST_OS') 701*4882a593Smuzhiyun target_arch = d.getVar('HOST_ARCH') 702*4882a593Smuzhiyun 703*4882a593Smuzhiyun warnings = {} 704*4882a593Smuzhiyun errors = {} 705*4882a593Smuzhiyun elves = {} 706*4882a593Smuzhiyun for path in pkgfiles[package]: 707*4882a593Smuzhiyun elf = None 708*4882a593Smuzhiyun if os.path.isfile(path): 709*4882a593Smuzhiyun elf = oe.qa.ELFFile(path) 710*4882a593Smuzhiyun try: 711*4882a593Smuzhiyun elf.open() 712*4882a593Smuzhiyun elf.close() 713*4882a593Smuzhiyun except oe.qa.NotELFFileError: 714*4882a593Smuzhiyun elf = None 715*4882a593Smuzhiyun if elf: 716*4882a593Smuzhiyun elves[path] = elf 717*4882a593Smuzhiyun 718*4882a593Smuzhiyun results = oe.utils.multiprocess_launch(prepopulate_objdump_p, elves.values(), d, extraargs=(d,)) 719*4882a593Smuzhiyun for item in results: 720*4882a593Smuzhiyun elves[item[0]].set_objdump("-p", item[1]) 721*4882a593Smuzhiyun 722*4882a593Smuzhiyun for path in pkgfiles[package]: 723*4882a593Smuzhiyun if path in elves: 724*4882a593Smuzhiyun elves[path].open() 725*4882a593Smuzhiyun for func in warnfuncs: 726*4882a593Smuzhiyun func(path, package, d, elves.get(path), warnings) 727*4882a593Smuzhiyun for func in errorfuncs: 728*4882a593Smuzhiyun func(path, package, d, elves.get(path), errors) 729*4882a593Smuzhiyun if path in elves: 730*4882a593Smuzhiyun elves[path].close() 731*4882a593Smuzhiyun 732*4882a593Smuzhiyun for w in warnings: 733*4882a593Smuzhiyun oe.qa.handle_error(w, warnings[w], d) 734*4882a593Smuzhiyun for e in errors: 735*4882a593Smuzhiyun oe.qa.handle_error(e, errors[e], d) 736*4882a593Smuzhiyun 737*4882a593Smuzhiyundef package_qa_check_rdepends(pkg, pkgdest, skip, taskdeps, packages, d): 738*4882a593Smuzhiyun # Don't do this check for kernel/module recipes, there aren't too many debug/development 739*4882a593Smuzhiyun # packages and you can get false positives e.g. on kernel-module-lirc-dev 740*4882a593Smuzhiyun if bb.data.inherits_class("kernel", d) or bb.data.inherits_class("module-base", d): 741*4882a593Smuzhiyun return 742*4882a593Smuzhiyun 743*4882a593Smuzhiyun if not "-dbg" in pkg and not "packagegroup-" in pkg and not "-image" in pkg: 744*4882a593Smuzhiyun localdata = bb.data.createCopy(d) 745*4882a593Smuzhiyun localdata.setVar('OVERRIDES', localdata.getVar('OVERRIDES') + ':' + pkg) 746*4882a593Smuzhiyun 747*4882a593Smuzhiyun # Now check the RDEPENDS 748*4882a593Smuzhiyun rdepends = bb.utils.explode_deps(localdata.getVar('RDEPENDS') or "") 749*4882a593Smuzhiyun 750*4882a593Smuzhiyun # Now do the sanity check!!! 751*4882a593Smuzhiyun if "build-deps" not in skip: 752*4882a593Smuzhiyun for rdepend in rdepends: 753*4882a593Smuzhiyun if "-dbg" in rdepend and "debug-deps" not in skip: 754*4882a593Smuzhiyun error_msg = "%s rdepends on %s" % (pkg,rdepend) 755*4882a593Smuzhiyun oe.qa.handle_error("debug-deps", error_msg, d) 756*4882a593Smuzhiyun if (not "-dev" in pkg and not "-staticdev" in pkg) and rdepend.endswith("-dev") and "dev-deps" not in skip: 757*4882a593Smuzhiyun error_msg = "%s rdepends on %s" % (pkg, rdepend) 758*4882a593Smuzhiyun oe.qa.handle_error("dev-deps", error_msg, d) 759*4882a593Smuzhiyun if rdepend not in packages: 760*4882a593Smuzhiyun rdep_data = oe.packagedata.read_subpkgdata(rdepend, d) 761*4882a593Smuzhiyun if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps: 762*4882a593Smuzhiyun continue 763*4882a593Smuzhiyun if not rdep_data or not 'PN' in rdep_data: 764*4882a593Smuzhiyun pkgdata_dir = d.getVar("PKGDATA_DIR") 765*4882a593Smuzhiyun try: 766*4882a593Smuzhiyun possibles = os.listdir("%s/runtime-rprovides/%s/" % (pkgdata_dir, rdepend)) 767*4882a593Smuzhiyun except OSError: 768*4882a593Smuzhiyun possibles = [] 769*4882a593Smuzhiyun for p in possibles: 770*4882a593Smuzhiyun rdep_data = oe.packagedata.read_subpkgdata(p, d) 771*4882a593Smuzhiyun if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps: 772*4882a593Smuzhiyun break 773*4882a593Smuzhiyun if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps: 774*4882a593Smuzhiyun continue 775*4882a593Smuzhiyun if rdep_data and 'PN' in rdep_data: 776*4882a593Smuzhiyun error_msg = "%s rdepends on %s, but it isn't a build dependency, missing %s in DEPENDS or PACKAGECONFIG?" % (pkg, rdepend, rdep_data['PN']) 777*4882a593Smuzhiyun else: 778*4882a593Smuzhiyun error_msg = "%s rdepends on %s, but it isn't a build dependency?" % (pkg, rdepend) 779*4882a593Smuzhiyun oe.qa.handle_error("build-deps", error_msg, d) 780*4882a593Smuzhiyun 781*4882a593Smuzhiyun if "file-rdeps" not in skip: 782*4882a593Smuzhiyun ignored_file_rdeps = set(['/bin/sh', '/usr/bin/env', 'rtld(GNU_HASH)']) 783*4882a593Smuzhiyun if bb.data.inherits_class('nativesdk', d): 784*4882a593Smuzhiyun ignored_file_rdeps |= set(['/bin/bash', '/usr/bin/perl', 'perl']) 785*4882a593Smuzhiyun # For Saving the FILERDEPENDS 786*4882a593Smuzhiyun filerdepends = {} 787*4882a593Smuzhiyun rdep_data = oe.packagedata.read_subpkgdata(pkg, d) 788*4882a593Smuzhiyun for key in rdep_data: 789*4882a593Smuzhiyun if key.startswith("FILERDEPENDS:"): 790*4882a593Smuzhiyun for subkey in bb.utils.explode_deps(rdep_data[key]): 791*4882a593Smuzhiyun if subkey not in ignored_file_rdeps and \ 792*4882a593Smuzhiyun not subkey.startswith('perl('): 793*4882a593Smuzhiyun # We already know it starts with FILERDEPENDS_ 794*4882a593Smuzhiyun filerdepends[subkey] = key[13:] 795*4882a593Smuzhiyun 796*4882a593Smuzhiyun if filerdepends: 797*4882a593Smuzhiyun done = rdepends[:] 798*4882a593Smuzhiyun # Add the rprovides of itself 799*4882a593Smuzhiyun if pkg not in done: 800*4882a593Smuzhiyun done.insert(0, pkg) 801*4882a593Smuzhiyun 802*4882a593Smuzhiyun # The python is not a package, but python-core provides it, so 803*4882a593Smuzhiyun # skip checking /usr/bin/python if python is in the rdeps, in 804*4882a593Smuzhiyun # case there is a RDEPENDS:pkg = "python" in the recipe. 805*4882a593Smuzhiyun for py in [ d.getVar('MLPREFIX') + "python", "python" ]: 806*4882a593Smuzhiyun if py in done: 807*4882a593Smuzhiyun filerdepends.pop("/usr/bin/python",None) 808*4882a593Smuzhiyun done.remove(py) 809*4882a593Smuzhiyun for rdep in done: 810*4882a593Smuzhiyun # The file dependencies may contain package names, e.g., 811*4882a593Smuzhiyun # perl 812*4882a593Smuzhiyun filerdepends.pop(rdep,None) 813*4882a593Smuzhiyun 814*4882a593Smuzhiyun # For Saving the FILERPROVIDES, RPROVIDES and FILES_INFO 815*4882a593Smuzhiyun rdep_data = oe.packagedata.read_subpkgdata(rdep, d) 816*4882a593Smuzhiyun for key in rdep_data: 817*4882a593Smuzhiyun if key.startswith("FILERPROVIDES:") or key.startswith("RPROVIDES:"): 818*4882a593Smuzhiyun for subkey in bb.utils.explode_deps(rdep_data[key]): 819*4882a593Smuzhiyun filerdepends.pop(subkey,None) 820*4882a593Smuzhiyun # Add the files list to the rprovides 821*4882a593Smuzhiyun if key.startswith("FILES_INFO:"): 822*4882a593Smuzhiyun # Use eval() to make it as a dict 823*4882a593Smuzhiyun for subkey in eval(rdep_data[key]): 824*4882a593Smuzhiyun filerdepends.pop(subkey,None) 825*4882a593Smuzhiyun if not filerdepends: 826*4882a593Smuzhiyun # Break if all the file rdepends are met 827*4882a593Smuzhiyun break 828*4882a593Smuzhiyun if filerdepends: 829*4882a593Smuzhiyun for key in filerdepends: 830*4882a593Smuzhiyun error_msg = "%s contained in package %s requires %s, but no providers found in RDEPENDS:%s?" % \ 831*4882a593Smuzhiyun (filerdepends[key].replace(":%s" % pkg, "").replace("@underscore@", "_"), pkg, key, pkg) 832*4882a593Smuzhiyun oe.qa.handle_error("file-rdeps", error_msg, d) 833*4882a593Smuzhiyunpackage_qa_check_rdepends[vardepsexclude] = "OVERRIDES" 834*4882a593Smuzhiyun 835*4882a593Smuzhiyundef package_qa_check_deps(pkg, pkgdest, d): 836*4882a593Smuzhiyun 837*4882a593Smuzhiyun localdata = bb.data.createCopy(d) 838*4882a593Smuzhiyun localdata.setVar('OVERRIDES', pkg) 839*4882a593Smuzhiyun 840*4882a593Smuzhiyun def check_valid_deps(var): 841*4882a593Smuzhiyun try: 842*4882a593Smuzhiyun rvar = bb.utils.explode_dep_versions2(localdata.getVar(var) or "") 843*4882a593Smuzhiyun except ValueError as e: 844*4882a593Smuzhiyun bb.fatal("%s:%s: %s" % (var, pkg, e)) 845*4882a593Smuzhiyun for dep in rvar: 846*4882a593Smuzhiyun for v in rvar[dep]: 847*4882a593Smuzhiyun if v and not v.startswith(('< ', '= ', '> ', '<= ', '>=')): 848*4882a593Smuzhiyun error_msg = "%s:%s is invalid: %s (%s) only comparisons <, =, >, <=, and >= are allowed" % (var, pkg, dep, v) 849*4882a593Smuzhiyun oe.qa.handle_error("dep-cmp", error_msg, d) 850*4882a593Smuzhiyun 851*4882a593Smuzhiyun check_valid_deps('RDEPENDS') 852*4882a593Smuzhiyun check_valid_deps('RRECOMMENDS') 853*4882a593Smuzhiyun check_valid_deps('RSUGGESTS') 854*4882a593Smuzhiyun check_valid_deps('RPROVIDES') 855*4882a593Smuzhiyun check_valid_deps('RREPLACES') 856*4882a593Smuzhiyun check_valid_deps('RCONFLICTS') 857*4882a593Smuzhiyun 858*4882a593SmuzhiyunQAPKGTEST[usrmerge] = "package_qa_check_usrmerge" 859*4882a593Smuzhiyundef package_qa_check_usrmerge(pkg, d, messages): 860*4882a593Smuzhiyun 861*4882a593Smuzhiyun pkgdest = d.getVar('PKGDEST') 862*4882a593Smuzhiyun pkg_dir = pkgdest + os.sep + pkg + os.sep 863*4882a593Smuzhiyun merged_dirs = ['bin', 'sbin', 'lib'] + d.getVar('MULTILIB_VARIANTS').split() 864*4882a593Smuzhiyun for f in merged_dirs: 865*4882a593Smuzhiyun if os.path.exists(pkg_dir + f) and not os.path.islink(pkg_dir + f): 866*4882a593Smuzhiyun msg = "%s package is not obeying usrmerge distro feature. /%s should be relocated to /usr." % (pkg, f) 867*4882a593Smuzhiyun oe.qa.add_message(messages, "usrmerge", msg) 868*4882a593Smuzhiyun return False 869*4882a593Smuzhiyun return True 870*4882a593Smuzhiyun 871*4882a593SmuzhiyunQAPKGTEST[perllocalpod] = "package_qa_check_perllocalpod" 872*4882a593Smuzhiyundef package_qa_check_perllocalpod(pkg, d, messages): 873*4882a593Smuzhiyun """ 874*4882a593Smuzhiyun Check that the recipe didn't ship a perlocal.pod file, which shouldn't be 875*4882a593Smuzhiyun installed in a distribution package. cpan.bbclass sets NO_PERLLOCAL=1 to 876*4882a593Smuzhiyun handle this for most recipes. 877*4882a593Smuzhiyun """ 878*4882a593Smuzhiyun import glob 879*4882a593Smuzhiyun pkgd = oe.path.join(d.getVar('PKGDEST'), pkg) 880*4882a593Smuzhiyun podpath = oe.path.join(pkgd, d.getVar("libdir"), "perl*", "*", "*", "perllocal.pod") 881*4882a593Smuzhiyun 882*4882a593Smuzhiyun matches = glob.glob(podpath) 883*4882a593Smuzhiyun if matches: 884*4882a593Smuzhiyun matches = [package_qa_clean_path(path, d, pkg) for path in matches] 885*4882a593Smuzhiyun msg = "%s contains perllocal.pod (%s), should not be installed" % (pkg, " ".join(matches)) 886*4882a593Smuzhiyun oe.qa.add_message(messages, "perllocalpod", msg) 887*4882a593Smuzhiyun 888*4882a593SmuzhiyunQAPKGTEST[expanded-d] = "package_qa_check_expanded_d" 889*4882a593Smuzhiyundef package_qa_check_expanded_d(package, d, messages): 890*4882a593Smuzhiyun """ 891*4882a593Smuzhiyun Check for the expanded D (${D}) value in pkg_* and FILES 892*4882a593Smuzhiyun variables, warn the user to use it correctly. 893*4882a593Smuzhiyun """ 894*4882a593Smuzhiyun sane = True 895*4882a593Smuzhiyun expanded_d = d.getVar('D') 896*4882a593Smuzhiyun 897*4882a593Smuzhiyun for var in 'FILES','pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm': 898*4882a593Smuzhiyun bbvar = d.getVar(var + ":" + package) or "" 899*4882a593Smuzhiyun if expanded_d in bbvar: 900*4882a593Smuzhiyun if var == 'FILES': 901*4882a593Smuzhiyun oe.qa.add_message(messages, "expanded-d", "FILES in %s recipe should not contain the ${D} variable as it references the local build directory not the target filesystem, best solution is to remove the ${D} reference" % package) 902*4882a593Smuzhiyun sane = False 903*4882a593Smuzhiyun else: 904*4882a593Smuzhiyun oe.qa.add_message(messages, "expanded-d", "%s in %s recipe contains ${D}, it should be replaced by $D instead" % (var, package)) 905*4882a593Smuzhiyun sane = False 906*4882a593Smuzhiyun return sane 907*4882a593Smuzhiyun 908*4882a593SmuzhiyunQAPKGTEST[unlisted-pkg-lics] = "package_qa_check_unlisted_pkg_lics" 909*4882a593Smuzhiyundef package_qa_check_unlisted_pkg_lics(package, d, messages): 910*4882a593Smuzhiyun """ 911*4882a593Smuzhiyun Check that all licenses for a package are among the licenses for the recipe. 912*4882a593Smuzhiyun """ 913*4882a593Smuzhiyun pkg_lics = d.getVar('LICENSE:' + package) 914*4882a593Smuzhiyun if not pkg_lics: 915*4882a593Smuzhiyun return True 916*4882a593Smuzhiyun 917*4882a593Smuzhiyun recipe_lics_set = oe.license.list_licenses(d.getVar('LICENSE')) 918*4882a593Smuzhiyun package_lics = oe.license.list_licenses(pkg_lics) 919*4882a593Smuzhiyun unlisted = package_lics - recipe_lics_set 920*4882a593Smuzhiyun if unlisted: 921*4882a593Smuzhiyun oe.qa.add_message(messages, "unlisted-pkg-lics", 922*4882a593Smuzhiyun "LICENSE:%s includes licenses (%s) that are not " 923*4882a593Smuzhiyun "listed in LICENSE" % (package, ' '.join(unlisted))) 924*4882a593Smuzhiyun return False 925*4882a593Smuzhiyun obsolete = set(oe.license.obsolete_license_list()) & package_lics - recipe_lics_set 926*4882a593Smuzhiyun if obsolete: 927*4882a593Smuzhiyun oe.qa.add_message(messages, "obsolete-license", 928*4882a593Smuzhiyun "LICENSE:%s includes obsolete licenses %s" % (package, ' '.join(obsolete))) 929*4882a593Smuzhiyun return False 930*4882a593Smuzhiyun return True 931*4882a593Smuzhiyun 932*4882a593SmuzhiyunQAPKGTEST[empty-dirs] = "package_qa_check_empty_dirs" 933*4882a593Smuzhiyundef package_qa_check_empty_dirs(pkg, d, messages): 934*4882a593Smuzhiyun """ 935*4882a593Smuzhiyun Check for the existence of files in directories that are expected to be 936*4882a593Smuzhiyun empty. 937*4882a593Smuzhiyun """ 938*4882a593Smuzhiyun 939*4882a593Smuzhiyun pkgd = oe.path.join(d.getVar('PKGDEST'), pkg) 940*4882a593Smuzhiyun for dir in (d.getVar('QA_EMPTY_DIRS') or "").split(): 941*4882a593Smuzhiyun empty_dir = oe.path.join(pkgd, dir) 942*4882a593Smuzhiyun if os.path.exists(empty_dir) and os.listdir(empty_dir): 943*4882a593Smuzhiyun recommendation = (d.getVar('QA_EMPTY_DIRS_RECOMMENDATION:' + dir) or 944*4882a593Smuzhiyun "but it is expected to be empty") 945*4882a593Smuzhiyun msg = "%s installs files in %s, %s" % (pkg, dir, recommendation) 946*4882a593Smuzhiyun oe.qa.add_message(messages, "empty-dirs", msg) 947*4882a593Smuzhiyun 948*4882a593Smuzhiyundef package_qa_check_encoding(keys, encode, d): 949*4882a593Smuzhiyun def check_encoding(key, enc): 950*4882a593Smuzhiyun sane = True 951*4882a593Smuzhiyun value = d.getVar(key) 952*4882a593Smuzhiyun if value: 953*4882a593Smuzhiyun try: 954*4882a593Smuzhiyun s = value.encode(enc) 955*4882a593Smuzhiyun except UnicodeDecodeError as e: 956*4882a593Smuzhiyun error_msg = "%s has non %s characters" % (key,enc) 957*4882a593Smuzhiyun sane = False 958*4882a593Smuzhiyun oe.qa.handle_error("invalid-chars", error_msg, d) 959*4882a593Smuzhiyun return sane 960*4882a593Smuzhiyun 961*4882a593Smuzhiyun for key in keys: 962*4882a593Smuzhiyun sane = check_encoding(key, encode) 963*4882a593Smuzhiyun if not sane: 964*4882a593Smuzhiyun break 965*4882a593Smuzhiyun 966*4882a593SmuzhiyunHOST_USER_UID := "${@os.getuid()}" 967*4882a593SmuzhiyunHOST_USER_GID := "${@os.getgid()}" 968*4882a593Smuzhiyun 969*4882a593SmuzhiyunQAPATHTEST[host-user-contaminated] = "package_qa_check_host_user" 970*4882a593Smuzhiyundef package_qa_check_host_user(path, name, d, elf, messages): 971*4882a593Smuzhiyun """Check for paths outside of /home which are owned by the user running bitbake.""" 972*4882a593Smuzhiyun 973*4882a593Smuzhiyun if not os.path.lexists(path): 974*4882a593Smuzhiyun return 975*4882a593Smuzhiyun 976*4882a593Smuzhiyun dest = d.getVar('PKGDEST') 977*4882a593Smuzhiyun pn = d.getVar('PN') 978*4882a593Smuzhiyun home = os.path.join(dest, name, 'home') 979*4882a593Smuzhiyun if path == home or path.startswith(home + os.sep): 980*4882a593Smuzhiyun return 981*4882a593Smuzhiyun 982*4882a593Smuzhiyun try: 983*4882a593Smuzhiyun stat = os.lstat(path) 984*4882a593Smuzhiyun except OSError as exc: 985*4882a593Smuzhiyun import errno 986*4882a593Smuzhiyun if exc.errno != errno.ENOENT: 987*4882a593Smuzhiyun raise 988*4882a593Smuzhiyun else: 989*4882a593Smuzhiyun check_uid = int(d.getVar('HOST_USER_UID')) 990*4882a593Smuzhiyun if stat.st_uid == check_uid: 991*4882a593Smuzhiyun oe.qa.add_message(messages, "host-user-contaminated", "%s: %s is owned by uid %d, which is the same as the user running bitbake. This may be due to host contamination" % (pn, package_qa_clean_path(path, d, name), check_uid)) 992*4882a593Smuzhiyun return False 993*4882a593Smuzhiyun 994*4882a593Smuzhiyun check_gid = int(d.getVar('HOST_USER_GID')) 995*4882a593Smuzhiyun if stat.st_gid == check_gid: 996*4882a593Smuzhiyun oe.qa.add_message(messages, "host-user-contaminated", "%s: %s is owned by gid %d, which is the same as the user running bitbake. This may be due to host contamination" % (pn, package_qa_clean_path(path, d, name), check_gid)) 997*4882a593Smuzhiyun return False 998*4882a593Smuzhiyun return True 999*4882a593Smuzhiyun 1000*4882a593SmuzhiyunQARECIPETEST[unhandled-features-check] = "package_qa_check_unhandled_features_check" 1001*4882a593Smuzhiyundef package_qa_check_unhandled_features_check(pn, d, messages): 1002*4882a593Smuzhiyun if not bb.data.inherits_class('features_check', d): 1003*4882a593Smuzhiyun var_set = False 1004*4882a593Smuzhiyun for kind in ['DISTRO', 'MACHINE', 'COMBINED']: 1005*4882a593Smuzhiyun for var in ['ANY_OF_' + kind + '_FEATURES', 'REQUIRED_' + kind + '_FEATURES', 'CONFLICT_' + kind + '_FEATURES']: 1006*4882a593Smuzhiyun if d.getVar(var) is not None or d.hasOverrides(var): 1007*4882a593Smuzhiyun var_set = True 1008*4882a593Smuzhiyun if var_set: 1009*4882a593Smuzhiyun oe.qa.handle_error("unhandled-features-check", "%s: recipe doesn't inherit features_check" % pn, d) 1010*4882a593Smuzhiyun 1011*4882a593SmuzhiyunQARECIPETEST[missing-update-alternatives] = "package_qa_check_missing_update_alternatives" 1012*4882a593Smuzhiyundef package_qa_check_missing_update_alternatives(pn, d, messages): 1013*4882a593Smuzhiyun # Look at all packages and find out if any of those sets ALTERNATIVE variable 1014*4882a593Smuzhiyun # without inheriting update-alternatives class 1015*4882a593Smuzhiyun for pkg in (d.getVar('PACKAGES') or '').split(): 1016*4882a593Smuzhiyun if d.getVar('ALTERNATIVE:%s' % pkg) and not bb.data.inherits_class('update-alternatives', d): 1017*4882a593Smuzhiyun oe.qa.handle_error("missing-update-alternatives", "%s: recipe defines ALTERNATIVE:%s but doesn't inherit update-alternatives. This might fail during do_rootfs later!" % (pn, pkg), d) 1018*4882a593Smuzhiyun 1019*4882a593Smuzhiyun# The PACKAGE FUNC to scan each package 1020*4882a593Smuzhiyunpython do_package_qa () { 1021*4882a593Smuzhiyun import subprocess 1022*4882a593Smuzhiyun import oe.packagedata 1023*4882a593Smuzhiyun 1024*4882a593Smuzhiyun bb.note("DO PACKAGE QA") 1025*4882a593Smuzhiyun 1026*4882a593Smuzhiyun main_lic = d.getVar('LICENSE') 1027*4882a593Smuzhiyun 1028*4882a593Smuzhiyun # Check for obsolete license references in main LICENSE (packages are checked below for any changes) 1029*4882a593Smuzhiyun main_licenses = oe.license.list_licenses(d.getVar('LICENSE')) 1030*4882a593Smuzhiyun obsolete = set(oe.license.obsolete_license_list()) & main_licenses 1031*4882a593Smuzhiyun if obsolete: 1032*4882a593Smuzhiyun oe.qa.handle_error("obsolete-license", "Recipe LICENSE includes obsolete licenses %s" % ' '.join(obsolete), d) 1033*4882a593Smuzhiyun 1034*4882a593Smuzhiyun bb.build.exec_func("read_subpackage_metadata", d) 1035*4882a593Smuzhiyun 1036*4882a593Smuzhiyun # Check non UTF-8 characters on recipe's metadata 1037*4882a593Smuzhiyun package_qa_check_encoding(['DESCRIPTION', 'SUMMARY', 'LICENSE', 'SECTION'], 'utf-8', d) 1038*4882a593Smuzhiyun 1039*4882a593Smuzhiyun logdir = d.getVar('T') 1040*4882a593Smuzhiyun pn = d.getVar('PN') 1041*4882a593Smuzhiyun 1042*4882a593Smuzhiyun # Scan the packages... 1043*4882a593Smuzhiyun pkgdest = d.getVar('PKGDEST') 1044*4882a593Smuzhiyun packages = set((d.getVar('PACKAGES') or '').split()) 1045*4882a593Smuzhiyun 1046*4882a593Smuzhiyun global pkgfiles 1047*4882a593Smuzhiyun pkgfiles = {} 1048*4882a593Smuzhiyun for pkg in packages: 1049*4882a593Smuzhiyun pkgfiles[pkg] = [] 1050*4882a593Smuzhiyun pkgdir = os.path.join(pkgdest, pkg) 1051*4882a593Smuzhiyun for walkroot, dirs, files in os.walk(pkgdir): 1052*4882a593Smuzhiyun # Don't walk into top-level CONTROL or DEBIAN directories as these 1053*4882a593Smuzhiyun # are temporary directories created by do_package. 1054*4882a593Smuzhiyun if walkroot == pkgdir: 1055*4882a593Smuzhiyun for control in ("CONTROL", "DEBIAN"): 1056*4882a593Smuzhiyun if control in dirs: 1057*4882a593Smuzhiyun dirs.remove(control) 1058*4882a593Smuzhiyun for file in files: 1059*4882a593Smuzhiyun pkgfiles[pkg].append(os.path.join(walkroot, file)) 1060*4882a593Smuzhiyun 1061*4882a593Smuzhiyun # no packages should be scanned 1062*4882a593Smuzhiyun if not packages: 1063*4882a593Smuzhiyun return 1064*4882a593Smuzhiyun 1065*4882a593Smuzhiyun import re 1066*4882a593Smuzhiyun # The package name matches the [a-z0-9.+-]+ regular expression 1067*4882a593Smuzhiyun pkgname_pattern = re.compile(r"^[a-z0-9.+-]+$") 1068*4882a593Smuzhiyun 1069*4882a593Smuzhiyun taskdepdata = d.getVar("BB_TASKDEPDATA", False) 1070*4882a593Smuzhiyun taskdeps = set() 1071*4882a593Smuzhiyun for dep in taskdepdata: 1072*4882a593Smuzhiyun taskdeps.add(taskdepdata[dep][0]) 1073*4882a593Smuzhiyun 1074*4882a593Smuzhiyun def parse_test_matrix(matrix_name): 1075*4882a593Smuzhiyun testmatrix = d.getVarFlags(matrix_name) or {} 1076*4882a593Smuzhiyun g = globals() 1077*4882a593Smuzhiyun warnchecks = [] 1078*4882a593Smuzhiyun for w in (d.getVar("WARN_QA") or "").split(): 1079*4882a593Smuzhiyun if w in skip: 1080*4882a593Smuzhiyun continue 1081*4882a593Smuzhiyun if w in testmatrix and testmatrix[w] in g: 1082*4882a593Smuzhiyun warnchecks.append(g[testmatrix[w]]) 1083*4882a593Smuzhiyun 1084*4882a593Smuzhiyun errorchecks = [] 1085*4882a593Smuzhiyun for e in (d.getVar("ERROR_QA") or "").split(): 1086*4882a593Smuzhiyun if e in skip: 1087*4882a593Smuzhiyun continue 1088*4882a593Smuzhiyun if e in testmatrix and testmatrix[e] in g: 1089*4882a593Smuzhiyun errorchecks.append(g[testmatrix[e]]) 1090*4882a593Smuzhiyun return warnchecks, errorchecks 1091*4882a593Smuzhiyun 1092*4882a593Smuzhiyun for package in packages: 1093*4882a593Smuzhiyun skip = set((d.getVar('INSANE_SKIP') or "").split() + 1094*4882a593Smuzhiyun (d.getVar('INSANE_SKIP:' + package) or "").split()) 1095*4882a593Smuzhiyun if skip: 1096*4882a593Smuzhiyun bb.note("Package %s skipping QA tests: %s" % (package, str(skip))) 1097*4882a593Smuzhiyun 1098*4882a593Smuzhiyun bb.note("Checking Package: %s" % package) 1099*4882a593Smuzhiyun # Check package name 1100*4882a593Smuzhiyun if not pkgname_pattern.match(package): 1101*4882a593Smuzhiyun oe.qa.handle_error("pkgname", 1102*4882a593Smuzhiyun "%s doesn't match the [a-z0-9.+-]+ regex" % package, d) 1103*4882a593Smuzhiyun 1104*4882a593Smuzhiyun warn_checks, error_checks = parse_test_matrix("QAPATHTEST") 1105*4882a593Smuzhiyun package_qa_walk(warn_checks, error_checks, package, d) 1106*4882a593Smuzhiyun 1107*4882a593Smuzhiyun warn_checks, error_checks = parse_test_matrix("QAPKGTEST") 1108*4882a593Smuzhiyun package_qa_package(warn_checks, error_checks, package, d) 1109*4882a593Smuzhiyun 1110*4882a593Smuzhiyun package_qa_check_rdepends(package, pkgdest, skip, taskdeps, packages, d) 1111*4882a593Smuzhiyun package_qa_check_deps(package, pkgdest, d) 1112*4882a593Smuzhiyun 1113*4882a593Smuzhiyun warn_checks, error_checks = parse_test_matrix("QARECIPETEST") 1114*4882a593Smuzhiyun package_qa_recipe(warn_checks, error_checks, pn, d) 1115*4882a593Smuzhiyun 1116*4882a593Smuzhiyun if 'libdir' in d.getVar("ALL_QA").split(): 1117*4882a593Smuzhiyun package_qa_check_libdir(d) 1118*4882a593Smuzhiyun 1119*4882a593Smuzhiyun oe.qa.exit_if_errors(d) 1120*4882a593Smuzhiyun} 1121*4882a593Smuzhiyun 1122*4882a593Smuzhiyun# binutils is used for most checks, so need to set as dependency 1123*4882a593Smuzhiyun# POPULATESYSROOTDEPS is defined in staging class. 1124*4882a593Smuzhiyundo_package_qa[depends] += "${POPULATESYSROOTDEPS}" 1125*4882a593Smuzhiyundo_package_qa[vardeps] = "${@bb.utils.contains('ERROR_QA', 'empty-dirs', 'QA_EMPTY_DIRS', '', d)}" 1126*4882a593Smuzhiyundo_package_qa[vardepsexclude] = "BB_TASKDEPDATA" 1127*4882a593Smuzhiyundo_package_qa[rdeptask] = "do_packagedata" 1128*4882a593Smuzhiyunaddtask do_package_qa after do_packagedata do_package before do_build 1129*4882a593Smuzhiyun 1130*4882a593Smuzhiyun# Add the package specific INSANE_SKIPs to the sstate dependencies 1131*4882a593Smuzhiyunpython() { 1132*4882a593Smuzhiyun pkgs = (d.getVar('PACKAGES') or '').split() 1133*4882a593Smuzhiyun for pkg in pkgs: 1134*4882a593Smuzhiyun d.appendVarFlag("do_package_qa", "vardeps", " INSANE_SKIP:{}".format(pkg)) 1135*4882a593Smuzhiyun} 1136*4882a593Smuzhiyun 1137*4882a593SmuzhiyunSSTATETASKS += "do_package_qa" 1138*4882a593Smuzhiyundo_package_qa[sstate-inputdirs] = "" 1139*4882a593Smuzhiyundo_package_qa[sstate-outputdirs] = "" 1140*4882a593Smuzhiyunpython do_package_qa_setscene () { 1141*4882a593Smuzhiyun sstate_setscene(d) 1142*4882a593Smuzhiyun} 1143*4882a593Smuzhiyunaddtask do_package_qa_setscene 1144*4882a593Smuzhiyun 1145*4882a593Smuzhiyunpython do_qa_staging() { 1146*4882a593Smuzhiyun bb.note("QA checking staging") 1147*4882a593Smuzhiyun qa_check_staged(d.expand('${SYSROOT_DESTDIR}${libdir}'), d) 1148*4882a593Smuzhiyun oe.qa.exit_with_message_if_errors("QA staging was broken by the package built above", d) 1149*4882a593Smuzhiyun} 1150*4882a593Smuzhiyun 1151*4882a593Smuzhiyunpython do_qa_patch() { 1152*4882a593Smuzhiyun import subprocess 1153*4882a593Smuzhiyun 1154*4882a593Smuzhiyun ########################################################################### 1155*4882a593Smuzhiyun # Check patch.log for fuzz warnings 1156*4882a593Smuzhiyun # 1157*4882a593Smuzhiyun # Further information on why we check for patch fuzz warnings: 1158*4882a593Smuzhiyun # http://lists.openembedded.org/pipermail/openembedded-core/2018-March/148675.html 1159*4882a593Smuzhiyun # https://bugzilla.yoctoproject.org/show_bug.cgi?id=10450 1160*4882a593Smuzhiyun ########################################################################### 1161*4882a593Smuzhiyun 1162*4882a593Smuzhiyun logdir = d.getVar('T') 1163*4882a593Smuzhiyun patchlog = os.path.join(logdir,"log.do_patch") 1164*4882a593Smuzhiyun 1165*4882a593Smuzhiyun if os.path.exists(patchlog): 1166*4882a593Smuzhiyun fuzzheader = '--- Patch fuzz start ---' 1167*4882a593Smuzhiyun fuzzfooter = '--- Patch fuzz end ---' 1168*4882a593Smuzhiyun statement = "grep -e '%s' %s > /dev/null" % (fuzzheader, patchlog) 1169*4882a593Smuzhiyun if subprocess.call(statement, shell=True) == 0: 1170*4882a593Smuzhiyun msg = "Fuzz detected:\n\n" 1171*4882a593Smuzhiyun fuzzmsg = "" 1172*4882a593Smuzhiyun inFuzzInfo = False 1173*4882a593Smuzhiyun f = open(patchlog, "r") 1174*4882a593Smuzhiyun for line in f: 1175*4882a593Smuzhiyun if fuzzheader in line: 1176*4882a593Smuzhiyun inFuzzInfo = True 1177*4882a593Smuzhiyun fuzzmsg = "" 1178*4882a593Smuzhiyun elif fuzzfooter in line: 1179*4882a593Smuzhiyun fuzzmsg = fuzzmsg.replace('\n\n', '\n') 1180*4882a593Smuzhiyun msg += fuzzmsg 1181*4882a593Smuzhiyun msg += "\n" 1182*4882a593Smuzhiyun inFuzzInfo = False 1183*4882a593Smuzhiyun elif inFuzzInfo and not 'Now at patch' in line: 1184*4882a593Smuzhiyun fuzzmsg += line 1185*4882a593Smuzhiyun f.close() 1186*4882a593Smuzhiyun msg += "The context lines in the patches can be updated with devtool:\n" 1187*4882a593Smuzhiyun msg += "\n" 1188*4882a593Smuzhiyun msg += " devtool modify %s\n" % d.getVar('PN') 1189*4882a593Smuzhiyun msg += " devtool finish --force-patch-refresh %s <layer_path>\n\n" % d.getVar('PN') 1190*4882a593Smuzhiyun msg += "Don't forget to review changes done by devtool!\n" 1191*4882a593Smuzhiyun if bb.utils.filter('ERROR_QA', 'patch-fuzz', d): 1192*4882a593Smuzhiyun bb.error(msg) 1193*4882a593Smuzhiyun elif bb.utils.filter('WARN_QA', 'patch-fuzz', d): 1194*4882a593Smuzhiyun bb.warn(msg) 1195*4882a593Smuzhiyun msg = "Patch log indicates that patches do not apply cleanly." 1196*4882a593Smuzhiyun oe.qa.handle_error("patch-fuzz", msg, d) 1197*4882a593Smuzhiyun 1198*4882a593Smuzhiyun # Check if the patch contains a correctly formatted and spelled Upstream-Status 1199*4882a593Smuzhiyun import re 1200*4882a593Smuzhiyun from oe import patch 1201*4882a593Smuzhiyun 1202*4882a593Smuzhiyun coremeta_path = os.path.join(d.getVar('COREBASE'), 'meta', '') 1203*4882a593Smuzhiyun for url in patch.src_patches(d): 1204*4882a593Smuzhiyun (_, _, fullpath, _, _, _) = bb.fetch.decodeurl(url) 1205*4882a593Smuzhiyun 1206*4882a593Smuzhiyun # skip patches not in oe-core 1207*4882a593Smuzhiyun if not os.path.abspath(fullpath).startswith(coremeta_path): 1208*4882a593Smuzhiyun continue 1209*4882a593Smuzhiyun 1210*4882a593Smuzhiyun kinda_status_re = re.compile(r"^.*upstream.*status.*$", re.IGNORECASE | re.MULTILINE) 1211*4882a593Smuzhiyun strict_status_re = re.compile(r"^Upstream-Status: (Pending|Submitted|Denied|Accepted|Inappropriate|Backport|Inactive-Upstream)( .+)?$", re.MULTILINE) 1212*4882a593Smuzhiyun guidelines = "https://www.openembedded.org/wiki/Commit_Patch_Message_Guidelines#Patch_Header_Recommendations:_Upstream-Status" 1213*4882a593Smuzhiyun 1214*4882a593Smuzhiyun with open(fullpath, encoding='utf-8', errors='ignore') as f: 1215*4882a593Smuzhiyun file_content = f.read() 1216*4882a593Smuzhiyun match_kinda = kinda_status_re.search(file_content) 1217*4882a593Smuzhiyun match_strict = strict_status_re.search(file_content) 1218*4882a593Smuzhiyun 1219*4882a593Smuzhiyun if not match_strict: 1220*4882a593Smuzhiyun if match_kinda: 1221*4882a593Smuzhiyun bb.error("Malformed Upstream-Status in patch\n%s\nPlease correct according to %s :\n%s" % (fullpath, guidelines, match_kinda.group(0))) 1222*4882a593Smuzhiyun else: 1223*4882a593Smuzhiyun bb.error("Missing Upstream-Status in patch\n%s\nPlease add according to %s ." % (fullpath, guidelines)) 1224*4882a593Smuzhiyun} 1225*4882a593Smuzhiyun 1226*4882a593Smuzhiyunpython do_qa_configure() { 1227*4882a593Smuzhiyun import subprocess 1228*4882a593Smuzhiyun 1229*4882a593Smuzhiyun ########################################################################### 1230*4882a593Smuzhiyun # Check config.log for cross compile issues 1231*4882a593Smuzhiyun ########################################################################### 1232*4882a593Smuzhiyun 1233*4882a593Smuzhiyun configs = [] 1234*4882a593Smuzhiyun workdir = d.getVar('WORKDIR') 1235*4882a593Smuzhiyun 1236*4882a593Smuzhiyun skip = (d.getVar('INSANE_SKIP') or "").split() 1237*4882a593Smuzhiyun skip_configure_unsafe = False 1238*4882a593Smuzhiyun if 'configure-unsafe' in skip: 1239*4882a593Smuzhiyun bb.note("Recipe %s skipping qa checking: configure-unsafe" % d.getVar('PN')) 1240*4882a593Smuzhiyun skip_configure_unsafe = True 1241*4882a593Smuzhiyun 1242*4882a593Smuzhiyun if bb.data.inherits_class('autotools', d) and not skip_configure_unsafe: 1243*4882a593Smuzhiyun bb.note("Checking autotools environment for common misconfiguration") 1244*4882a593Smuzhiyun for root, dirs, files in os.walk(workdir): 1245*4882a593Smuzhiyun statement = "grep -q -F -e 'is unsafe for cross-compilation' %s" % \ 1246*4882a593Smuzhiyun os.path.join(root,"config.log") 1247*4882a593Smuzhiyun if "config.log" in files: 1248*4882a593Smuzhiyun if subprocess.call(statement, shell=True) == 0: 1249*4882a593Smuzhiyun error_msg = """This autoconf log indicates errors, it looked at host include and/or library paths while determining system capabilities. 1250*4882a593SmuzhiyunRerun configure task after fixing this.""" 1251*4882a593Smuzhiyun oe.qa.handle_error("configure-unsafe", error_msg, d) 1252*4882a593Smuzhiyun 1253*4882a593Smuzhiyun if "configure.ac" in files: 1254*4882a593Smuzhiyun configs.append(os.path.join(root,"configure.ac")) 1255*4882a593Smuzhiyun if "configure.in" in files: 1256*4882a593Smuzhiyun configs.append(os.path.join(root, "configure.in")) 1257*4882a593Smuzhiyun 1258*4882a593Smuzhiyun ########################################################################### 1259*4882a593Smuzhiyun # Check gettext configuration and dependencies are correct 1260*4882a593Smuzhiyun ########################################################################### 1261*4882a593Smuzhiyun 1262*4882a593Smuzhiyun skip_configure_gettext = False 1263*4882a593Smuzhiyun if 'configure-gettext' in skip: 1264*4882a593Smuzhiyun bb.note("Recipe %s skipping qa checking: configure-gettext" % d.getVar('PN')) 1265*4882a593Smuzhiyun skip_configure_gettext = True 1266*4882a593Smuzhiyun 1267*4882a593Smuzhiyun cnf = d.getVar('EXTRA_OECONF') or "" 1268*4882a593Smuzhiyun if not ("gettext" in d.getVar('P') or "gcc-runtime" in d.getVar('P') or \ 1269*4882a593Smuzhiyun "--disable-nls" in cnf or skip_configure_gettext): 1270*4882a593Smuzhiyun ml = d.getVar("MLPREFIX") or "" 1271*4882a593Smuzhiyun if bb.data.inherits_class('cross-canadian', d): 1272*4882a593Smuzhiyun gt = "nativesdk-gettext" 1273*4882a593Smuzhiyun else: 1274*4882a593Smuzhiyun gt = "gettext-native" 1275*4882a593Smuzhiyun deps = bb.utils.explode_deps(d.getVar('DEPENDS') or "") 1276*4882a593Smuzhiyun if gt not in deps: 1277*4882a593Smuzhiyun for config in configs: 1278*4882a593Smuzhiyun gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config 1279*4882a593Smuzhiyun if subprocess.call(gnu, shell=True) == 0: 1280*4882a593Smuzhiyun error_msg = "AM_GNU_GETTEXT used but no inherit gettext" 1281*4882a593Smuzhiyun oe.qa.handle_error("configure-gettext", error_msg, d) 1282*4882a593Smuzhiyun 1283*4882a593Smuzhiyun ########################################################################### 1284*4882a593Smuzhiyun # Check unrecognised configure options (with a white list) 1285*4882a593Smuzhiyun ########################################################################### 1286*4882a593Smuzhiyun if bb.data.inherits_class("autotools", d): 1287*4882a593Smuzhiyun bb.note("Checking configure output for unrecognised options") 1288*4882a593Smuzhiyun try: 1289*4882a593Smuzhiyun if bb.data.inherits_class("autotools", d): 1290*4882a593Smuzhiyun flag = "WARNING: unrecognized options:" 1291*4882a593Smuzhiyun log = os.path.join(d.getVar('B'), 'config.log') 1292*4882a593Smuzhiyun output = subprocess.check_output(['grep', '-F', flag, log]).decode("utf-8").replace(', ', ' ').replace('"', '') 1293*4882a593Smuzhiyun options = set() 1294*4882a593Smuzhiyun for line in output.splitlines(): 1295*4882a593Smuzhiyun options |= set(line.partition(flag)[2].split()) 1296*4882a593Smuzhiyun ignore_opts = set(d.getVar("UNKNOWN_CONFIGURE_OPT_IGNORE").split()) 1297*4882a593Smuzhiyun options -= ignore_opts 1298*4882a593Smuzhiyun if options: 1299*4882a593Smuzhiyun pn = d.getVar('PN') 1300*4882a593Smuzhiyun error_msg = pn + ": configure was passed unrecognised options: " + " ".join(options) 1301*4882a593Smuzhiyun oe.qa.handle_error("unknown-configure-option", error_msg, d) 1302*4882a593Smuzhiyun except subprocess.CalledProcessError: 1303*4882a593Smuzhiyun pass 1304*4882a593Smuzhiyun 1305*4882a593Smuzhiyun # Check invalid PACKAGECONFIG 1306*4882a593Smuzhiyun pkgconfig = (d.getVar("PACKAGECONFIG") or "").split() 1307*4882a593Smuzhiyun if pkgconfig: 1308*4882a593Smuzhiyun pkgconfigflags = d.getVarFlags("PACKAGECONFIG") or {} 1309*4882a593Smuzhiyun for pconfig in pkgconfig: 1310*4882a593Smuzhiyun if pconfig not in pkgconfigflags: 1311*4882a593Smuzhiyun pn = d.getVar('PN') 1312*4882a593Smuzhiyun error_msg = "%s: invalid PACKAGECONFIG: %s" % (pn, pconfig) 1313*4882a593Smuzhiyun oe.qa.handle_error("invalid-packageconfig", error_msg, d) 1314*4882a593Smuzhiyun 1315*4882a593Smuzhiyun oe.qa.exit_if_errors(d) 1316*4882a593Smuzhiyun} 1317*4882a593Smuzhiyun 1318*4882a593Smuzhiyundef unpack_check_src_uri(pn, d): 1319*4882a593Smuzhiyun import re 1320*4882a593Smuzhiyun 1321*4882a593Smuzhiyun skip = (d.getVar('INSANE_SKIP') or "").split() 1322*4882a593Smuzhiyun if 'src-uri-bad' in skip: 1323*4882a593Smuzhiyun bb.note("Recipe %s skipping qa checking: src-uri-bad" % d.getVar('PN')) 1324*4882a593Smuzhiyun return 1325*4882a593Smuzhiyun 1326*4882a593Smuzhiyun if "${PN}" in d.getVar("SRC_URI", False): 1327*4882a593Smuzhiyun oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses PN not BPN" % pn, d) 1328*4882a593Smuzhiyun 1329*4882a593Smuzhiyun for url in d.getVar("SRC_URI").split(): 1330*4882a593Smuzhiyun if re.search(r"git(hu|la)b\.com/.+/.+/archive/.+", url): 1331*4882a593Smuzhiyun oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses unstable GitHub/GitLab archives, convert recipe to use git protocol" % pn, d) 1332*4882a593Smuzhiyun 1333*4882a593Smuzhiyunpython do_qa_unpack() { 1334*4882a593Smuzhiyun src_uri = d.getVar('SRC_URI') 1335*4882a593Smuzhiyun s_dir = d.getVar('S') 1336*4882a593Smuzhiyun if src_uri and not os.path.exists(s_dir): 1337*4882a593Smuzhiyun bb.warn('%s: the directory %s (%s) pointed to by the S variable doesn\'t exist - please set S within the recipe to point to where the source has been unpacked to' % (d.getVar('PN'), d.getVar('S', False), s_dir)) 1338*4882a593Smuzhiyun 1339*4882a593Smuzhiyun unpack_check_src_uri(d.getVar('PN'), d) 1340*4882a593Smuzhiyun} 1341*4882a593Smuzhiyun 1342*4882a593Smuzhiyun# The Staging Func, to check all staging 1343*4882a593Smuzhiyun#addtask qa_staging after do_populate_sysroot before do_build 1344*4882a593Smuzhiyundo_populate_sysroot[postfuncs] += "do_qa_staging " 1345*4882a593Smuzhiyun 1346*4882a593Smuzhiyun# Check for patch fuzz 1347*4882a593Smuzhiyundo_patch[postfuncs] += "do_qa_patch " 1348*4882a593Smuzhiyun 1349*4882a593Smuzhiyun# Check broken config.log files, for packages requiring Gettext which 1350*4882a593Smuzhiyun# don't have it in DEPENDS. 1351*4882a593Smuzhiyun#addtask qa_configure after do_configure before do_compile 1352*4882a593Smuzhiyundo_configure[postfuncs] += "do_qa_configure " 1353*4882a593Smuzhiyun 1354*4882a593Smuzhiyun# Check does S exist. 1355*4882a593Smuzhiyundo_unpack[postfuncs] += "do_qa_unpack" 1356*4882a593Smuzhiyun 1357*4882a593Smuzhiyunpython () { 1358*4882a593Smuzhiyun import re 1359*4882a593Smuzhiyun 1360*4882a593Smuzhiyun tests = d.getVar('ALL_QA').split() 1361*4882a593Smuzhiyun if "desktop" in tests: 1362*4882a593Smuzhiyun d.appendVar("PACKAGE_DEPENDS", " desktop-file-utils-native") 1363*4882a593Smuzhiyun 1364*4882a593Smuzhiyun ########################################################################### 1365*4882a593Smuzhiyun # Check various variables 1366*4882a593Smuzhiyun ########################################################################### 1367*4882a593Smuzhiyun 1368*4882a593Smuzhiyun # Checking ${FILESEXTRAPATHS} 1369*4882a593Smuzhiyun extrapaths = (d.getVar("FILESEXTRAPATHS") or "") 1370*4882a593Smuzhiyun if '__default' not in extrapaths.split(":"): 1371*4882a593Smuzhiyun msg = "FILESEXTRAPATHS-variable, must always use :prepend (or :append)\n" 1372*4882a593Smuzhiyun msg += "type of assignment, and don't forget the colon.\n" 1373*4882a593Smuzhiyun msg += "Please assign it with the format of:\n" 1374*4882a593Smuzhiyun msg += " FILESEXTRAPATHS:append := \":${THISDIR}/Your_Files_Path\" or\n" 1375*4882a593Smuzhiyun msg += " FILESEXTRAPATHS:prepend := \"${THISDIR}/Your_Files_Path:\"\n" 1376*4882a593Smuzhiyun msg += "in your bbappend file\n\n" 1377*4882a593Smuzhiyun msg += "Your incorrect assignment is:\n" 1378*4882a593Smuzhiyun msg += "%s\n" % extrapaths 1379*4882a593Smuzhiyun bb.warn(msg) 1380*4882a593Smuzhiyun 1381*4882a593Smuzhiyun overrides = d.getVar('OVERRIDES').split(':') 1382*4882a593Smuzhiyun pn = d.getVar('PN') 1383*4882a593Smuzhiyun if pn in overrides: 1384*4882a593Smuzhiyun msg = 'Recipe %s has PN of "%s" which is in OVERRIDES, this can result in unexpected behaviour.' % (d.getVar("FILE"), pn) 1385*4882a593Smuzhiyun oe.qa.handle_error("pn-overrides", msg, d) 1386*4882a593Smuzhiyun prog = re.compile(r'[A-Z]') 1387*4882a593Smuzhiyun if prog.search(pn): 1388*4882a593Smuzhiyun oe.qa.handle_error("uppercase-pn", 'PN: %s is upper case, this can result in unexpected behavior.' % pn, d) 1389*4882a593Smuzhiyun 1390*4882a593Smuzhiyun # Some people mistakenly use DEPENDS:${PN} instead of DEPENDS and wonder 1391*4882a593Smuzhiyun # why it doesn't work. 1392*4882a593Smuzhiyun if (d.getVar(d.expand('DEPENDS:${PN}'))): 1393*4882a593Smuzhiyun oe.qa.handle_error("pkgvarcheck", "recipe uses DEPENDS:${PN}, should use DEPENDS", d) 1394*4882a593Smuzhiyun 1395*4882a593Smuzhiyun issues = [] 1396*4882a593Smuzhiyun if (d.getVar('PACKAGES') or "").split(): 1397*4882a593Smuzhiyun for dep in (d.getVar('QADEPENDS') or "").split(): 1398*4882a593Smuzhiyun d.appendVarFlag('do_package_qa', 'depends', " %s:do_populate_sysroot" % dep) 1399*4882a593Smuzhiyun for var in 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RCONFLICTS', 'RPROVIDES', 'RREPLACES', 'FILES', 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', 'ALLOW_EMPTY': 1400*4882a593Smuzhiyun if d.getVar(var, False): 1401*4882a593Smuzhiyun issues.append(var) 1402*4882a593Smuzhiyun 1403*4882a593Smuzhiyun fakeroot_tests = d.getVar('FAKEROOT_QA').split() 1404*4882a593Smuzhiyun if set(tests) & set(fakeroot_tests): 1405*4882a593Smuzhiyun d.setVarFlag('do_package_qa', 'fakeroot', '1') 1406*4882a593Smuzhiyun d.appendVarFlag('do_package_qa', 'depends', ' virtual/fakeroot-native:do_populate_sysroot') 1407*4882a593Smuzhiyun else: 1408*4882a593Smuzhiyun d.setVarFlag('do_package_qa', 'rdeptask', '') 1409*4882a593Smuzhiyun for i in issues: 1410*4882a593Smuzhiyun oe.qa.handle_error("pkgvarcheck", "%s: Variable %s is set as not being package specific, please fix this." % (d.getVar("FILE"), i), d) 1411*4882a593Smuzhiyun 1412*4882a593Smuzhiyun if 'native-last' not in (d.getVar('INSANE_SKIP') or "").split(): 1413*4882a593Smuzhiyun for native_class in ['native', 'nativesdk']: 1414*4882a593Smuzhiyun if bb.data.inherits_class(native_class, d): 1415*4882a593Smuzhiyun 1416*4882a593Smuzhiyun inherited_classes = d.getVar('__inherit_cache', False) or [] 1417*4882a593Smuzhiyun needle = os.path.join('classes', native_class) 1418*4882a593Smuzhiyun 1419*4882a593Smuzhiyun bbclassextend = (d.getVar('BBCLASSEXTEND') or '').split() 1420*4882a593Smuzhiyun # BBCLASSEXTEND items are always added in the end 1421*4882a593Smuzhiyun skip_classes = bbclassextend 1422*4882a593Smuzhiyun if bb.data.inherits_class('native', d) or 'native' in bbclassextend: 1423*4882a593Smuzhiyun # native also inherits nopackages and relocatable bbclasses 1424*4882a593Smuzhiyun skip_classes.extend(['nopackages', 'relocatable']) 1425*4882a593Smuzhiyun 1426*4882a593Smuzhiyun broken_order = [] 1427*4882a593Smuzhiyun for class_item in reversed(inherited_classes): 1428*4882a593Smuzhiyun if needle not in class_item: 1429*4882a593Smuzhiyun for extend_item in skip_classes: 1430*4882a593Smuzhiyun if os.path.join('classes', '%s.bbclass' % extend_item) in class_item: 1431*4882a593Smuzhiyun break 1432*4882a593Smuzhiyun else: 1433*4882a593Smuzhiyun pn = d.getVar('PN') 1434*4882a593Smuzhiyun broken_order.append(os.path.basename(class_item)) 1435*4882a593Smuzhiyun else: 1436*4882a593Smuzhiyun break 1437*4882a593Smuzhiyun if broken_order: 1438*4882a593Smuzhiyun oe.qa.handle_error("native-last", "%s: native/nativesdk class is not inherited last, this can result in unexpected behaviour. " 1439*4882a593Smuzhiyun "Classes inherited after native/nativesdk: %s" % (pn, " ".join(broken_order)), d) 1440*4882a593Smuzhiyun 1441*4882a593Smuzhiyun oe.qa.exit_if_errors(d) 1442*4882a593Smuzhiyun} 1443