1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: MIT 5# 6 7ROOTFS_LICENSE_DIR = "${IMAGE_ROOTFS}/usr/share/common-licenses" 8 9# This requires LICENSE_CREATE_PACKAGE=1 to work too 10COMPLEMENTARY_GLOB[lic-pkgs] = "*-lic" 11 12python() { 13 if not oe.data.typed_value('LICENSE_CREATE_PACKAGE', d): 14 features = set(oe.data.typed_value('IMAGE_FEATURES', d)) 15 if 'lic-pkgs' in features: 16 bb.error("'lic-pkgs' in IMAGE_FEATURES but LICENSE_CREATE_PACKAGE not enabled to generate -lic packages") 17} 18 19python write_package_manifest() { 20 # Get list of installed packages 21 license_image_dir = d.expand('${LICENSE_DIRECTORY}/${IMAGE_NAME}') 22 bb.utils.mkdirhier(license_image_dir) 23 from oe.rootfs import image_list_installed_packages 24 from oe.utils import format_pkg_list 25 26 pkgs = image_list_installed_packages(d) 27 output = format_pkg_list(pkgs) 28 with open(os.path.join(license_image_dir, 'package.manifest'), "w+") as package_manifest: 29 package_manifest.write(output) 30} 31 32python license_create_manifest() { 33 import oe.packagedata 34 from oe.rootfs import image_list_installed_packages 35 36 build_images_from_feeds = d.getVar('BUILD_IMAGES_FROM_FEEDS') 37 if build_images_from_feeds == "1": 38 return 0 39 40 pkg_dic = {} 41 for pkg in sorted(image_list_installed_packages(d)): 42 pkg_info = os.path.join(d.getVar('PKGDATA_DIR'), 43 'runtime-reverse', pkg) 44 pkg_name = os.path.basename(os.readlink(pkg_info)) 45 46 pkg_dic[pkg_name] = oe.packagedata.read_pkgdatafile(pkg_info) 47 if not "LICENSE" in pkg_dic[pkg_name].keys(): 48 pkg_lic_name = "LICENSE:" + pkg_name 49 pkg_dic[pkg_name]["LICENSE"] = pkg_dic[pkg_name][pkg_lic_name] 50 51 rootfs_license_manifest = os.path.join(d.getVar('LICENSE_DIRECTORY'), 52 d.getVar('IMAGE_NAME'), 'license.manifest') 53 write_license_files(d, rootfs_license_manifest, pkg_dic, rootfs=True) 54} 55 56def write_license_files(d, license_manifest, pkg_dic, rootfs=True): 57 import re 58 import stat 59 60 bad_licenses = (d.getVar("INCOMPATIBLE_LICENSE") or "").split() 61 bad_licenses = expand_wildcard_licenses(d, bad_licenses) 62 63 exceptions = (d.getVar("INCOMPATIBLE_LICENSE_EXCEPTIONS") or "").split() 64 with open(license_manifest, "w") as license_file: 65 for pkg in sorted(pkg_dic): 66 remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions) 67 incompatible_licenses = incompatible_pkg_license(d, remaining_bad_licenses, pkg_dic[pkg]["LICENSE"]) 68 if incompatible_licenses: 69 bb.fatal("Package %s cannot be installed into the image because it has incompatible license(s): %s" %(pkg, ' '.join(incompatible_licenses))) 70 else: 71 incompatible_licenses = incompatible_pkg_license(d, bad_licenses, pkg_dic[pkg]["LICENSE"]) 72 if incompatible_licenses: 73 oe.qa.handle_error('license-incompatible', "Including %s with incompatible license(s) %s into the image, because it has been allowed by exception list." %(pkg, ' '.join(incompatible_licenses)), d) 74 try: 75 (pkg_dic[pkg]["LICENSE"], pkg_dic[pkg]["LICENSES"]) = \ 76 oe.license.manifest_licenses(pkg_dic[pkg]["LICENSE"], 77 remaining_bad_licenses, canonical_license, d) 78 except oe.license.LicenseError as exc: 79 bb.fatal('%s: %s' % (d.getVar('P'), exc)) 80 81 if not "IMAGE_MANIFEST" in pkg_dic[pkg]: 82 # Rootfs manifest 83 license_file.write("PACKAGE NAME: %s\n" % pkg) 84 license_file.write("PACKAGE VERSION: %s\n" % pkg_dic[pkg]["PV"]) 85 license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"]) 86 license_file.write("LICENSE: %s\n\n" % pkg_dic[pkg]["LICENSE"]) 87 88 # If the package doesn't contain any file, that is, its size is 0, the license 89 # isn't relevant as far as the final image is concerned. So doing license check 90 # doesn't make much sense, skip it. 91 if pkg_dic[pkg]["PKGSIZE:%s" % pkg] == "0": 92 continue 93 else: 94 # Image manifest 95 license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"]) 96 license_file.write("VERSION: %s\n" % pkg_dic[pkg]["PV"]) 97 license_file.write("LICENSE: %s\n" % pkg_dic[pkg]["LICENSE"]) 98 license_file.write("FILES: %s\n\n" % pkg_dic[pkg]["FILES"]) 99 100 for lic in pkg_dic[pkg]["LICENSES"]: 101 lic_file = os.path.join(d.getVar('LICENSE_DIRECTORY'), 102 pkg_dic[pkg]["PN"], "generic_%s" % 103 re.sub(r'\+', '', lic)) 104 # add explicity avoid of CLOSED license because isn't generic 105 if lic == "CLOSED": 106 continue 107 108 if not os.path.exists(lic_file): 109 oe.qa.handle_error('license-file-missing', 110 "The license listed %s was not in the "\ 111 "licenses collected for recipe %s" 112 % (lic, pkg_dic[pkg]["PN"]), d) 113 oe.qa.exit_if_errors(d) 114 115 # Two options here: 116 # - Just copy the manifest 117 # - Copy the manifest and the license directories 118 # With both options set we see a .5 M increase in core-image-minimal 119 copy_lic_manifest = d.getVar('COPY_LIC_MANIFEST') 120 copy_lic_dirs = d.getVar('COPY_LIC_DIRS') 121 if rootfs and copy_lic_manifest == "1": 122 rootfs_license_dir = d.getVar('ROOTFS_LICENSE_DIR') 123 bb.utils.mkdirhier(rootfs_license_dir) 124 rootfs_license_manifest = os.path.join(rootfs_license_dir, 125 os.path.split(license_manifest)[1]) 126 if not os.path.exists(rootfs_license_manifest): 127 oe.path.copyhardlink(license_manifest, rootfs_license_manifest) 128 129 if copy_lic_dirs == "1": 130 for pkg in sorted(pkg_dic): 131 pkg_rootfs_license_dir = os.path.join(rootfs_license_dir, pkg) 132 bb.utils.mkdirhier(pkg_rootfs_license_dir) 133 pkg_license_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), 134 pkg_dic[pkg]["PN"]) 135 136 pkg_manifest_licenses = [canonical_license(d, lic) \ 137 for lic in pkg_dic[pkg]["LICENSES"]] 138 139 licenses = os.listdir(pkg_license_dir) 140 for lic in licenses: 141 pkg_license = os.path.join(pkg_license_dir, lic) 142 pkg_rootfs_license = os.path.join(pkg_rootfs_license_dir, lic) 143 144 if re.match(r"^generic_.*$", lic): 145 generic_lic = canonical_license(d, 146 re.search(r"^generic_(.*)$", lic).group(1)) 147 148 # Do not copy generic license into package if isn't 149 # declared into LICENSES of the package. 150 if not re.sub(r'\+$', '', generic_lic) in \ 151 [re.sub(r'\+', '', lic) for lic in \ 152 pkg_manifest_licenses]: 153 continue 154 155 if oe.license.license_ok(generic_lic, 156 bad_licenses) == False: 157 continue 158 159 # Make sure we use only canonical name for the license file 160 generic_lic_file = "generic_%s" % generic_lic 161 rootfs_license = os.path.join(rootfs_license_dir, generic_lic_file) 162 if not os.path.exists(rootfs_license): 163 oe.path.copyhardlink(pkg_license, rootfs_license) 164 165 if not os.path.exists(pkg_rootfs_license): 166 os.symlink(os.path.join('..', generic_lic_file), pkg_rootfs_license) 167 else: 168 if (oe.license.license_ok(canonical_license(d, 169 lic), bad_licenses) == False or 170 os.path.exists(pkg_rootfs_license)): 171 continue 172 173 oe.path.copyhardlink(pkg_license, pkg_rootfs_license) 174 # Fixup file ownership and permissions 175 for walkroot, dirs, files in os.walk(rootfs_license_dir): 176 for f in files: 177 p = os.path.join(walkroot, f) 178 os.lchown(p, 0, 0) 179 if not os.path.islink(p): 180 os.chmod(p, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) 181 for dir in dirs: 182 p = os.path.join(walkroot, dir) 183 os.lchown(p, 0, 0) 184 os.chmod(p, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) 185 186 187 188def license_deployed_manifest(d): 189 """ 190 Write the license manifest for the deployed recipes. 191 The deployed recipes usually includes the bootloader 192 and extra files to boot the target. 193 """ 194 195 dep_dic = {} 196 man_dic = {} 197 lic_dir = d.getVar("LICENSE_DIRECTORY") 198 199 dep_dic = get_deployed_dependencies(d) 200 for dep in dep_dic.keys(): 201 man_dic[dep] = {} 202 # It is necessary to mark this will be used for image manifest 203 man_dic[dep]["IMAGE_MANIFEST"] = True 204 man_dic[dep]["PN"] = dep 205 man_dic[dep]["FILES"] = \ 206 " ".join(get_deployed_files(dep_dic[dep])) 207 with open(os.path.join(lic_dir, dep, "recipeinfo"), "r") as f: 208 for line in f.readlines(): 209 key,val = line.split(": ", 1) 210 man_dic[dep][key] = val[:-1] 211 212 lic_manifest_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), 213 d.getVar('IMAGE_NAME')) 214 bb.utils.mkdirhier(lic_manifest_dir) 215 image_license_manifest = os.path.join(lic_manifest_dir, 'image_license.manifest') 216 write_license_files(d, image_license_manifest, man_dic, rootfs=False) 217 218 link_name = d.getVar('IMAGE_LINK_NAME') 219 if link_name: 220 lic_manifest_symlink_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), 221 link_name) 222 # remove old symlink 223 if os.path.islink(lic_manifest_symlink_dir): 224 os.unlink(lic_manifest_symlink_dir) 225 226 # create the image dir symlink 227 if lic_manifest_dir != lic_manifest_symlink_dir: 228 os.symlink(lic_manifest_dir, lic_manifest_symlink_dir) 229 230def get_deployed_dependencies(d): 231 """ 232 Get all the deployed dependencies of an image 233 """ 234 235 deploy = {} 236 # Get all the dependencies for the current task (rootfs). 237 taskdata = d.getVar("BB_TASKDEPDATA", False) 238 pn = d.getVar("PN") 239 depends = list(set([dep[0] for dep 240 in list(taskdata.values()) 241 if not dep[0].endswith("-native") and not dep[0] == pn])) 242 243 # To verify what was deployed it checks the rootfs dependencies against 244 # the SSTATE_MANIFESTS for "deploy" task. 245 # The manifest file name contains the arch. Because we are not running 246 # in the recipe context it is necessary to check every arch used. 247 sstate_manifest_dir = d.getVar("SSTATE_MANIFESTS") 248 archs = list(set(d.getVar("SSTATE_ARCHS").split())) 249 for dep in depends: 250 for arch in archs: 251 sstate_manifest_file = os.path.join(sstate_manifest_dir, 252 "manifest-%s-%s.deploy" % (arch, dep)) 253 if os.path.exists(sstate_manifest_file): 254 deploy[dep] = sstate_manifest_file 255 break 256 257 return deploy 258get_deployed_dependencies[vardepsexclude] = "BB_TASKDEPDATA" 259 260def get_deployed_files(man_file): 261 """ 262 Get the files deployed from the sstate manifest 263 """ 264 265 dep_files = [] 266 excluded_files = [] 267 with open(man_file, "r") as manifest: 268 all_files = manifest.read() 269 for f in all_files.splitlines(): 270 if ((not (os.path.islink(f) or os.path.isdir(f))) and 271 not os.path.basename(f) in excluded_files): 272 dep_files.append(os.path.basename(f)) 273 return dep_files 274 275ROOTFS_POSTPROCESS_COMMAND:prepend = "write_package_manifest; license_create_manifest; " 276do_rootfs[recrdeptask] += "do_populate_lic" 277 278python do_populate_lic_deploy() { 279 license_deployed_manifest(d) 280 oe.qa.exit_if_errors(d) 281} 282 283addtask populate_lic_deploy before do_build after do_image_complete 284do_populate_lic_deploy[recrdeptask] += "do_populate_lic do_deploy" 285 286python license_qa_dead_symlink() { 287 import os 288 289 for root, dirs, files in os.walk(d.getVar('ROOTFS_LICENSE_DIR')): 290 for file in files: 291 full_path = root + "/" + file 292 if os.path.islink(full_path) and not os.path.exists(full_path): 293 bb.error("broken symlink: " + full_path) 294} 295IMAGE_QA_COMMANDS += "license_qa_dead_symlink" 296