192b42cb3SPatrick Williams# 292b42cb3SPatrick Williams# Copyright OpenEmbedded Contributors 392b42cb3SPatrick Williams# 492b42cb3SPatrick Williams# SPDX-License-Identifier: MIT 592b42cb3SPatrick Williams# 692b42cb3SPatrick Williams 792b42cb3SPatrick WilliamsROOTFS_LICENSE_DIR = "${IMAGE_ROOTFS}/usr/share/common-licenses" 892b42cb3SPatrick Williams 992b42cb3SPatrick Williams# This requires LICENSE_CREATE_PACKAGE=1 to work too 1092b42cb3SPatrick WilliamsCOMPLEMENTARY_GLOB[lic-pkgs] = "*-lic" 1192b42cb3SPatrick Williams 1292b42cb3SPatrick Williamspython() { 1392b42cb3SPatrick Williams if not oe.data.typed_value('LICENSE_CREATE_PACKAGE', d): 1492b42cb3SPatrick Williams features = set(oe.data.typed_value('IMAGE_FEATURES', d)) 1592b42cb3SPatrick Williams if 'lic-pkgs' in features: 1692b42cb3SPatrick Williams bb.error("'lic-pkgs' in IMAGE_FEATURES but LICENSE_CREATE_PACKAGE not enabled to generate -lic packages") 1792b42cb3SPatrick Williams} 1892b42cb3SPatrick Williams 1992b42cb3SPatrick Williamspython write_package_manifest() { 2092b42cb3SPatrick Williams # Get list of installed packages 21220dafdbSAndrew Geissler license_image_dir = d.expand('${LICENSE_DIRECTORY}/${SSTATE_PKGARCH}/${IMAGE_NAME}') 2292b42cb3SPatrick Williams bb.utils.mkdirhier(license_image_dir) 2392b42cb3SPatrick Williams from oe.rootfs import image_list_installed_packages 2492b42cb3SPatrick Williams from oe.utils import format_pkg_list 2592b42cb3SPatrick Williams 2692b42cb3SPatrick Williams pkgs = image_list_installed_packages(d) 2792b42cb3SPatrick Williams output = format_pkg_list(pkgs) 2892b42cb3SPatrick Williams with open(os.path.join(license_image_dir, 'package.manifest'), "w+") as package_manifest: 2992b42cb3SPatrick Williams package_manifest.write(output) 3092b42cb3SPatrick Williams} 3192b42cb3SPatrick Williams 3292b42cb3SPatrick Williamspython license_create_manifest() { 3392b42cb3SPatrick Williams import oe.packagedata 3492b42cb3SPatrick Williams from oe.rootfs import image_list_installed_packages 3592b42cb3SPatrick Williams 3692b42cb3SPatrick Williams build_images_from_feeds = d.getVar('BUILD_IMAGES_FROM_FEEDS') 3792b42cb3SPatrick Williams if build_images_from_feeds == "1": 3892b42cb3SPatrick Williams return 0 3992b42cb3SPatrick Williams 4092b42cb3SPatrick Williams pkg_dic = {} 4192b42cb3SPatrick Williams for pkg in sorted(image_list_installed_packages(d)): 4292b42cb3SPatrick Williams pkg_info = os.path.join(d.getVar('PKGDATA_DIR'), 4392b42cb3SPatrick Williams 'runtime-reverse', pkg) 4492b42cb3SPatrick Williams pkg_name = os.path.basename(os.readlink(pkg_info)) 4592b42cb3SPatrick Williams 4692b42cb3SPatrick Williams pkg_dic[pkg_name] = oe.packagedata.read_pkgdatafile(pkg_info) 4792b42cb3SPatrick Williams if not "LICENSE" in pkg_dic[pkg_name].keys(): 4892b42cb3SPatrick Williams pkg_lic_name = "LICENSE:" + pkg_name 4992b42cb3SPatrick Williams pkg_dic[pkg_name]["LICENSE"] = pkg_dic[pkg_name][pkg_lic_name] 5092b42cb3SPatrick Williams 5192b42cb3SPatrick Williams rootfs_license_manifest = os.path.join(d.getVar('LICENSE_DIRECTORY'), 52220dafdbSAndrew Geissler d.getVar('SSTATE_PKGARCH'), d.getVar('IMAGE_NAME'), 'license.manifest') 5392b42cb3SPatrick Williams write_license_files(d, rootfs_license_manifest, pkg_dic, rootfs=True) 5492b42cb3SPatrick Williams} 5592b42cb3SPatrick Williams 5692b42cb3SPatrick Williamsdef write_license_files(d, license_manifest, pkg_dic, rootfs=True): 5792b42cb3SPatrick Williams import re 5892b42cb3SPatrick Williams import stat 5992b42cb3SPatrick Williams 6092b42cb3SPatrick Williams bad_licenses = (d.getVar("INCOMPATIBLE_LICENSE") or "").split() 61*8460358cSPatrick Williams bad_licenses = oe.license.expand_wildcard_licenses(d, bad_licenses) 62220dafdbSAndrew Geissler pkgarchs = d.getVar("SSTATE_ARCHS").split() 63220dafdbSAndrew Geissler pkgarchs.reverse() 6492b42cb3SPatrick Williams 6592b42cb3SPatrick Williams exceptions = (d.getVar("INCOMPATIBLE_LICENSE_EXCEPTIONS") or "").split() 6692b42cb3SPatrick Williams with open(license_manifest, "w") as license_file: 6792b42cb3SPatrick Williams for pkg in sorted(pkg_dic): 6892b42cb3SPatrick Williams remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions) 69*8460358cSPatrick Williams incompatible_licenses = oe.license.incompatible_pkg_license(d, remaining_bad_licenses, pkg_dic[pkg]["LICENSE"]) 7092b42cb3SPatrick Williams if incompatible_licenses: 7192b42cb3SPatrick Williams bb.fatal("Package %s cannot be installed into the image because it has incompatible license(s): %s" %(pkg, ' '.join(incompatible_licenses))) 7292b42cb3SPatrick Williams else: 73*8460358cSPatrick Williams incompatible_licenses = oe.license.incompatible_pkg_license(d, bad_licenses, pkg_dic[pkg]["LICENSE"]) 7492b42cb3SPatrick Williams if incompatible_licenses: 75*8460358cSPatrick Williams oe.qa.handle_error('license-exception', "Including %s with incompatible license(s) %s into the image, because it has been allowed by exception list." %(pkg, ' '.join(incompatible_licenses)), d) 7692b42cb3SPatrick Williams try: 7792b42cb3SPatrick Williams (pkg_dic[pkg]["LICENSE"], pkg_dic[pkg]["LICENSES"]) = \ 7892b42cb3SPatrick Williams oe.license.manifest_licenses(pkg_dic[pkg]["LICENSE"], 79*8460358cSPatrick Williams remaining_bad_licenses, oe.license.canonical_license, d) 8092b42cb3SPatrick Williams except oe.license.LicenseError as exc: 8192b42cb3SPatrick Williams bb.fatal('%s: %s' % (d.getVar('P'), exc)) 8292b42cb3SPatrick Williams 8392b42cb3SPatrick Williams if not "IMAGE_MANIFEST" in pkg_dic[pkg]: 8492b42cb3SPatrick Williams # Rootfs manifest 8592b42cb3SPatrick Williams license_file.write("PACKAGE NAME: %s\n" % pkg) 8692b42cb3SPatrick Williams license_file.write("PACKAGE VERSION: %s\n" % pkg_dic[pkg]["PV"]) 8792b42cb3SPatrick Williams license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"]) 8892b42cb3SPatrick Williams license_file.write("LICENSE: %s\n\n" % pkg_dic[pkg]["LICENSE"]) 8992b42cb3SPatrick Williams 9092b42cb3SPatrick Williams # If the package doesn't contain any file, that is, its size is 0, the license 9192b42cb3SPatrick Williams # isn't relevant as far as the final image is concerned. So doing license check 9292b42cb3SPatrick Williams # doesn't make much sense, skip it. 9392b42cb3SPatrick Williams if pkg_dic[pkg]["PKGSIZE:%s" % pkg] == "0": 9492b42cb3SPatrick Williams continue 9592b42cb3SPatrick Williams else: 9692b42cb3SPatrick Williams # Image manifest 9792b42cb3SPatrick Williams license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"]) 9892b42cb3SPatrick Williams license_file.write("VERSION: %s\n" % pkg_dic[pkg]["PV"]) 9992b42cb3SPatrick Williams license_file.write("LICENSE: %s\n" % pkg_dic[pkg]["LICENSE"]) 10092b42cb3SPatrick Williams license_file.write("FILES: %s\n\n" % pkg_dic[pkg]["FILES"]) 10192b42cb3SPatrick Williams 10292b42cb3SPatrick Williams for lic in pkg_dic[pkg]["LICENSES"]: 103220dafdbSAndrew Geissler for pkgarch in pkgarchs: 10492b42cb3SPatrick Williams lic_file = os.path.join(d.getVar('LICENSE_DIRECTORY'), 105220dafdbSAndrew Geissler pkgarch, 10692b42cb3SPatrick Williams pkg_dic[pkg]["PN"], "generic_%s" % 10792b42cb3SPatrick Williams re.sub(r'\+', '', lic)) 108220dafdbSAndrew Geissler if os.path.exists(lic_file): 109220dafdbSAndrew Geissler break 11092b42cb3SPatrick Williams # add explicity avoid of CLOSED license because isn't generic 11192b42cb3SPatrick Williams if lic == "CLOSED": 11292b42cb3SPatrick Williams continue 11392b42cb3SPatrick Williams 11492b42cb3SPatrick Williams if not os.path.exists(lic_file): 11592b42cb3SPatrick Williams oe.qa.handle_error('license-file-missing', 11692b42cb3SPatrick Williams "The license listed %s was not in the "\ 11792b42cb3SPatrick Williams "licenses collected for recipe %s" 11892b42cb3SPatrick Williams % (lic, pkg_dic[pkg]["PN"]), d) 11992b42cb3SPatrick Williams oe.qa.exit_if_errors(d) 12092b42cb3SPatrick Williams 12192b42cb3SPatrick Williams # Two options here: 12292b42cb3SPatrick Williams # - Just copy the manifest 12392b42cb3SPatrick Williams # - Copy the manifest and the license directories 12492b42cb3SPatrick Williams # With both options set we see a .5 M increase in core-image-minimal 12592b42cb3SPatrick Williams copy_lic_manifest = d.getVar('COPY_LIC_MANIFEST') 12692b42cb3SPatrick Williams copy_lic_dirs = d.getVar('COPY_LIC_DIRS') 12792b42cb3SPatrick Williams if rootfs and copy_lic_manifest == "1": 12892b42cb3SPatrick Williams rootfs_license_dir = d.getVar('ROOTFS_LICENSE_DIR') 12992b42cb3SPatrick Williams bb.utils.mkdirhier(rootfs_license_dir) 13092b42cb3SPatrick Williams rootfs_license_manifest = os.path.join(rootfs_license_dir, 13192b42cb3SPatrick Williams os.path.split(license_manifest)[1]) 13292b42cb3SPatrick Williams if not os.path.exists(rootfs_license_manifest): 13392b42cb3SPatrick Williams oe.path.copyhardlink(license_manifest, rootfs_license_manifest) 13492b42cb3SPatrick Williams 13592b42cb3SPatrick Williams if copy_lic_dirs == "1": 13692b42cb3SPatrick Williams for pkg in sorted(pkg_dic): 13792b42cb3SPatrick Williams pkg_rootfs_license_dir = os.path.join(rootfs_license_dir, pkg) 13892b42cb3SPatrick Williams bb.utils.mkdirhier(pkg_rootfs_license_dir) 139220dafdbSAndrew Geissler for pkgarch in pkgarchs: 14092b42cb3SPatrick Williams pkg_license_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), 141220dafdbSAndrew Geissler pkgarch, pkg_dic[pkg]["PN"]) 142220dafdbSAndrew Geissler if os.path.exists(pkg_license_dir): 143220dafdbSAndrew Geissler break 144220dafdbSAndrew Geissler if not os.path.exists(pkg_license_dir ): 145220dafdbSAndrew Geissler bb.fatal("Couldn't find license information for dependency %s" % pkg) 14692b42cb3SPatrick Williams 147*8460358cSPatrick Williams pkg_manifest_licenses = [oe.license.canonical_license(d, lic) \ 14892b42cb3SPatrick Williams for lic in pkg_dic[pkg]["LICENSES"]] 14992b42cb3SPatrick Williams 15092b42cb3SPatrick Williams licenses = os.listdir(pkg_license_dir) 15192b42cb3SPatrick Williams for lic in licenses: 15292b42cb3SPatrick Williams pkg_license = os.path.join(pkg_license_dir, lic) 15392b42cb3SPatrick Williams pkg_rootfs_license = os.path.join(pkg_rootfs_license_dir, lic) 15492b42cb3SPatrick Williams 15592b42cb3SPatrick Williams if re.match(r"^generic_.*$", lic): 156*8460358cSPatrick Williams generic_lic = oe.license.canonical_license(d, 15792b42cb3SPatrick Williams re.search(r"^generic_(.*)$", lic).group(1)) 15892b42cb3SPatrick Williams 15992b42cb3SPatrick Williams # Do not copy generic license into package if isn't 16092b42cb3SPatrick Williams # declared into LICENSES of the package. 16192b42cb3SPatrick Williams if not re.sub(r'\+$', '', generic_lic) in \ 16292b42cb3SPatrick Williams [re.sub(r'\+', '', lic) for lic in \ 16392b42cb3SPatrick Williams pkg_manifest_licenses]: 16492b42cb3SPatrick Williams continue 16592b42cb3SPatrick Williams 16692b42cb3SPatrick Williams if oe.license.license_ok(generic_lic, 16792b42cb3SPatrick Williams bad_licenses) == False: 16892b42cb3SPatrick Williams continue 16992b42cb3SPatrick Williams 17092b42cb3SPatrick Williams # Make sure we use only canonical name for the license file 17192b42cb3SPatrick Williams generic_lic_file = "generic_%s" % generic_lic 17292b42cb3SPatrick Williams rootfs_license = os.path.join(rootfs_license_dir, generic_lic_file) 17392b42cb3SPatrick Williams if not os.path.exists(rootfs_license): 17492b42cb3SPatrick Williams oe.path.copyhardlink(pkg_license, rootfs_license) 17592b42cb3SPatrick Williams 17692b42cb3SPatrick Williams if not os.path.exists(pkg_rootfs_license): 17792b42cb3SPatrick Williams os.symlink(os.path.join('..', generic_lic_file), pkg_rootfs_license) 17892b42cb3SPatrick Williams else: 179*8460358cSPatrick Williams if (oe.license.license_ok(oe.license.canonical_license(d, 18092b42cb3SPatrick Williams lic), bad_licenses) == False or 18192b42cb3SPatrick Williams os.path.exists(pkg_rootfs_license)): 18292b42cb3SPatrick Williams continue 18392b42cb3SPatrick Williams 18492b42cb3SPatrick Williams oe.path.copyhardlink(pkg_license, pkg_rootfs_license) 18592b42cb3SPatrick Williams # Fixup file ownership and permissions 18692b42cb3SPatrick Williams for walkroot, dirs, files in os.walk(rootfs_license_dir): 18792b42cb3SPatrick Williams for f in files: 18892b42cb3SPatrick Williams p = os.path.join(walkroot, f) 18992b42cb3SPatrick Williams os.lchown(p, 0, 0) 19092b42cb3SPatrick Williams if not os.path.islink(p): 19192b42cb3SPatrick Williams os.chmod(p, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) 19292b42cb3SPatrick Williams for dir in dirs: 19392b42cb3SPatrick Williams p = os.path.join(walkroot, dir) 19492b42cb3SPatrick Williams os.lchown(p, 0, 0) 19592b42cb3SPatrick Williams os.chmod(p, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) 19692b42cb3SPatrick Williams 197220dafdbSAndrew Geisslerwrite_license_files[vardepsexclude] = "SSTATE_ARCHS" 19892b42cb3SPatrick Williams 19992b42cb3SPatrick Williamsdef license_deployed_manifest(d): 20092b42cb3SPatrick Williams """ 20192b42cb3SPatrick Williams Write the license manifest for the deployed recipes. 20292b42cb3SPatrick Williams The deployed recipes usually includes the bootloader 20392b42cb3SPatrick Williams and extra files to boot the target. 20492b42cb3SPatrick Williams """ 20592b42cb3SPatrick Williams 20692b42cb3SPatrick Williams dep_dic = {} 20792b42cb3SPatrick Williams man_dic = {} 20892b42cb3SPatrick Williams lic_dir = d.getVar("LICENSE_DIRECTORY") 209220dafdbSAndrew Geissler pkgarchs = d.getVar("SSTATE_ARCHS").split() 210220dafdbSAndrew Geissler pkgarchs.reverse() 21192b42cb3SPatrick Williams 21292b42cb3SPatrick Williams dep_dic = get_deployed_dependencies(d) 21392b42cb3SPatrick Williams for dep in dep_dic.keys(): 21492b42cb3SPatrick Williams man_dic[dep] = {} 21592b42cb3SPatrick Williams # It is necessary to mark this will be used for image manifest 21692b42cb3SPatrick Williams man_dic[dep]["IMAGE_MANIFEST"] = True 21792b42cb3SPatrick Williams man_dic[dep]["PN"] = dep 21892b42cb3SPatrick Williams man_dic[dep]["FILES"] = \ 21992b42cb3SPatrick Williams " ".join(get_deployed_files(dep_dic[dep])) 220220dafdbSAndrew Geissler 221220dafdbSAndrew Geissler for pkgarch in pkgarchs: 222220dafdbSAndrew Geissler licfile = os.path.join(lic_dir, pkgarch, dep, "recipeinfo") 223220dafdbSAndrew Geissler if os.path.exists(licfile): 224220dafdbSAndrew Geissler break 225220dafdbSAndrew Geissler if not os.path.exists(licfile): 226220dafdbSAndrew Geissler bb.fatal("Couldn't find license information for dependency %s" % dep) 227220dafdbSAndrew Geissler with open(licfile, "r") as f: 22892b42cb3SPatrick Williams for line in f.readlines(): 22992b42cb3SPatrick Williams key,val = line.split(": ", 1) 23092b42cb3SPatrick Williams man_dic[dep][key] = val[:-1] 23192b42cb3SPatrick Williams 232220dafdbSAndrew Geissler lic_manifest_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), d.getVar('SSTATE_PKGARCH'), 23392b42cb3SPatrick Williams d.getVar('IMAGE_NAME')) 23492b42cb3SPatrick Williams bb.utils.mkdirhier(lic_manifest_dir) 23592b42cb3SPatrick Williams image_license_manifest = os.path.join(lic_manifest_dir, 'image_license.manifest') 23692b42cb3SPatrick Williams write_license_files(d, image_license_manifest, man_dic, rootfs=False) 23792b42cb3SPatrick Williams 23892b42cb3SPatrick Williams link_name = d.getVar('IMAGE_LINK_NAME') 23992b42cb3SPatrick Williams if link_name: 240220dafdbSAndrew Geissler lic_manifest_symlink_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), d.getVar('SSTATE_PKGARCH'), 24192b42cb3SPatrick Williams link_name) 24292b42cb3SPatrick Williams # remove old symlink 24392b42cb3SPatrick Williams if os.path.islink(lic_manifest_symlink_dir): 24492b42cb3SPatrick Williams os.unlink(lic_manifest_symlink_dir) 24592b42cb3SPatrick Williams 24692b42cb3SPatrick Williams # create the image dir symlink 24792b42cb3SPatrick Williams if lic_manifest_dir != lic_manifest_symlink_dir: 24892b42cb3SPatrick Williams os.symlink(lic_manifest_dir, lic_manifest_symlink_dir) 24992b42cb3SPatrick Williams 250220dafdbSAndrew Geisslerlicense_deployed_manifest[vardepsexclude] = "SSTATE_ARCHS" 251220dafdbSAndrew Geissler 25292b42cb3SPatrick Williamsdef get_deployed_dependencies(d): 25392b42cb3SPatrick Williams """ 25492b42cb3SPatrick Williams Get all the deployed dependencies of an image 25592b42cb3SPatrick Williams """ 25692b42cb3SPatrick Williams 25792b42cb3SPatrick Williams deploy = {} 25892b42cb3SPatrick Williams # Get all the dependencies for the current task (rootfs). 25992b42cb3SPatrick Williams taskdata = d.getVar("BB_TASKDEPDATA", False) 260864cc43bSPatrick Williams pn = d.getVar("PN") 26192b42cb3SPatrick Williams depends = list(set([dep[0] for dep 26292b42cb3SPatrick Williams in list(taskdata.values()) 26392b42cb3SPatrick Williams if not dep[0].endswith("-native") and not dep[0] == pn])) 26492b42cb3SPatrick Williams 26592b42cb3SPatrick Williams # To verify what was deployed it checks the rootfs dependencies against 26692b42cb3SPatrick Williams # the SSTATE_MANIFESTS for "deploy" task. 26792b42cb3SPatrick Williams # The manifest file name contains the arch. Because we are not running 26892b42cb3SPatrick Williams # in the recipe context it is necessary to check every arch used. 26992b42cb3SPatrick Williams sstate_manifest_dir = d.getVar("SSTATE_MANIFESTS") 27092b42cb3SPatrick Williams archs = list(set(d.getVar("SSTATE_ARCHS").split())) 27192b42cb3SPatrick Williams for dep in depends: 27292b42cb3SPatrick Williams for arch in archs: 27392b42cb3SPatrick Williams sstate_manifest_file = os.path.join(sstate_manifest_dir, 27492b42cb3SPatrick Williams "manifest-%s-%s.deploy" % (arch, dep)) 27592b42cb3SPatrick Williams if os.path.exists(sstate_manifest_file): 27692b42cb3SPatrick Williams deploy[dep] = sstate_manifest_file 27792b42cb3SPatrick Williams break 27892b42cb3SPatrick Williams 27992b42cb3SPatrick Williams return deploy 280220dafdbSAndrew Geisslerget_deployed_dependencies[vardepsexclude] = "BB_TASKDEPDATA SSTATE_ARCHS" 28192b42cb3SPatrick Williams 28292b42cb3SPatrick Williamsdef get_deployed_files(man_file): 28392b42cb3SPatrick Williams """ 28492b42cb3SPatrick Williams Get the files deployed from the sstate manifest 28592b42cb3SPatrick Williams """ 28692b42cb3SPatrick Williams 28792b42cb3SPatrick Williams dep_files = [] 28892b42cb3SPatrick Williams excluded_files = [] 28992b42cb3SPatrick Williams with open(man_file, "r") as manifest: 29092b42cb3SPatrick Williams all_files = manifest.read() 29192b42cb3SPatrick Williams for f in all_files.splitlines(): 29292b42cb3SPatrick Williams if ((not (os.path.islink(f) or os.path.isdir(f))) and 29392b42cb3SPatrick Williams not os.path.basename(f) in excluded_files): 29492b42cb3SPatrick Williams dep_files.append(os.path.basename(f)) 29592b42cb3SPatrick Williams return dep_files 29692b42cb3SPatrick Williams 2975082cc7fSAndrew GeisslerROOTFS_POSTPROCESS_COMMAND:prepend = "write_package_manifest license_create_manifest " 29892b42cb3SPatrick Williamsdo_rootfs[recrdeptask] += "do_populate_lic" 29992b42cb3SPatrick Williams 30092b42cb3SPatrick Williamspython do_populate_lic_deploy() { 30192b42cb3SPatrick Williams license_deployed_manifest(d) 30292b42cb3SPatrick Williams oe.qa.exit_if_errors(d) 30392b42cb3SPatrick Williams} 30492b42cb3SPatrick Williams 30592b42cb3SPatrick Williamsaddtask populate_lic_deploy before do_build after do_image_complete 30692b42cb3SPatrick Williamsdo_populate_lic_deploy[recrdeptask] += "do_populate_lic do_deploy" 30792b42cb3SPatrick Williams 30892b42cb3SPatrick Williamspython license_qa_dead_symlink() { 30992b42cb3SPatrick Williams import os 31092b42cb3SPatrick Williams 31192b42cb3SPatrick Williams for root, dirs, files in os.walk(d.getVar('ROOTFS_LICENSE_DIR')): 31292b42cb3SPatrick Williams for file in files: 31392b42cb3SPatrick Williams full_path = root + "/" + file 31492b42cb3SPatrick Williams if os.path.islink(full_path) and not os.path.exists(full_path): 31592b42cb3SPatrick Williams bb.error("broken symlink: " + full_path) 31692b42cb3SPatrick Williams} 31792b42cb3SPatrick WilliamsIMAGE_QA_COMMANDS += "license_qa_dead_symlink" 318