blob: 0bc6492c834a238402f04e8496fb85e9c1d2f177 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# BB Class inspired by ebuild.sh
2#
3# This class will test files after installation for certain
4# security issues and other kind of issues.
5#
6# Checks we do:
7# -Check the ownership and permissions
8# -Check the RUNTIME path for the $TMPDIR
9# -Check if .la files wrongly point to workdir
10# -Check if .pc files wrongly point to workdir
11# -Check if packages contains .debug directories or .so files
12# where they should be in -dev or -dbg
13# -Check if config.log contains traces to broken autoconf tests
14# -Check invalid characters (non-utf8) on some package metadata
15# -Ensure that binaries in base_[bindir|sbindir|libdir] do not link
16# into exec_prefix
17# -Check that scripts in base_[bindir|sbindir|libdir] do not reference
18# files under exec_prefix
Brad Bishopd7bf8c12018-02-25 22:55:05 -050019# -Check if the package name is upper case
Patrick Williamsc124f4f2015-09-15 14:41:29 -050020
Patrick Williamsc124f4f2015-09-15 14:41:29 -050021# Elect whether a given type of error is a warning or error, they may
22# have been set by other files.
Andrew Geisslerc182c622020-05-15 14:13:32 -050023WARN_QA ?= " libdir xorg-driver-abi \
24 textrel incompatible-license files-invalid \
25 infodir build-deps src-uri-bad symlink-to-sysroot multilib \
Brad Bishopd89cb5f2019-04-10 09:02:41 -040026 invalid-packageconfig host-user-contaminated uppercase-pn patch-fuzz \
Andrew Geissler5a43b432020-06-13 10:46:56 -050027 mime mime-xdg unlisted-pkg-lics unhandled-features-check \
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050028 missing-update-alternatives native-last missing-ptest \
Andrew Geisslereff27472021-10-29 15:35:00 -050029 license-exists license-no-generic license-syntax license-format \
Andrew Geissler9aee5002022-03-30 16:27:02 +000030 license-incompatible license-file-missing obsolete-license \
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031 "
32ERROR_QA ?= "dev-so debug-deps dev-deps debug-files arch pkgconfig la \
33 perms dep-cmp pkgvarcheck perm-config perm-line perm-link \
34 split-strip packages-list pkgv-undefined var-undefined \
35 version-going-backwards expanded-d invalid-chars \
Brad Bishop19323692019-04-05 15:28:33 -040036 license-checksum dev-elf file-rdeps configure-unsafe \
Andrew Geissler82c905d2020-04-13 13:39:40 -050037 configure-gettext perllocalpod shebang-size \
Andrew Geisslerc182c622020-05-15 14:13:32 -050038 already-stripped installed-vs-shipped ldflags compile-host-path \
39 install-host-path pn-overrides unknown-configure-option \
Andrew Geissler595f6302022-01-24 19:11:47 +000040 useless-rpaths rpaths staticdev empty-dirs \
Patrick Williamsc124f4f2015-09-15 14:41:29 -050041 "
Brad Bishopd7bf8c12018-02-25 22:55:05 -050042# Add usrmerge QA check based on distro feature
Patrick Williams213cb262021-08-07 19:21:33 -050043ERROR_QA:append = "${@bb.utils.contains('DISTRO_FEATURES', 'usrmerge', ' usrmerge', '', d)}"
Brad Bishopd7bf8c12018-02-25 22:55:05 -050044
Patrick Williamsc124f4f2015-09-15 14:41:29 -050045FAKEROOT_QA = "host-user-contaminated"
46FAKEROOT_QA[doc] = "QA tests which need to run under fakeroot. If any \
47enabled tests are listed here, the do_package_qa task will run under fakeroot."
48
49ALL_QA = "${WARN_QA} ${ERROR_QA}"
50
Andrew Geissler7e0e3c02022-02-25 20:34:39 +000051UNKNOWN_CONFIGURE_OPT_IGNORE ?= "--enable-nls --disable-nls --disable-silent-rules --disable-dependency-tracking --with-libtool-sysroot --disable-static"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050052
Andrew Geissler595f6302022-01-24 19:11:47 +000053# This is a list of directories that are expected to be empty.
54QA_EMPTY_DIRS ?= " \
55 /dev/pts \
56 /media \
57 /proc \
58 /run \
59 /tmp \
60 ${localstatedir}/run \
61 ${localstatedir}/volatile \
62"
63# It is possible to specify why a directory is expected to be empty by defining
64# QA_EMPTY_DIRS_RECOMMENDATION:<path>, which will then be included in the error
65# message if the directory is not empty. If it is not specified for a directory,
66# then "but it is expected to be empty" will be used.
67
Patrick Williamsc0f7c042017-02-23 20:41:17 -060068def package_qa_clean_path(path, d, pkg=None):
69 """
70 Remove redundant paths from the path for display. If pkg isn't set then
71 TMPDIR is stripped, otherwise PKGDEST/pkg is stripped.
72 """
73 if pkg:
Brad Bishop6e60e8b2018-02-01 10:27:11 -050074 path = path.replace(os.path.join(d.getVar("PKGDEST"), pkg), "/")
75 return path.replace(d.getVar("TMPDIR"), "/").replace("//", "/")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050076
Andrew Geissler82c905d2020-04-13 13:39:40 -050077QAPATHTEST[shebang-size] = "package_qa_check_shebang_size"
78def package_qa_check_shebang_size(path, name, d, elf, messages):
Andrew Geisslerd1e89492021-02-12 15:35:20 -060079 import stat
80 if os.path.islink(path) or stat.S_ISFIFO(os.stat(path).st_mode) or elf:
Andrew Geissler82c905d2020-04-13 13:39:40 -050081 return
82
83 try:
84 with open(path, 'rb') as f:
85 stanza = f.readline(130)
86 except IOError:
87 return
88
89 if stanza.startswith(b'#!'):
90 #Shebang not found
91 try:
92 stanza = stanza.decode("utf-8")
93 except UnicodeDecodeError:
94 #If it is not a text file, it is not a script
95 return
96
97 if len(stanza) > 129:
Andrew Geisslereff27472021-10-29 15:35:00 -050098 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)))
Andrew Geissler82c905d2020-04-13 13:39:40 -050099 return
100
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500101QAPATHTEST[libexec] = "package_qa_check_libexec"
102def package_qa_check_libexec(path,name, d, elf, messages):
103
104 # Skip the case where the default is explicitly /usr/libexec
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500105 libexec = d.getVar('libexecdir')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500106 if libexec == "/usr/libexec":
107 return True
108
109 if 'libexec' in path.split(os.path.sep):
Andrew Geisslereff27472021-10-29 15:35:00 -0500110 oe.qa.add_message(messages, "libexec", "%s: %s is using libexec please relocate to %s" % (name, package_qa_clean_path(path, d), libexec))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500111 return False
112
113 return True
114
115QAPATHTEST[rpaths] = "package_qa_check_rpath"
116def package_qa_check_rpath(file,name, d, elf, messages):
117 """
118 Check for dangerous RPATHs
119 """
120 if not elf:
121 return
122
123 if os.path.islink(file):
124 return
125
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500126 bad_dirs = [d.getVar('BASE_WORKDIR'), d.getVar('STAGING_DIR_TARGET')]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500127
128 phdrs = elf.run_objdump("-p", d)
129
130 import re
Brad Bishop977dc1a2019-02-06 16:01:43 -0500131 rpath_re = re.compile(r"\s+RPATH\s+(.*)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500132 for line in phdrs.split("\n"):
133 m = rpath_re.match(line)
134 if m:
135 rpath = m.group(1)
136 for dir in bad_dirs:
137 if dir in rpath:
Andrew Geisslereff27472021-10-29 15:35:00 -0500138 oe.qa.add_message(messages, "rpaths", "package %s contains bad RPATH %s in file %s" % (name, rpath, file))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500139
140QAPATHTEST[useless-rpaths] = "package_qa_check_useless_rpaths"
141def package_qa_check_useless_rpaths(file, name, d, elf, messages):
142 """
143 Check for RPATHs that are useless but not dangerous
144 """
145 def rpath_eq(a, b):
146 return os.path.normpath(a) == os.path.normpath(b)
147
148 if not elf:
149 return
150
151 if os.path.islink(file):
152 return
153
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500154 libdir = d.getVar("libdir")
155 base_libdir = d.getVar("base_libdir")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500156
157 phdrs = elf.run_objdump("-p", d)
158
159 import re
Brad Bishop977dc1a2019-02-06 16:01:43 -0500160 rpath_re = re.compile(r"\s+RPATH\s+(.*)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500161 for line in phdrs.split("\n"):
162 m = rpath_re.match(line)
163 if m:
164 rpath = m.group(1)
165 if rpath_eq(rpath, libdir) or rpath_eq(rpath, base_libdir):
166 # The dynamic linker searches both these places anyway. There is no point in
167 # looking there again.
Andrew Geisslereff27472021-10-29 15:35:00 -0500168 oe.qa.add_message(messages, "useless-rpaths", "%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d, name), rpath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500169
170QAPATHTEST[dev-so] = "package_qa_check_dev"
171def package_qa_check_dev(path, name, d, elf, messages):
172 """
173 Check for ".so" library symlinks in non-dev packages
174 """
175
176 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):
Andrew Geisslereff27472021-10-29 15:35:00 -0500177 oe.qa.add_message(messages, "dev-so", "non -dev/-dbg/nativesdk- package %s contains symlink .so '%s'" % \
Andrew Geisslerc926e172021-05-07 16:11:35 -0500178 (name, package_qa_clean_path(path, d, name)))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500179
180QAPATHTEST[dev-elf] = "package_qa_check_dev_elf"
181def package_qa_check_dev_elf(path, name, d, elf, messages):
182 """
183 Check that -dev doesn't contain real shared libraries. The test has to
184 check that the file is not a link and is an ELF object as some recipes
185 install link-time .so files that are linker scripts.
186 """
187 if name.endswith("-dev") and path.endswith(".so") and not os.path.islink(path) and elf:
Andrew Geisslereff27472021-10-29 15:35:00 -0500188 oe.qa.add_message(messages, "dev-elf", "-dev package %s contains non-symlink .so '%s'" % \
Andrew Geisslerc926e172021-05-07 16:11:35 -0500189 (name, package_qa_clean_path(path, d, name)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500190
191QAPATHTEST[staticdev] = "package_qa_check_staticdev"
192def package_qa_check_staticdev(path, name, d, elf, messages):
193 """
194 Check for ".a" library in non-staticdev packages
195 There are a number of exceptions to this rule, -pic packages can contain
196 static libraries, the _nonshared.a belong with their -dev packages and
197 libgcc.a, libgcov.a will be skipped in their packages
198 """
199
Andrew Geissler82c905d2020-04-13 13:39:40 -0500200 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:
Andrew Geisslereff27472021-10-29 15:35:00 -0500201 oe.qa.add_message(messages, "staticdev", "non -staticdev package contains static .a library: %s path '%s'" % \
Andrew Geisslerc926e172021-05-07 16:11:35 -0500202 (name, package_qa_clean_path(path,d, name)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500203
Andrew Geissler82c905d2020-04-13 13:39:40 -0500204QAPATHTEST[mime] = "package_qa_check_mime"
205def package_qa_check_mime(path, name, d, elf, messages):
206 """
207 Check if package installs mime types to /usr/share/mime/packages
208 while no inheriting mime.bbclass
209 """
210
211 if d.getVar("datadir") + "/mime/packages" in path and path.endswith('.xml') and not bb.data.inherits_class("mime", d):
Andrew Geisslereff27472021-10-29 15:35:00 -0500212 oe.qa.add_message(messages, "mime", "package contains mime types but does not inherit mime: %s path '%s'" % \
Andrew Geissler82c905d2020-04-13 13:39:40 -0500213 (name, package_qa_clean_path(path,d)))
214
215QAPATHTEST[mime-xdg] = "package_qa_check_mime_xdg"
216def package_qa_check_mime_xdg(path, name, d, elf, messages):
217 """
218 Check if package installs desktop file containing MimeType and requires
219 mime-types.bbclass to create /usr/share/applications/mimeinfo.cache
220 """
221
222 if d.getVar("datadir") + "/applications" in path and path.endswith('.desktop') and not bb.data.inherits_class("mime-xdg", d):
223 mime_type_found = False
224 try:
225 with open(path, 'r') as f:
226 for line in f.read().split('\n'):
227 if 'MimeType' in line:
228 mime_type_found = True
229 break;
230 except:
231 # At least libreoffice installs symlinks with absolute paths that are dangling here.
232 # We could implement some magic but for few (one) recipes it is not worth the effort so just warn:
233 wstr = "%s cannot open %s - is it a symlink with absolute path?\n" % (name, package_qa_clean_path(path,d))
234 wstr += "Please check if (linked) file contains key 'MimeType'.\n"
235 pkgname = name
236 if name == d.getVar('PN'):
237 pkgname = '${PN}'
Patrick Williams213cb262021-08-07 19:21:33 -0500238 wstr += "If yes: add \'inhert mime-xdg\' and \'MIME_XDG_PACKAGES += \"%s\"\' / if no add \'INSANE_SKIP:%s += \"mime-xdg\"\' to recipe." % (pkgname, pkgname)
Andrew Geisslereff27472021-10-29 15:35:00 -0500239 oe.qa.add_message(messages, "mime-xdg", wstr)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500240 if mime_type_found:
Andrew Geisslereff27472021-10-29 15:35:00 -0500241 oe.qa.add_message(messages, "mime-xdg", "package contains desktop file with key 'MimeType' but does not inhert mime-xdg: %s path '%s'" % \
Andrew Geissler82c905d2020-04-13 13:39:40 -0500242 (name, package_qa_clean_path(path,d)))
243
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500244def package_qa_check_libdir(d):
245 """
246 Check for wrong library installation paths. For instance, catch
247 recipes installing /lib/bar.so when ${base_libdir}="lib32" or
248 installing in /usr/lib64 when ${libdir}="/usr/lib"
249 """
250 import re
251
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500252 pkgdest = d.getVar('PKGDEST')
253 base_libdir = d.getVar("base_libdir") + os.sep
254 libdir = d.getVar("libdir") + os.sep
255 libexecdir = d.getVar("libexecdir") + os.sep
256 exec_prefix = d.getVar("exec_prefix") + os.sep
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500257
258 messages = []
259
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500260 # The re's are purposely fuzzy, as some there are some .so.x.y.z files
261 # that don't follow the standard naming convention. It checks later
262 # that they are actual ELF files
Brad Bishop977dc1a2019-02-06 16:01:43 -0500263 lib_re = re.compile(r"^/lib.+\.so(\..+)?$")
264 exec_re = re.compile(r"^%s.*/lib.+\.so(\..+)?$" % exec_prefix)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500265
266 for root, dirs, files in os.walk(pkgdest):
267 if root == pkgdest:
268 # Skip subdirectories for any packages with libdir in INSANE_SKIP
269 skippackages = []
270 for package in dirs:
Patrick Williams213cb262021-08-07 19:21:33 -0500271 if 'libdir' in (d.getVar('INSANE_SKIP:' + package) or "").split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500272 bb.note("Package %s skipping libdir QA test" % (package))
273 skippackages.append(package)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500274 elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-file-directory' and package.endswith("-dbg"):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500275 bb.note("Package %s skipping libdir QA test for PACKAGE_DEBUG_SPLIT_STYLE equals debug-file-directory" % (package))
276 skippackages.append(package)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500277 for package in skippackages:
278 dirs.remove(package)
279 for file in files:
280 full_path = os.path.join(root, file)
281 rel_path = os.path.relpath(full_path, pkgdest)
282 if os.sep in rel_path:
283 package, rel_path = rel_path.split(os.sep, 1)
284 rel_path = os.sep + rel_path
285 if lib_re.match(rel_path):
286 if base_libdir not in rel_path:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500287 # make sure it's an actual ELF file
288 elf = oe.qa.ELFFile(full_path)
289 try:
290 elf.open()
291 messages.append("%s: found library in wrong location: %s" % (package, rel_path))
292 except (oe.qa.NotELFFileError):
293 pass
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500294 if exec_re.match(rel_path):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500295 if libdir not in rel_path and libexecdir not in rel_path:
296 # make sure it's an actual ELF file
297 elf = oe.qa.ELFFile(full_path)
298 try:
299 elf.open()
300 messages.append("%s: found library in wrong location: %s" % (package, rel_path))
301 except (oe.qa.NotELFFileError):
302 pass
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303
304 if messages:
Andrew Geisslereff27472021-10-29 15:35:00 -0500305 oe.qa.handle_error("libdir", "\n".join(messages), d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500306
307QAPATHTEST[debug-files] = "package_qa_check_dbg"
308def package_qa_check_dbg(path, name, d, elf, messages):
309 """
310 Check for ".debug" files or directories outside of the dbg package
311 """
312
313 if not "-dbg" in name and not "-ptest" in name:
314 if '.debug' in path.split(os.path.sep):
Andrew Geisslereff27472021-10-29 15:35:00 -0500315 oe.qa.add_message(messages, "debug-files", "non debug package contains .debug directory: %s path %s" % \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500316 (name, package_qa_clean_path(path,d)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500317
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500318QAPATHTEST[arch] = "package_qa_check_arch"
319def package_qa_check_arch(path,name,d, elf, messages):
320 """
321 Check if archs are compatible
322 """
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800323 import re, oe.elf
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600324
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500325 if not elf:
326 return
327
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000328 target_os = d.getVar('HOST_OS')
329 target_arch = d.getVar('HOST_ARCH')
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500330 provides = d.getVar('PROVIDES')
331 bpn = d.getVar('BPN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500332
333 if target_arch == "allarch":
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500334 pn = d.getVar('PN')
Andrew Geisslereff27472021-10-29 15:35:00 -0500335 oe.qa.add_message(messages, "arch", pn + ": Recipe inherits the allarch class, but has packaged architecture-specific binaries")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500336 return
337
338 # FIXME: Cross package confuse this check, so just skip them
339 for s in ['cross', 'nativesdk', 'cross-canadian']:
340 if bb.data.inherits_class(s, d):
341 return
342
343 # avoid following links to /usr/bin (e.g. on udev builds)
344 # we will check the files pointed to anyway...
345 if os.path.islink(path):
346 return
347
348 #if this will throw an exception, then fix the dict above
349 (machine, osabi, abiversion, littleendian, bits) \
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800350 = oe.elf.machine_dict(d)[target_os][target_arch]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351
352 # Check the architecture and endiannes of the binary
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600353 is_32 = (("virtual/kernel" in provides) or bb.data.inherits_class("module", d)) and \
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800354 (target_os == "linux-gnux32" or target_os == "linux-muslx32" or \
Brad Bishop977dc1a2019-02-06 16:01:43 -0500355 target_os == "linux-gnu_ilp32" or re.match(r'mips64.*32', d.getVar('DEFAULTTUNE')))
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800356 is_bpf = (oe.qa.elf_machine_to_string(elf.machine()) == "BPF")
357 if not ((machine == elf.machine()) or is_32 or is_bpf):
Andrew Geisslereff27472021-10-29 15:35:00 -0500358 oe.qa.add_message(messages, "arch", "Architecture did not match (%s, expected %s) in %s" % \
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500359 (oe.qa.elf_machine_to_string(elf.machine()), oe.qa.elf_machine_to_string(machine), package_qa_clean_path(path, d, name)))
Brad Bishop19323692019-04-05 15:28:33 -0400360 elif not ((bits == elf.abiSize()) or is_32 or is_bpf):
Andrew Geisslereff27472021-10-29 15:35:00 -0500361 oe.qa.add_message(messages, "arch", "Bit size did not match (%d, expected %d) in %s" % \
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500362 (elf.abiSize(), bits, package_qa_clean_path(path, d, name)))
Brad Bishop19323692019-04-05 15:28:33 -0400363 elif not ((littleendian == elf.isLittleEndian()) or is_bpf):
Andrew Geisslereff27472021-10-29 15:35:00 -0500364 oe.qa.add_message(messages, "arch", "Endiannes did not match (%d, expected %d) in %s" % \
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500365 (elf.isLittleEndian(), littleendian, package_qa_clean_path(path,d, name)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500366
367QAPATHTEST[desktop] = "package_qa_check_desktop"
368def package_qa_check_desktop(path, name, d, elf, messages):
369 """
370 Run all desktop files through desktop-file-validate.
371 """
372 if path.endswith(".desktop"):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500373 desktop_file_validate = os.path.join(d.getVar('STAGING_BINDIR_NATIVE'),'desktop-file-validate')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500374 output = os.popen("%s %s" % (desktop_file_validate, path))
375 # This only produces output on errors
376 for l in output:
Andrew Geisslereff27472021-10-29 15:35:00 -0500377 oe.qa.add_message(messages, "desktop", "Desktop file issue: " + l.strip())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500378
379QAPATHTEST[textrel] = "package_qa_textrel"
380def package_qa_textrel(path, name, d, elf, messages):
381 """
382 Check if the binary contains relocations in .text
383 """
384
385 if not elf:
386 return
387
388 if os.path.islink(path):
389 return
390
391 phdrs = elf.run_objdump("-p", d)
392 sane = True
393
394 import re
Brad Bishop977dc1a2019-02-06 16:01:43 -0500395 textrel_re = re.compile(r"\s+TEXTREL\s+")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500396 for line in phdrs.split("\n"):
397 if textrel_re.match(line):
398 sane = False
Brad Bishop1d80a2e2019-11-15 16:35:03 -0500399 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500400
401 if not sane:
Brad Bishop1d80a2e2019-11-15 16:35:03 -0500402 path = package_qa_clean_path(path, d, name)
Andrew Geisslereff27472021-10-29 15:35:00 -0500403 oe.qa.add_message(messages, "textrel", "%s: ELF binary %s has relocations in .text" % (name, path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500404
405QAPATHTEST[ldflags] = "package_qa_hash_style"
406def package_qa_hash_style(path, name, d, elf, messages):
407 """
408 Check if the binary has the right hash style...
409 """
410
411 if not elf:
412 return
413
414 if os.path.islink(path):
415 return
416
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500417 gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500418 if not gnu_hash:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500419 gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500420 if not gnu_hash:
421 return
422
423 sane = False
424 has_syms = False
425
426 phdrs = elf.run_objdump("-p", d)
427
428 # If this binary has symbols, we expect it to have GNU_HASH too.
429 for line in phdrs.split("\n"):
430 if "SYMTAB" in line:
431 has_syms = True
Andrew Geissler9aee5002022-03-30 16:27:02 +0000432 if "GNU_HASH" in line or "MIPS_XHASH" in line:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500433 sane = True
Andrew Geissler82c905d2020-04-13 13:39:40 -0500434 if ("[mips32]" in line or "[mips64]" in line) and d.getVar('TCLIBC') == "musl":
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500435 sane = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500436 if has_syms and not sane:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500437 path = package_qa_clean_path(path, d, name)
Andrew Geisslereff27472021-10-29 15:35:00 -0500438 oe.qa.add_message(messages, "ldflags", "File %s in package %s doesn't have GNU_HASH (didn't pass LDFLAGS?)" % (path, name))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500439
440
441QAPATHTEST[buildpaths] = "package_qa_check_buildpaths"
442def package_qa_check_buildpaths(path, name, d, elf, messages):
443 """
Andrew Geissler9aee5002022-03-30 16:27:02 +0000444 Check for build paths inside target files and error if paths are not
445 explicitly ignored.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500446 """
447 # Ignore .debug files, not interesting
448 if path.find(".debug") != -1:
449 return
450
451 # Ignore symlinks
452 if os.path.islink(path):
453 return
454
Brad Bishopd5ae7d92018-06-14 09:52:03 -0700455 tmpdir = bytes(d.getVar('TMPDIR'), encoding="utf-8")
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500456 with open(path, 'rb') as f:
Brad Bishopd5ae7d92018-06-14 09:52:03 -0700457 file_content = f.read()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500458 if tmpdir in file_content:
Brad Bishopf3fd2882019-06-21 08:06:37 -0400459 trimmed = path.replace(os.path.join (d.getVar("PKGDEST"), name), "")
Andrew Geisslereff27472021-10-29 15:35:00 -0500460 oe.qa.add_message(messages, "buildpaths", "File %s in package %s contains reference to TMPDIR" % (trimmed, name))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500461
462
463QAPATHTEST[xorg-driver-abi] = "package_qa_check_xorg_driver_abi"
464def package_qa_check_xorg_driver_abi(path, name, d, elf, messages):
465 """
466 Check that all packages containing Xorg drivers have ABI dependencies
467 """
468
469 # Skip dev, dbg or nativesdk packages
470 if name.endswith("-dev") or name.endswith("-dbg") or name.startswith("nativesdk-"):
471 return
472
473 driverdir = d.expand("${libdir}/xorg/modules/drivers/")
474 if driverdir in path and path.endswith(".so"):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500475 mlprefix = d.getVar('MLPREFIX') or ''
Patrick Williams213cb262021-08-07 19:21:33 -0500476 for rdep in bb.utils.explode_deps(d.getVar('RDEPENDS:' + name) or ""):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500477 if rdep.startswith("%sxorg-abi-" % mlprefix):
478 return
Andrew Geisslereff27472021-10-29 15:35:00 -0500479 oe.qa.add_message(messages, "xorg-driver-abi", "Package %s contains Xorg driver (%s) but no xorg-abi- dependencies" % (name, os.path.basename(path)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500480
481QAPATHTEST[infodir] = "package_qa_check_infodir"
482def package_qa_check_infodir(path, name, d, elf, messages):
483 """
484 Check that /usr/share/info/dir isn't shipped in a particular package
485 """
486 infodir = d.expand("${infodir}/dir")
487
488 if infodir in path:
Andrew Geisslereff27472021-10-29 15:35:00 -0500489 oe.qa.add_message(messages, "infodir", "The /usr/share/info/dir file is not meant to be shipped in a particular package.")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500490
491QAPATHTEST[symlink-to-sysroot] = "package_qa_check_symlink_to_sysroot"
492def package_qa_check_symlink_to_sysroot(path, name, d, elf, messages):
493 """
494 Check that the package doesn't contain any absolute symlinks to the sysroot.
495 """
496 if os.path.islink(path):
497 target = os.readlink(path)
498 if os.path.isabs(target):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500499 tmpdir = d.getVar('TMPDIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500500 if target.startswith(tmpdir):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500501 trimmed = path.replace(os.path.join (d.getVar("PKGDEST"), name), "")
Andrew Geisslereff27472021-10-29 15:35:00 -0500502 oe.qa.add_message(messages, "symlink-to-sysroot", "Symlink %s in %s points to TMPDIR" % (trimmed, name))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500503
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600504# Check license variables
505do_populate_lic[postfuncs] += "populate_lic_qa_checksum"
506python populate_lic_qa_checksum() {
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500507 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600508 Check for changes in the license files.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500509 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500510
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500511 lic_files = d.getVar('LIC_FILES_CHKSUM') or ''
512 lic = d.getVar('LICENSE')
513 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500514
515 if lic == "CLOSED":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500516 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500517
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500518 if not lic_files and d.getVar('SRC_URI'):
Andrew Geisslereff27472021-10-29 15:35:00 -0500519 oe.qa.handle_error("license-checksum", pn + ": Recipe file fetches files and does not have license file information (LIC_FILES_CHKSUM)", d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500521 srcdir = d.getVar('S')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500522 corebase_licensefile = d.getVar('COREBASE') + "/LICENSE"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500523 for url in lic_files.split():
524 try:
525 (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(url)
526 except bb.fetch.MalformedUrl:
Andrew Geisslereff27472021-10-29 15:35:00 -0500527 oe.qa.handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM contains an invalid URL: " + url, d)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500528 continue
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500529 srclicfile = os.path.join(srcdir, path)
530 if not os.path.isfile(srclicfile):
Andrew Geisslereff27472021-10-29 15:35:00 -0500531 oe.qa.handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM points to an invalid file: " + srclicfile, d)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500532 continue
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500533
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500534 if (srclicfile == corebase_licensefile):
535 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")
536
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500537 recipemd5 = parm.get('md5', '')
538 beginline, endline = 0, 0
539 if 'beginline' in parm:
540 beginline = int(parm['beginline'])
541 if 'endline' in parm:
542 endline = int(parm['endline'])
543
544 if (not beginline) and (not endline):
545 md5chksum = bb.utils.md5_file(srclicfile)
Brad Bishop19323692019-04-05 15:28:33 -0400546 with open(srclicfile, 'r', errors='replace') as f:
547 license = f.read().splitlines()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500548 else:
Brad Bishop19323692019-04-05 15:28:33 -0400549 with open(srclicfile, 'rb') as f:
550 import hashlib
551 lineno = 0
552 license = []
Andrew Geissler9aee5002022-03-30 16:27:02 +0000553 m = hashlib.new('MD5', usedforsecurity=False)
Brad Bishop19323692019-04-05 15:28:33 -0400554 for line in f:
555 lineno += 1
556 if (lineno >= beginline):
557 if ((lineno <= endline) or not endline):
558 m.update(line)
559 license.append(line.decode('utf-8', errors='replace').rstrip())
560 else:
561 break
562 md5chksum = m.hexdigest()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500563 if recipemd5 == md5chksum:
564 bb.note (pn + ": md5 checksum matched for ", url)
565 else:
566 if recipemd5:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500567 msg = pn + ": The LIC_FILES_CHKSUM does not match for " + url
568 msg = msg + "\n" + pn + ": The new md5 checksum is " + md5chksum
Brad Bishop19323692019-04-05 15:28:33 -0400569 max_lines = int(d.getVar('QA_MAX_LICENSE_LINES') or 20)
570 if not license or license[-1] != '':
571 # Ensure that our license text ends with a line break
572 # (will be added with join() below).
573 license.append('')
574 remove = len(license) - max_lines
575 if remove > 0:
576 start = max_lines // 2
577 end = start + remove - 1
578 del license[start:end]
579 license.insert(start, '...')
580 msg = msg + "\n" + pn + ": Here is the selected license text:" + \
581 "\n" + \
582 "{:v^70}".format(" beginline=%d " % beginline if beginline else "") + \
583 "\n" + "\n".join(license) + \
584 "{:^^70}".format(" endline=%d " % endline if endline else "")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500585 if beginline:
586 if endline:
587 srcfiledesc = "%s (lines %d through to %d)" % (srclicfile, beginline, endline)
588 else:
589 srcfiledesc = "%s (beginning on line %d)" % (srclicfile, beginline)
590 elif endline:
591 srcfiledesc = "%s (ending on line %d)" % (srclicfile, endline)
592 else:
593 srcfiledesc = srclicfile
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500594 msg = msg + "\n" + pn + ": Check if the license information has changed in %s to verify that the LICENSE value \"%s\" remains valid" % (srcfiledesc, lic)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500595
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500596 else:
597 msg = pn + ": LIC_FILES_CHKSUM is not specified for " + url
598 msg = msg + "\n" + pn + ": The md5 checksum is " + md5chksum
Andrew Geisslereff27472021-10-29 15:35:00 -0500599 oe.qa.handle_error("license-checksum", msg, d)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600600
Andrew Geisslereff27472021-10-29 15:35:00 -0500601 oe.qa.exit_if_errors(d)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600602}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500603
Brad Bishop19323692019-04-05 15:28:33 -0400604def qa_check_staged(path,d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500605 """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500606 Check staged la and pc files for common problems like references to the work
607 directory.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500608
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500609 As this is run after every stage we should be able to find the one
610 responsible for the errors easily even if we look at every .pc and .la file.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500611 """
612
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500613 tmpdir = d.getVar('TMPDIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500614 workdir = os.path.join(tmpdir, "work")
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500615 recipesysroot = d.getVar("RECIPE_SYSROOT")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500616
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500617 if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d):
618 pkgconfigcheck = workdir
619 else:
620 pkgconfigcheck = tmpdir
621
Brad Bishop19323692019-04-05 15:28:33 -0400622 skip = (d.getVar('INSANE_SKIP') or "").split()
623 skip_la = False
624 if 'la' in skip:
625 bb.note("Recipe %s skipping qa checking: la" % d.getVar('PN'))
626 skip_la = True
627
628 skip_pkgconfig = False
629 if 'pkgconfig' in skip:
630 bb.note("Recipe %s skipping qa checking: pkgconfig" % d.getVar('PN'))
631 skip_pkgconfig = True
632
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500633 # find all .la and .pc files
634 # read the content
635 # and check for stuff that looks wrong
636 for root, dirs, files in os.walk(path):
637 for file in files:
638 path = os.path.join(root,file)
Brad Bishop19323692019-04-05 15:28:33 -0400639 if file.endswith(".la") and not skip_la:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500640 with open(path) as f:
641 file_content = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500642 file_content = file_content.replace(recipesysroot, "")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500643 if workdir in file_content:
644 error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
Andrew Geisslereff27472021-10-29 15:35:00 -0500645 oe.qa.handle_error("la", error_msg, d)
Brad Bishop19323692019-04-05 15:28:33 -0400646 elif file.endswith(".pc") and not skip_pkgconfig:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500647 with open(path) as f:
648 file_content = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500649 file_content = file_content.replace(recipesysroot, "")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500650 if pkgconfigcheck in file_content:
651 error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
Andrew Geisslereff27472021-10-29 15:35:00 -0500652 oe.qa.handle_error("pkgconfig", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500653
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500654# Run all package-wide warnfuncs and errorfuncs
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500655def package_qa_package(warnfuncs, errorfuncs, package, d):
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500656 warnings = {}
657 errors = {}
658
659 for func in warnfuncs:
660 func(package, d, warnings)
661 for func in errorfuncs:
662 func(package, d, errors)
663
664 for w in warnings:
Andrew Geisslereff27472021-10-29 15:35:00 -0500665 oe.qa.handle_error(w, warnings[w], d)
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500666 for e in errors:
Andrew Geisslereff27472021-10-29 15:35:00 -0500667 oe.qa.handle_error(e, errors[e], d)
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500668
669 return len(errors) == 0
670
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500671# Run all recipe-wide warnfuncs and errorfuncs
672def package_qa_recipe(warnfuncs, errorfuncs, pn, d):
673 warnings = {}
674 errors = {}
675
676 for func in warnfuncs:
677 func(pn, d, warnings)
678 for func in errorfuncs:
679 func(pn, d, errors)
680
681 for w in warnings:
Andrew Geisslereff27472021-10-29 15:35:00 -0500682 oe.qa.handle_error(w, warnings[w], d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500683 for e in errors:
Andrew Geisslereff27472021-10-29 15:35:00 -0500684 oe.qa.handle_error(e, errors[e], d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500685
686 return len(errors) == 0
687
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000688def prepopulate_objdump_p(elf, d):
689 output = elf.run_objdump("-p", d)
690 return (elf.name, output)
691
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500692# Walk over all files in a directory and call func
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500693def package_qa_walk(warnfuncs, errorfuncs, package, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500694 #if this will throw an exception, then fix the dict above
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000695 target_os = d.getVar('HOST_OS')
696 target_arch = d.getVar('HOST_ARCH')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500697
698 warnings = {}
699 errors = {}
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000700 elves = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500701 for path in pkgfiles[package]:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500702 elf = None
703 if os.path.isfile(path):
704 elf = oe.qa.ELFFile(path)
705 try:
706 elf.open()
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000707 elf.close()
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500708 except oe.qa.NotELFFileError:
709 elf = None
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000710 if elf:
711 elves[path] = elf
712
713 results = oe.utils.multiprocess_launch(prepopulate_objdump_p, elves.values(), d, extraargs=(d,))
714 for item in results:
715 elves[item[0]].set_objdump("-p", item[1])
716
717 for path in pkgfiles[package]:
718 if path in elves:
719 elves[path].open()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500720 for func in warnfuncs:
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000721 func(path, package, d, elves.get(path), warnings)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500722 for func in errorfuncs:
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000723 func(path, package, d, elves.get(path), errors)
724 if path in elves:
725 elves[path].close()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500726
727 for w in warnings:
Andrew Geisslereff27472021-10-29 15:35:00 -0500728 oe.qa.handle_error(w, warnings[w], d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500729 for e in errors:
Andrew Geisslereff27472021-10-29 15:35:00 -0500730 oe.qa.handle_error(e, errors[e], d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500731
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500732def package_qa_check_rdepends(pkg, pkgdest, skip, taskdeps, packages, d):
733 # Don't do this check for kernel/module recipes, there aren't too many debug/development
734 # packages and you can get false positives e.g. on kernel-module-lirc-dev
735 if bb.data.inherits_class("kernel", d) or bb.data.inherits_class("module-base", d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500736 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500737
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500738 if not "-dbg" in pkg and not "packagegroup-" in pkg and not "-image" in pkg:
739 localdata = bb.data.createCopy(d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500740 localdata.setVar('OVERRIDES', localdata.getVar('OVERRIDES') + ':' + pkg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500741
742 # Now check the RDEPENDS
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500743 rdepends = bb.utils.explode_deps(localdata.getVar('RDEPENDS') or "")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500744
745 # Now do the sanity check!!!
746 if "build-deps" not in skip:
747 for rdepend in rdepends:
748 if "-dbg" in rdepend and "debug-deps" not in skip:
749 error_msg = "%s rdepends on %s" % (pkg,rdepend)
Andrew Geisslereff27472021-10-29 15:35:00 -0500750 oe.qa.handle_error("debug-deps", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751 if (not "-dev" in pkg and not "-staticdev" in pkg) and rdepend.endswith("-dev") and "dev-deps" not in skip:
752 error_msg = "%s rdepends on %s" % (pkg, rdepend)
Andrew Geisslereff27472021-10-29 15:35:00 -0500753 oe.qa.handle_error("dev-deps", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500754 if rdepend not in packages:
755 rdep_data = oe.packagedata.read_subpkgdata(rdepend, d)
756 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps:
757 continue
758 if not rdep_data or not 'PN' in rdep_data:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500759 pkgdata_dir = d.getVar("PKGDATA_DIR")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500760 try:
761 possibles = os.listdir("%s/runtime-rprovides/%s/" % (pkgdata_dir, rdepend))
762 except OSError:
763 possibles = []
764 for p in possibles:
765 rdep_data = oe.packagedata.read_subpkgdata(p, d)
766 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps:
767 break
768 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps:
769 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500770 if rdep_data and 'PN' in rdep_data:
771 error_msg = "%s rdepends on %s, but it isn't a build dependency, missing %s in DEPENDS or PACKAGECONFIG?" % (pkg, rdepend, rdep_data['PN'])
772 else:
773 error_msg = "%s rdepends on %s, but it isn't a build dependency?" % (pkg, rdepend)
Andrew Geisslereff27472021-10-29 15:35:00 -0500774 oe.qa.handle_error("build-deps", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500775
776 if "file-rdeps" not in skip:
777 ignored_file_rdeps = set(['/bin/sh', '/usr/bin/env', 'rtld(GNU_HASH)'])
778 if bb.data.inherits_class('nativesdk', d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500779 ignored_file_rdeps |= set(['/bin/bash', '/usr/bin/perl', 'perl'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500780 # For Saving the FILERDEPENDS
781 filerdepends = {}
782 rdep_data = oe.packagedata.read_subpkgdata(pkg, d)
783 for key in rdep_data:
Andrew Geissler5199d832021-09-24 16:47:35 -0500784 if key.startswith("FILERDEPENDS:"):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500785 for subkey in bb.utils.explode_deps(rdep_data[key]):
786 if subkey not in ignored_file_rdeps and \
787 not subkey.startswith('perl('):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500788 # We already know it starts with FILERDEPENDS_
789 filerdepends[subkey] = key[13:]
790
791 if filerdepends:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500792 done = rdepends[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500793 # Add the rprovides of itself
794 if pkg not in done:
795 done.insert(0, pkg)
796
797 # The python is not a package, but python-core provides it, so
798 # skip checking /usr/bin/python if python is in the rdeps, in
Patrick Williams213cb262021-08-07 19:21:33 -0500799 # case there is a RDEPENDS:pkg = "python" in the recipe.
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500800 for py in [ d.getVar('MLPREFIX') + "python", "python" ]:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500801 if py in done:
802 filerdepends.pop("/usr/bin/python",None)
803 done.remove(py)
804 for rdep in done:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500805 # The file dependencies may contain package names, e.g.,
806 # perl
807 filerdepends.pop(rdep,None)
808
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500809 # For Saving the FILERPROVIDES, RPROVIDES and FILES_INFO
810 rdep_data = oe.packagedata.read_subpkgdata(rdep, d)
811 for key in rdep_data:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500812 if key.startswith("FILERPROVIDES:") or key.startswith("RPROVIDES:"):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500813 for subkey in bb.utils.explode_deps(rdep_data[key]):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500814 filerdepends.pop(subkey,None)
815 # Add the files list to the rprovides
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500816 if key.startswith("FILES_INFO:"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500817 # Use eval() to make it as a dict
818 for subkey in eval(rdep_data[key]):
819 filerdepends.pop(subkey,None)
820 if not filerdepends:
821 # Break if all the file rdepends are met
822 break
823 if filerdepends:
824 for key in filerdepends:
Patrick Williams213cb262021-08-07 19:21:33 -0500825 error_msg = "%s contained in package %s requires %s, but no providers found in RDEPENDS:%s?" % \
826 (filerdepends[key].replace(":%s" % pkg, "").replace("@underscore@", "_"), pkg, key, pkg)
Andrew Geisslereff27472021-10-29 15:35:00 -0500827 oe.qa.handle_error("file-rdeps", error_msg, d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500828package_qa_check_rdepends[vardepsexclude] = "OVERRIDES"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500829
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500830def package_qa_check_deps(pkg, pkgdest, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500831
832 localdata = bb.data.createCopy(d)
833 localdata.setVar('OVERRIDES', pkg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500834
835 def check_valid_deps(var):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500836 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500837 rvar = bb.utils.explode_dep_versions2(localdata.getVar(var) or "")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500838 except ValueError as e:
Patrick Williams0ca19cc2021-08-16 14:03:13 -0500839 bb.fatal("%s:%s: %s" % (var, pkg, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500840 for dep in rvar:
841 for v in rvar[dep]:
842 if v and not v.startswith(('< ', '= ', '> ', '<= ', '>=')):
Patrick Williams0ca19cc2021-08-16 14:03:13 -0500843 error_msg = "%s:%s is invalid: %s (%s) only comparisons <, =, >, <=, and >= are allowed" % (var, pkg, dep, v)
Andrew Geisslereff27472021-10-29 15:35:00 -0500844 oe.qa.handle_error("dep-cmp", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500845
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500846 check_valid_deps('RDEPENDS')
847 check_valid_deps('RRECOMMENDS')
848 check_valid_deps('RSUGGESTS')
849 check_valid_deps('RPROVIDES')
850 check_valid_deps('RREPLACES')
851 check_valid_deps('RCONFLICTS')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500852
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500853QAPKGTEST[usrmerge] = "package_qa_check_usrmerge"
854def package_qa_check_usrmerge(pkg, d, messages):
Andrew Geisslereff27472021-10-29 15:35:00 -0500855
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500856 pkgdest = d.getVar('PKGDEST')
857 pkg_dir = pkgdest + os.sep + pkg + os.sep
858 merged_dirs = ['bin', 'sbin', 'lib'] + d.getVar('MULTILIB_VARIANTS').split()
859 for f in merged_dirs:
860 if os.path.exists(pkg_dir + f) and not os.path.islink(pkg_dir + f):
861 msg = "%s package is not obeying usrmerge distro feature. /%s should be relocated to /usr." % (pkg, f)
Andrew Geisslereff27472021-10-29 15:35:00 -0500862 oe.qa.add_message(messages, "usrmerge", msg)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500863 return False
864 return True
865
Brad Bishopf3f93bb2019-10-16 14:33:32 -0400866QAPKGTEST[perllocalpod] = "package_qa_check_perllocalpod"
867def package_qa_check_perllocalpod(pkg, d, messages):
868 """
869 Check that the recipe didn't ship a perlocal.pod file, which shouldn't be
870 installed in a distribution package. cpan.bbclass sets NO_PERLLOCAL=1 to
871 handle this for most recipes.
872 """
873 import glob
874 pkgd = oe.path.join(d.getVar('PKGDEST'), pkg)
875 podpath = oe.path.join(pkgd, d.getVar("libdir"), "perl*", "*", "*", "perllocal.pod")
876
877 matches = glob.glob(podpath)
878 if matches:
879 matches = [package_qa_clean_path(path, d, pkg) for path in matches]
880 msg = "%s contains perllocal.pod (%s), should not be installed" % (pkg, " ".join(matches))
Andrew Geisslereff27472021-10-29 15:35:00 -0500881 oe.qa.add_message(messages, "perllocalpod", msg)
Brad Bishopf3f93bb2019-10-16 14:33:32 -0400882
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500883QAPKGTEST[expanded-d] = "package_qa_check_expanded_d"
884def package_qa_check_expanded_d(package, d, messages):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500885 """
886 Check for the expanded D (${D}) value in pkg_* and FILES
887 variables, warn the user to use it correctly.
888 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500889 sane = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500890 expanded_d = d.getVar('D')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500891
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500892 for var in 'FILES','pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm':
Patrick Williams0ca19cc2021-08-16 14:03:13 -0500893 bbvar = d.getVar(var + ":" + package) or ""
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500894 if expanded_d in bbvar:
895 if var == 'FILES':
Andrew Geisslereff27472021-10-29 15:35:00 -0500896 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)
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500897 sane = False
898 else:
Andrew Geisslereff27472021-10-29 15:35:00 -0500899 oe.qa.add_message(messages, "expanded-d", "%s in %s recipe contains ${D}, it should be replaced by $D instead" % (var, package))
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500900 sane = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500901 return sane
902
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500903QAPKGTEST[unlisted-pkg-lics] = "package_qa_check_unlisted_pkg_lics"
904def package_qa_check_unlisted_pkg_lics(package, d, messages):
905 """
906 Check that all licenses for a package are among the licenses for the recipe.
907 """
Patrick Williams213cb262021-08-07 19:21:33 -0500908 pkg_lics = d.getVar('LICENSE:' + package)
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500909 if not pkg_lics:
910 return True
911
912 recipe_lics_set = oe.license.list_licenses(d.getVar('LICENSE'))
Andrew Geissler9aee5002022-03-30 16:27:02 +0000913 package_lics = oe.license.list_licenses(pkg_lics)
914 unlisted = package_lics - recipe_lics_set
915 if unlisted:
916 oe.qa.add_message(messages, "unlisted-pkg-lics",
917 "LICENSE:%s includes licenses (%s) that are not "
918 "listed in LICENSE" % (package, ' '.join(unlisted)))
919 return False
920 obsolete = set(oe.license.obsolete_license_list()) & package_lics - recipe_lics_set
921 if obsolete:
922 oe.qa.add_message(messages, "obsolete-license",
923 "LICENSE:%s includes obsolete licenses %s" % (package, ' '.join(obsolete)))
924 return False
925 return True
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500926
Andrew Geissler595f6302022-01-24 19:11:47 +0000927QAPKGTEST[empty-dirs] = "package_qa_check_empty_dirs"
928def package_qa_check_empty_dirs(pkg, d, messages):
929 """
930 Check for the existence of files in directories that are expected to be
931 empty.
932 """
933
934 pkgd = oe.path.join(d.getVar('PKGDEST'), pkg)
935 for dir in (d.getVar('QA_EMPTY_DIRS') or "").split():
936 empty_dir = oe.path.join(pkgd, dir)
937 if os.path.exists(empty_dir) and os.listdir(empty_dir):
938 recommendation = (d.getVar('QA_EMPTY_DIRS_RECOMMENDATION:' + dir) or
939 "but it is expected to be empty")
940 msg = "%s installs files in %s, %s" % (pkg, dir, recommendation)
941 oe.qa.add_message(messages, "empty-dirs", msg)
942
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500943def package_qa_check_encoding(keys, encode, d):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600944 def check_encoding(key, enc):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500945 sane = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500946 value = d.getVar(key)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500947 if value:
948 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600949 s = value.encode(enc)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500950 except UnicodeDecodeError as e:
951 error_msg = "%s has non %s characters" % (key,enc)
952 sane = False
Andrew Geisslereff27472021-10-29 15:35:00 -0500953 oe.qa.handle_error("invalid-chars", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500954 return sane
955
956 for key in keys:
957 sane = check_encoding(key, encode)
958 if not sane:
959 break
960
961HOST_USER_UID := "${@os.getuid()}"
962HOST_USER_GID := "${@os.getgid()}"
963
964QAPATHTEST[host-user-contaminated] = "package_qa_check_host_user"
965def package_qa_check_host_user(path, name, d, elf, messages):
966 """Check for paths outside of /home which are owned by the user running bitbake."""
967
968 if not os.path.lexists(path):
969 return
970
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500971 dest = d.getVar('PKGDEST')
972 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500973 home = os.path.join(dest, 'home')
974 if path == home or path.startswith(home + os.sep):
975 return
976
977 try:
978 stat = os.lstat(path)
979 except OSError as exc:
980 import errno
981 if exc.errno != errno.ENOENT:
982 raise
983 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500984 check_uid = int(d.getVar('HOST_USER_UID'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500985 if stat.st_uid == check_uid:
Andrew Geisslereff27472021-10-29 15:35:00 -0500986 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))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500987 return False
988
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500989 check_gid = int(d.getVar('HOST_USER_GID'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500990 if stat.st_gid == check_gid:
Andrew Geisslereff27472021-10-29 15:35:00 -0500991 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))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500992 return False
993 return True
994
Andrew Geissler5a43b432020-06-13 10:46:56 -0500995QARECIPETEST[unhandled-features-check] = "package_qa_check_unhandled_features_check"
996def package_qa_check_unhandled_features_check(pn, d, messages):
997 if not bb.data.inherits_class('features_check', d):
998 var_set = False
999 for kind in ['DISTRO', 'MACHINE', 'COMBINED']:
1000 for var in ['ANY_OF_' + kind + '_FEATURES', 'REQUIRED_' + kind + '_FEATURES', 'CONFLICT_' + kind + '_FEATURES']:
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001001 if d.getVar(var) is not None or d.hasOverrides(var):
Andrew Geissler5a43b432020-06-13 10:46:56 -05001002 var_set = True
1003 if var_set:
Andrew Geisslereff27472021-10-29 15:35:00 -05001004 oe.qa.handle_error("unhandled-features-check", "%s: recipe doesn't inherit features_check" % pn, d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001005
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001006QARECIPETEST[missing-update-alternatives] = "package_qa_check_missing_update_alternatives"
1007def package_qa_check_missing_update_alternatives(pn, d, messages):
1008 # Look at all packages and find out if any of those sets ALTERNATIVE variable
1009 # without inheriting update-alternatives class
1010 for pkg in (d.getVar('PACKAGES') or '').split():
Patrick Williams213cb262021-08-07 19:21:33 -05001011 if d.getVar('ALTERNATIVE:%s' % pkg) and not bb.data.inherits_class('update-alternatives', d):
Andrew Geisslereff27472021-10-29 15:35:00 -05001012 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)
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001013
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001014# The PACKAGE FUNC to scan each package
1015python do_package_qa () {
1016 import subprocess
1017 import oe.packagedata
1018
1019 bb.note("DO PACKAGE QA")
1020
Andrew Geissler9aee5002022-03-30 16:27:02 +00001021 main_lic = d.getVar('LICENSE')
1022
1023 # Check for obsolete license references in main LICENSE (packages are checked below for any changes)
1024 main_licenses = oe.license.list_licenses(d.getVar('LICENSE'))
1025 obsolete = set(oe.license.obsolete_license_list()) & main_licenses
1026 if obsolete:
1027 oe.qa.handle_error("obsolete-license", "Recipe LICENSE includes obsolete licenses %s" % ' '.join(obsolete), d)
1028
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001029 bb.build.exec_func("read_subpackage_metadata", d)
1030
1031 # Check non UTF-8 characters on recipe's metadata
1032 package_qa_check_encoding(['DESCRIPTION', 'SUMMARY', 'LICENSE', 'SECTION'], 'utf-8', d)
1033
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001034 logdir = d.getVar('T')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001035 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001036
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001037 # Scan the packages...
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001038 pkgdest = d.getVar('PKGDEST')
1039 packages = set((d.getVar('PACKAGES') or '').split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001040
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001041 global pkgfiles
1042 pkgfiles = {}
1043 for pkg in packages:
1044 pkgfiles[pkg] = []
Andrew Geisslerb7d28612020-07-24 16:15:54 -05001045 pkgdir = os.path.join(pkgdest, pkg)
1046 for walkroot, dirs, files in os.walk(pkgdir):
1047 # Don't walk into top-level CONTROL or DEBIAN directories as these
1048 # are temporary directories created by do_package.
1049 if walkroot == pkgdir:
1050 for control in ("CONTROL", "DEBIAN"):
1051 if control in dirs:
1052 dirs.remove(control)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001053 for file in files:
Andrew Geissler82c905d2020-04-13 13:39:40 -05001054 pkgfiles[pkg].append(os.path.join(walkroot, file))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001055
1056 # no packages should be scanned
1057 if not packages:
1058 return
1059
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001060 import re
1061 # The package name matches the [a-z0-9.+-]+ regular expression
Brad Bishop977dc1a2019-02-06 16:01:43 -05001062 pkgname_pattern = re.compile(r"^[a-z0-9.+-]+$")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001063
1064 taskdepdata = d.getVar("BB_TASKDEPDATA", False)
1065 taskdeps = set()
1066 for dep in taskdepdata:
1067 taskdeps.add(taskdepdata[dep][0])
1068
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001069 def parse_test_matrix(matrix_name):
1070 testmatrix = d.getVarFlags(matrix_name) or {}
1071 g = globals()
1072 warnchecks = []
1073 for w in (d.getVar("WARN_QA") or "").split():
1074 if w in skip:
1075 continue
1076 if w in testmatrix and testmatrix[w] in g:
1077 warnchecks.append(g[testmatrix[w]])
1078
1079 errorchecks = []
1080 for e in (d.getVar("ERROR_QA") or "").split():
1081 if e in skip:
1082 continue
1083 if e in testmatrix and testmatrix[e] in g:
1084 errorchecks.append(g[testmatrix[e]])
1085 return warnchecks, errorchecks
1086
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001087 for package in packages:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001088 skip = set((d.getVar('INSANE_SKIP') or "").split() +
Patrick Williams213cb262021-08-07 19:21:33 -05001089 (d.getVar('INSANE_SKIP:' + package) or "").split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001090 if skip:
1091 bb.note("Package %s skipping QA tests: %s" % (package, str(skip)))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001092
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001093 bb.note("Checking Package: %s" % package)
1094 # Check package name
1095 if not pkgname_pattern.match(package):
Andrew Geisslereff27472021-10-29 15:35:00 -05001096 oe.qa.handle_error("pkgname",
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001097 "%s doesn't match the [a-z0-9.+-]+ regex" % package, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001098
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001099 warn_checks, error_checks = parse_test_matrix("QAPATHTEST")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001100 package_qa_walk(warn_checks, error_checks, package, d)
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001101
1102 warn_checks, error_checks = parse_test_matrix("QAPKGTEST")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001103 package_qa_package(warn_checks, error_checks, package, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001104
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001105 package_qa_check_rdepends(package, pkgdest, skip, taskdeps, packages, d)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001106 package_qa_check_deps(package, pkgdest, d)
1107
1108 warn_checks, error_checks = parse_test_matrix("QARECIPETEST")
1109 package_qa_recipe(warn_checks, error_checks, pn, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001110
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001111 if 'libdir' in d.getVar("ALL_QA").split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001112 package_qa_check_libdir(d)
1113
Andrew Geisslereff27472021-10-29 15:35:00 -05001114 oe.qa.exit_if_errors(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001115}
1116
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001117# binutils is used for most checks, so need to set as dependency
1118# POPULATESYSROOTDEPS is defined in staging class.
1119do_package_qa[depends] += "${POPULATESYSROOTDEPS}"
Patrick Williamsde0582f2022-04-08 10:23:27 -05001120do_package_qa[vardeps] = "${@bb.utils.contains('ERROR_QA', 'empty-dirs', 'QA_EMPTY_DIRS', '', d)}"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001121do_package_qa[vardepsexclude] = "BB_TASKDEPDATA"
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001122do_package_qa[rdeptask] = "do_packagedata"
1123addtask do_package_qa after do_packagedata do_package before do_build
1124
Brad Bishop19323692019-04-05 15:28:33 -04001125# Add the package specific INSANE_SKIPs to the sstate dependencies
1126python() {
1127 pkgs = (d.getVar('PACKAGES') or '').split()
1128 for pkg in pkgs:
Patrick Williams213cb262021-08-07 19:21:33 -05001129 d.appendVarFlag("do_package_qa", "vardeps", " INSANE_SKIP:{}".format(pkg))
Brad Bishop19323692019-04-05 15:28:33 -04001130}
1131
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001132SSTATETASKS += "do_package_qa"
1133do_package_qa[sstate-inputdirs] = ""
1134do_package_qa[sstate-outputdirs] = ""
1135python do_package_qa_setscene () {
1136 sstate_setscene(d)
1137}
1138addtask do_package_qa_setscene
1139
1140python do_qa_staging() {
1141 bb.note("QA checking staging")
Andrew Geisslereff27472021-10-29 15:35:00 -05001142 qa_check_staged(d.expand('${SYSROOT_DESTDIR}${libdir}'), d)
1143 oe.qa.exit_with_message_if_errors("QA staging was broken by the package built above", d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001144}
1145
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001146python do_qa_patch() {
1147 import subprocess
1148
1149 ###########################################################################
1150 # Check patch.log for fuzz warnings
1151 #
1152 # Further information on why we check for patch fuzz warnings:
1153 # http://lists.openembedded.org/pipermail/openembedded-core/2018-March/148675.html
1154 # https://bugzilla.yoctoproject.org/show_bug.cgi?id=10450
1155 ###########################################################################
1156
1157 logdir = d.getVar('T')
1158 patchlog = os.path.join(logdir,"log.do_patch")
1159
1160 if os.path.exists(patchlog):
1161 fuzzheader = '--- Patch fuzz start ---'
1162 fuzzfooter = '--- Patch fuzz end ---'
1163 statement = "grep -e '%s' %s > /dev/null" % (fuzzheader, patchlog)
1164 if subprocess.call(statement, shell=True) == 0:
1165 msg = "Fuzz detected:\n\n"
1166 fuzzmsg = ""
1167 inFuzzInfo = False
1168 f = open(patchlog, "r")
1169 for line in f:
1170 if fuzzheader in line:
1171 inFuzzInfo = True
1172 fuzzmsg = ""
1173 elif fuzzfooter in line:
1174 fuzzmsg = fuzzmsg.replace('\n\n', '\n')
1175 msg += fuzzmsg
1176 msg += "\n"
1177 inFuzzInfo = False
1178 elif inFuzzInfo and not 'Now at patch' in line:
1179 fuzzmsg += line
1180 f.close()
1181 msg += "The context lines in the patches can be updated with devtool:\n"
1182 msg += "\n"
1183 msg += " devtool modify %s\n" % d.getVar('PN')
1184 msg += " devtool finish --force-patch-refresh %s <layer_path>\n\n" % d.getVar('PN')
1185 msg += "Don't forget to review changes done by devtool!\n"
Patrick Williamsde0582f2022-04-08 10:23:27 -05001186 if bb.utils.filter('ERROR_QA', 'patch-fuzz', d):
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001187 bb.error(msg)
Patrick Williamsde0582f2022-04-08 10:23:27 -05001188 elif bb.utils.filter('WARN_QA', 'patch-fuzz', d):
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001189 bb.warn(msg)
1190 msg = "Patch log indicates that patches do not apply cleanly."
Andrew Geisslereff27472021-10-29 15:35:00 -05001191 oe.qa.handle_error("patch-fuzz", msg, d)
Andrew Geissler595f6302022-01-24 19:11:47 +00001192
1193 # Check if the patch contains a correctly formatted and spelled Upstream-Status
1194 import re
1195 from oe import patch
1196
1197 for url in patch.src_patches(d):
1198 (_, _, fullpath, _, _, _) = bb.fetch.decodeurl(url)
1199
1200 # skip patches not in oe-core
1201 if '/meta/' not in fullpath:
1202 continue
1203
1204 content = open(fullpath, encoding='utf-8', errors='ignore').read()
1205 kinda_status_re = re.compile(r"^.*upstream.*status.*$", re.IGNORECASE | re.MULTILINE)
1206 strict_status_re = re.compile(r"^Upstream-Status: (Pending|Submitted|Denied|Accepted|Inappropriate|Backport|Inactive-Upstream)( .+)?$", re.MULTILINE)
1207 match_kinda = kinda_status_re.search(content)
1208 match_strict = strict_status_re.search(content)
1209 guidelines = "https://www.openembedded.org/wiki/Commit_Patch_Message_Guidelines#Patch_Header_Recommendations:_Upstream-Status"
1210
1211 if not match_strict:
1212 if match_kinda:
1213 bb.error("Malformed Upstream-Status in patch\n%s\nPlease correct according to %s :\n%s" % (fullpath, guidelines, match_kinda.group(0)))
1214 else:
1215 bb.error("Missing Upstream-Status in patch\n%s\nPlease add according to %s ." % (fullpath, guidelines))
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001216}
1217
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001218python do_qa_configure() {
1219 import subprocess
1220
1221 ###########################################################################
1222 # Check config.log for cross compile issues
1223 ###########################################################################
1224
1225 configs = []
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001226 workdir = d.getVar('WORKDIR')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001227
Brad Bishop19323692019-04-05 15:28:33 -04001228 skip = (d.getVar('INSANE_SKIP') or "").split()
1229 skip_configure_unsafe = False
1230 if 'configure-unsafe' in skip:
1231 bb.note("Recipe %s skipping qa checking: configure-unsafe" % d.getVar('PN'))
1232 skip_configure_unsafe = True
1233
1234 if bb.data.inherits_class('autotools', d) and not skip_configure_unsafe:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001235 bb.note("Checking autotools environment for common misconfiguration")
1236 for root, dirs, files in os.walk(workdir):
Andrew Geissler95ac1b82021-03-31 14:34:31 -05001237 statement = "grep -q -F -e 'is unsafe for cross-compilation' %s" % \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001238 os.path.join(root,"config.log")
1239 if "config.log" in files:
1240 if subprocess.call(statement, shell=True) == 0:
Brad Bishop19323692019-04-05 15:28:33 -04001241 error_msg = """This autoconf log indicates errors, it looked at host include and/or library paths while determining system capabilities.
1242Rerun configure task after fixing this."""
Andrew Geisslereff27472021-10-29 15:35:00 -05001243 oe.qa.handle_error("configure-unsafe", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001244
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001245 if "configure.ac" in files:
1246 configs.append(os.path.join(root,"configure.ac"))
1247 if "configure.in" in files:
1248 configs.append(os.path.join(root, "configure.in"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001249
1250 ###########################################################################
1251 # Check gettext configuration and dependencies are correct
1252 ###########################################################################
1253
Brad Bishop19323692019-04-05 15:28:33 -04001254 skip_configure_gettext = False
1255 if 'configure-gettext' in skip:
1256 bb.note("Recipe %s skipping qa checking: configure-gettext" % d.getVar('PN'))
1257 skip_configure_gettext = True
1258
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001259 cnf = d.getVar('EXTRA_OECONF') or ""
Brad Bishop19323692019-04-05 15:28:33 -04001260 if not ("gettext" in d.getVar('P') or "gcc-runtime" in d.getVar('P') or \
1261 "--disable-nls" in cnf or skip_configure_gettext):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001262 ml = d.getVar("MLPREFIX") or ""
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001263 if bb.data.inherits_class('cross-canadian', d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001264 gt = "nativesdk-gettext"
1265 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001266 gt = "gettext-native"
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001267 deps = bb.utils.explode_deps(d.getVar('DEPENDS') or "")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001268 if gt not in deps:
1269 for config in configs:
1270 gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config
1271 if subprocess.call(gnu, shell=True) == 0:
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001272 error_msg = "AM_GNU_GETTEXT used but no inherit gettext"
Andrew Geisslereff27472021-10-29 15:35:00 -05001273 oe.qa.handle_error("configure-gettext", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001274
1275 ###########################################################################
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001276 # Check unrecognised configure options (with a white list)
1277 ###########################################################################
Andrew Geissler595f6302022-01-24 19:11:47 +00001278 if bb.data.inherits_class("autotools", d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001279 bb.note("Checking configure output for unrecognised options")
1280 try:
Brad Bishopc342db32019-05-15 21:57:59 -04001281 if bb.data.inherits_class("autotools", d):
1282 flag = "WARNING: unrecognized options:"
1283 log = os.path.join(d.getVar('B'), 'config.log')
Brad Bishopc342db32019-05-15 21:57:59 -04001284 output = subprocess.check_output(['grep', '-F', flag, log]).decode("utf-8").replace(', ', ' ').replace('"', '')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001285 options = set()
1286 for line in output.splitlines():
1287 options |= set(line.partition(flag)[2].split())
Andrew Geissler9aee5002022-03-30 16:27:02 +00001288 ignore_opts = set(d.getVar("UNKNOWN_CONFIGURE_OPT_IGNORE").split())
1289 options -= ignore_opts
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001290 if options:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001291 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001292 error_msg = pn + ": configure was passed unrecognised options: " + " ".join(options)
Andrew Geisslereff27472021-10-29 15:35:00 -05001293 oe.qa.handle_error("unknown-configure-option", error_msg, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001294 except subprocess.CalledProcessError:
1295 pass
1296
1297 # Check invalid PACKAGECONFIG
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001298 pkgconfig = (d.getVar("PACKAGECONFIG") or "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001299 if pkgconfig:
1300 pkgconfigflags = d.getVarFlags("PACKAGECONFIG") or {}
1301 for pconfig in pkgconfig:
1302 if pconfig not in pkgconfigflags:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001303 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001304 error_msg = "%s: invalid PACKAGECONFIG: %s" % (pn, pconfig)
Andrew Geisslereff27472021-10-29 15:35:00 -05001305 oe.qa.handle_error("invalid-packageconfig", error_msg, d)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001306
Andrew Geisslereff27472021-10-29 15:35:00 -05001307 oe.qa.exit_if_errors(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001308}
1309
Andrew Geissler595f6302022-01-24 19:11:47 +00001310def unpack_check_src_uri(pn, d):
1311 import re
1312
1313 skip = (d.getVar('INSANE_SKIP') or "").split()
1314 if 'src-uri-bad' in skip:
1315 bb.note("Recipe %s skipping qa checking: src-uri-bad" % d.getVar('PN'))
1316 return
1317
1318 if "${PN}" in d.getVar("SRC_URI", False):
1319 oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses PN not BPN" % pn, d)
1320
1321 for url in d.getVar("SRC_URI").split():
1322 if re.search(r"git(hu|la)b\.com/.+/.+/archive/.+", url):
1323 oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses unstable GitHub/GitLab archives, convert recipe to use git protocol" % pn, d)
1324
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001325python do_qa_unpack() {
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001326 src_uri = d.getVar('SRC_URI')
1327 s_dir = d.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001328 if src_uri and not os.path.exists(s_dir):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001329 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))
Andrew Geissler595f6302022-01-24 19:11:47 +00001330
1331 unpack_check_src_uri(d.getVar('PN'), d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001332}
1333
1334# The Staging Func, to check all staging
1335#addtask qa_staging after do_populate_sysroot before do_build
1336do_populate_sysroot[postfuncs] += "do_qa_staging "
1337
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001338# Check for patch fuzz
1339do_patch[postfuncs] += "do_qa_patch "
1340
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001341# Check broken config.log files, for packages requiring Gettext which
1342# don't have it in DEPENDS.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001343#addtask qa_configure after do_configure before do_compile
1344do_configure[postfuncs] += "do_qa_configure "
1345
1346# Check does S exist.
1347do_unpack[postfuncs] += "do_qa_unpack"
1348
1349python () {
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001350 import re
1351
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001352 tests = d.getVar('ALL_QA').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001353 if "desktop" in tests:
1354 d.appendVar("PACKAGE_DEPENDS", " desktop-file-utils-native")
1355
1356 ###########################################################################
1357 # Check various variables
1358 ###########################################################################
1359
1360 # Checking ${FILESEXTRAPATHS}
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001361 extrapaths = (d.getVar("FILESEXTRAPATHS") or "")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001362 if '__default' not in extrapaths.split(":"):
Patrick Williams213cb262021-08-07 19:21:33 -05001363 msg = "FILESEXTRAPATHS-variable, must always use :prepend (or :append)\n"
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001364 msg += "type of assignment, and don't forget the colon.\n"
1365 msg += "Please assign it with the format of:\n"
Patrick Williams213cb262021-08-07 19:21:33 -05001366 msg += " FILESEXTRAPATHS:append := \":${THISDIR}/Your_Files_Path\" or\n"
1367 msg += " FILESEXTRAPATHS:prepend := \"${THISDIR}/Your_Files_Path:\"\n"
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001368 msg += "in your bbappend file\n\n"
1369 msg += "Your incorrect assignment is:\n"
1370 msg += "%s\n" % extrapaths
1371 bb.warn(msg)
1372
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001373 overrides = d.getVar('OVERRIDES').split(':')
1374 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001375 if pn in overrides:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001376 msg = 'Recipe %s has PN of "%s" which is in OVERRIDES, this can result in unexpected behaviour.' % (d.getVar("FILE"), pn)
Andrew Geisslereff27472021-10-29 15:35:00 -05001377 oe.qa.handle_error("pn-overrides", msg, d)
Brad Bishop977dc1a2019-02-06 16:01:43 -05001378 prog = re.compile(r'[A-Z]')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001379 if prog.search(pn):
Andrew Geisslereff27472021-10-29 15:35:00 -05001380 oe.qa.handle_error("uppercase-pn", 'PN: %s is upper case, this can result in unexpected behavior.' % pn, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001381
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001382 # Some people mistakenly use DEPENDS:${PN} instead of DEPENDS and wonder
Brad Bishop08902b02019-08-20 09:16:51 -04001383 # why it doesn't work.
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001384 if (d.getVar(d.expand('DEPENDS:${PN}'))):
Andrew Geisslereff27472021-10-29 15:35:00 -05001385 oe.qa.handle_error("pkgvarcheck", "recipe uses DEPENDS:${PN}, should use DEPENDS", d)
Brad Bishop08902b02019-08-20 09:16:51 -04001386
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001387 issues = []
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001388 if (d.getVar('PACKAGES') or "").split():
1389 for dep in (d.getVar('QADEPENDS') or "").split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001390 d.appendVarFlag('do_package_qa', 'depends', " %s:do_populate_sysroot" % dep)
1391 for var in 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RCONFLICTS', 'RPROVIDES', 'RREPLACES', 'FILES', 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', 'ALLOW_EMPTY':
1392 if d.getVar(var, False):
1393 issues.append(var)
1394
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001395 fakeroot_tests = d.getVar('FAKEROOT_QA').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001396 if set(tests) & set(fakeroot_tests):
1397 d.setVarFlag('do_package_qa', 'fakeroot', '1')
1398 d.appendVarFlag('do_package_qa', 'depends', ' virtual/fakeroot-native:do_populate_sysroot')
1399 else:
1400 d.setVarFlag('do_package_qa', 'rdeptask', '')
1401 for i in issues:
Andrew Geisslereff27472021-10-29 15:35:00 -05001402 oe.qa.handle_error("pkgvarcheck", "%s: Variable %s is set as not being package specific, please fix this." % (d.getVar("FILE"), i), d)
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001403
1404 if 'native-last' not in (d.getVar('INSANE_SKIP') or "").split():
1405 for native_class in ['native', 'nativesdk']:
1406 if bb.data.inherits_class(native_class, d):
1407
1408 inherited_classes = d.getVar('__inherit_cache', False) or []
1409 needle = os.path.join('classes', native_class)
1410
1411 bbclassextend = (d.getVar('BBCLASSEXTEND') or '').split()
1412 # BBCLASSEXTEND items are always added in the end
1413 skip_classes = bbclassextend
1414 if bb.data.inherits_class('native', d) or 'native' in bbclassextend:
1415 # native also inherits nopackages and relocatable bbclasses
1416 skip_classes.extend(['nopackages', 'relocatable'])
1417
1418 broken_order = []
1419 for class_item in reversed(inherited_classes):
1420 if needle not in class_item:
1421 for extend_item in skip_classes:
1422 if os.path.join('classes', '%s.bbclass' % extend_item) in class_item:
1423 break
1424 else:
1425 pn = d.getVar('PN')
1426 broken_order.append(os.path.basename(class_item))
1427 else:
1428 break
1429 if broken_order:
Andrew Geisslereff27472021-10-29 15:35:00 -05001430 oe.qa.handle_error("native-last", "%s: native/nativesdk class is not inherited last, this can result in unexpected behaviour. "
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001431 "Classes inherited after native/nativesdk: %s" % (pn, " ".join(broken_order)), d)
1432
Andrew Geisslereff27472021-10-29 15:35:00 -05001433 oe.qa.exit_if_errors(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001434}