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}/${SSTATE_PKGARCH}/${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('SSTATE_PKGARCH'), 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 = oe.license.expand_wildcard_licenses(d, bad_licenses) 62 pkgarchs = d.getVar("SSTATE_ARCHS").split() 63 pkgarchs.reverse() 64 65 exceptions = (d.getVar("INCOMPATIBLE_LICENSE_EXCEPTIONS") or "").split() 66 with open(license_manifest, "w") as license_file: 67 for pkg in sorted(pkg_dic): 68 remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions) 69 incompatible_licenses = oe.license.incompatible_pkg_license(d, remaining_bad_licenses, pkg_dic[pkg]["LICENSE"]) 70 if incompatible_licenses: 71 bb.fatal("Package %s cannot be installed into the image because it has incompatible license(s): %s" %(pkg, ' '.join(incompatible_licenses))) 72 else: 73 incompatible_licenses = oe.license.incompatible_pkg_license(d, bad_licenses, pkg_dic[pkg]["LICENSE"]) 74 if incompatible_licenses: 75 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) 76 try: 77 (pkg_dic[pkg]["LICENSE"], pkg_dic[pkg]["LICENSES"]) = \ 78 oe.license.manifest_licenses(pkg_dic[pkg]["LICENSE"], 79 remaining_bad_licenses, oe.license.canonical_license, d) 80 except oe.license.LicenseError as exc: 81 bb.fatal('%s: %s' % (d.getVar('P'), exc)) 82 83 if not "IMAGE_MANIFEST" in pkg_dic[pkg]: 84 # Rootfs manifest 85 license_file.write("PACKAGE NAME: %s\n" % pkg) 86 license_file.write("PACKAGE VERSION: %s\n" % pkg_dic[pkg]["PV"]) 87 license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"]) 88 license_file.write("LICENSE: %s\n\n" % pkg_dic[pkg]["LICENSE"]) 89 90 # If the package doesn't contain any file, that is, its size is 0, the license 91 # isn't relevant as far as the final image is concerned. So doing license check 92 # doesn't make much sense, skip it. 93 if pkg_dic[pkg]["PKGSIZE:%s" % pkg] == "0": 94 continue 95 else: 96 # Image manifest 97 license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"]) 98 license_file.write("VERSION: %s\n" % pkg_dic[pkg]["PV"]) 99 license_file.write("LICENSE: %s\n" % pkg_dic[pkg]["LICENSE"]) 100 license_file.write("FILES: %s\n\n" % pkg_dic[pkg]["FILES"]) 101 102 for lic in pkg_dic[pkg]["LICENSES"]: 103 for pkgarch in pkgarchs: 104 lic_file = os.path.join(d.getVar('LICENSE_DIRECTORY'), 105 pkgarch, 106 pkg_dic[pkg]["PN"], "generic_%s" % 107 re.sub(r'\+', '', lic)) 108 if os.path.exists(lic_file): 109 break 110 # add explicity avoid of CLOSED license because isn't generic 111 if lic == "CLOSED": 112 continue 113 114 if not os.path.exists(lic_file): 115 oe.qa.handle_error('license-file-missing', 116 "The license listed %s was not in the "\ 117 "licenses collected for recipe %s" 118 % (lic, pkg_dic[pkg]["PN"]), d) 119 oe.qa.exit_if_errors(d) 120 121 # Two options here: 122 # - Just copy the manifest 123 # - Copy the manifest and the license directories 124 # With both options set we see a .5 M increase in core-image-minimal 125 copy_lic_manifest = d.getVar('COPY_LIC_MANIFEST') 126 copy_lic_dirs = d.getVar('COPY_LIC_DIRS') 127 if rootfs and copy_lic_manifest == "1": 128 rootfs_license_dir = d.getVar('ROOTFS_LICENSE_DIR') 129 bb.utils.mkdirhier(rootfs_license_dir) 130 rootfs_license_manifest = os.path.join(rootfs_license_dir, 131 os.path.split(license_manifest)[1]) 132 if not os.path.exists(rootfs_license_manifest): 133 oe.path.copyhardlink(license_manifest, rootfs_license_manifest) 134 135 if copy_lic_dirs == "1": 136 for pkg in sorted(pkg_dic): 137 pkg_rootfs_license_dir = os.path.join(rootfs_license_dir, pkg) 138 bb.utils.mkdirhier(pkg_rootfs_license_dir) 139 for pkgarch in pkgarchs: 140 pkg_license_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), 141 pkgarch, pkg_dic[pkg]["PN"]) 142 if os.path.exists(pkg_license_dir): 143 break 144 if not os.path.exists(pkg_license_dir ): 145 bb.fatal("Couldn't find license information for dependency %s" % pkg) 146 147 pkg_manifest_licenses = [oe.license.canonical_license(d, lic) \ 148 for lic in pkg_dic[pkg]["LICENSES"]] 149 150 licenses = os.listdir(pkg_license_dir) 151 for lic in licenses: 152 pkg_license = os.path.join(pkg_license_dir, lic) 153 pkg_rootfs_license = os.path.join(pkg_rootfs_license_dir, lic) 154 155 if re.match(r"^generic_.*$", lic): 156 generic_lic = oe.license.canonical_license(d, 157 re.search(r"^generic_(.*)$", lic).group(1)) 158 159 # Do not copy generic license into package if isn't 160 # declared into LICENSES of the package. 161 if not re.sub(r'\+$', '', generic_lic) in \ 162 [re.sub(r'\+', '', lic) for lic in \ 163 pkg_manifest_licenses]: 164 continue 165 166 if oe.license.license_ok(generic_lic, 167 bad_licenses) == False: 168 continue 169 170 # Make sure we use only canonical name for the license file 171 generic_lic_file = "generic_%s" % generic_lic 172 rootfs_license = os.path.join(rootfs_license_dir, generic_lic_file) 173 if not os.path.exists(rootfs_license): 174 oe.path.copyhardlink(pkg_license, rootfs_license) 175 176 if not os.path.exists(pkg_rootfs_license): 177 os.symlink(os.path.join('..', generic_lic_file), pkg_rootfs_license) 178 else: 179 if (oe.license.license_ok(oe.license.canonical_license(d, 180 lic), bad_licenses) == False or 181 os.path.exists(pkg_rootfs_license)): 182 continue 183 184 oe.path.copyhardlink(pkg_license, pkg_rootfs_license) 185 # Fixup file ownership and permissions 186 for walkroot, dirs, files in os.walk(rootfs_license_dir): 187 for f in files: 188 p = os.path.join(walkroot, f) 189 os.lchown(p, 0, 0) 190 if not os.path.islink(p): 191 os.chmod(p, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) 192 for dir in dirs: 193 p = os.path.join(walkroot, dir) 194 os.lchown(p, 0, 0) 195 os.chmod(p, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) 196 197write_license_files[vardepsexclude] = "SSTATE_ARCHS" 198 199def license_deployed_manifest(d): 200 """ 201 Write the license manifest for the deployed recipes. 202 The deployed recipes usually includes the bootloader 203 and extra files to boot the target. 204 """ 205 206 dep_dic = {} 207 man_dic = {} 208 lic_dir = d.getVar("LICENSE_DIRECTORY") 209 pkgarchs = d.getVar("SSTATE_ARCHS").split() 210 pkgarchs.reverse() 211 212 dep_dic = get_deployed_dependencies(d) 213 for dep in dep_dic.keys(): 214 man_dic[dep] = {} 215 # It is necessary to mark this will be used for image manifest 216 man_dic[dep]["IMAGE_MANIFEST"] = True 217 man_dic[dep]["PN"] = dep 218 man_dic[dep]["FILES"] = \ 219 " ".join(get_deployed_files(dep_dic[dep])) 220 221 for pkgarch in pkgarchs: 222 licfile = os.path.join(lic_dir, pkgarch, dep, "recipeinfo") 223 if os.path.exists(licfile): 224 break 225 if not os.path.exists(licfile): 226 bb.fatal("Couldn't find license information for dependency %s" % dep) 227 with open(licfile, "r") as f: 228 for line in f.readlines(): 229 key,val = line.split(": ", 1) 230 man_dic[dep][key] = val[:-1] 231 232 lic_manifest_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), d.getVar('SSTATE_PKGARCH'), 233 d.getVar('IMAGE_NAME')) 234 bb.utils.mkdirhier(lic_manifest_dir) 235 image_license_manifest = os.path.join(lic_manifest_dir, 'image_license.manifest') 236 write_license_files(d, image_license_manifest, man_dic, rootfs=False) 237 238 link_name = d.getVar('IMAGE_LINK_NAME') 239 if link_name: 240 lic_manifest_symlink_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), d.getVar('SSTATE_PKGARCH'), 241 link_name) 242 # remove old symlink 243 if os.path.islink(lic_manifest_symlink_dir): 244 os.unlink(lic_manifest_symlink_dir) 245 246 # create the image dir symlink 247 if lic_manifest_dir != lic_manifest_symlink_dir: 248 os.symlink(lic_manifest_dir, lic_manifest_symlink_dir) 249 250license_deployed_manifest[vardepsexclude] = "SSTATE_ARCHS" 251 252def get_deployed_dependencies(d): 253 """ 254 Get all the deployed dependencies of an image 255 """ 256 257 deploy = {} 258 # Get all the dependencies for the current task (rootfs). 259 taskdata = d.getVar("BB_TASKDEPDATA", False) 260 pn = d.getVar("PN") 261 depends = list(set([dep[0] for dep 262 in list(taskdata.values()) 263 if not dep[0].endswith("-native") and not dep[0] == pn])) 264 265 # To verify what was deployed it checks the rootfs dependencies against 266 # the SSTATE_MANIFESTS for "deploy" task. 267 # The manifest file name contains the arch. Because we are not running 268 # in the recipe context it is necessary to check every arch used. 269 sstate_manifest_dir = d.getVar("SSTATE_MANIFESTS") 270 archs = list(set(d.getVar("SSTATE_ARCHS").split())) 271 for dep in depends: 272 for arch in archs: 273 sstate_manifest_file = os.path.join(sstate_manifest_dir, 274 "manifest-%s-%s.deploy" % (arch, dep)) 275 if os.path.exists(sstate_manifest_file): 276 deploy[dep] = sstate_manifest_file 277 break 278 279 return deploy 280get_deployed_dependencies[vardepsexclude] = "BB_TASKDEPDATA SSTATE_ARCHS" 281 282def get_deployed_files(man_file): 283 """ 284 Get the files deployed from the sstate manifest 285 """ 286 287 dep_files = [] 288 excluded_files = [] 289 with open(man_file, "r") as manifest: 290 all_files = manifest.read() 291 for f in all_files.splitlines(): 292 if ((not (os.path.islink(f) or os.path.isdir(f))) and 293 not os.path.basename(f) in excluded_files): 294 dep_files.append(os.path.basename(f)) 295 return dep_files 296 297ROOTFS_POSTPROCESS_COMMAND:prepend = "write_package_manifest license_create_manifest " 298do_rootfs[recrdeptask] += "do_populate_lic" 299 300python do_populate_lic_deploy() { 301 license_deployed_manifest(d) 302 oe.qa.exit_if_errors(d) 303} 304 305addtask populate_lic_deploy before do_build after do_image_complete 306do_populate_lic_deploy[recrdeptask] += "do_populate_lic do_deploy" 307 308python license_qa_dead_symlink() { 309 import os 310 311 for root, dirs, files in os.walk(d.getVar('ROOTFS_LICENSE_DIR')): 312 for file in files: 313 full_path = root + "/" + file 314 if os.path.islink(full_path) and not os.path.exists(full_path): 315 bb.error("broken symlink: " + full_path) 316} 317IMAGE_QA_COMMANDS += "license_qa_dead_symlink" 318