xref: /OK3568_Linux_fs/yocto/poky/meta/classes/insane.bbclass (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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