1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: MIT 5# 6 7# Populates LICENSE_DIRECTORY as set in distro config with the license files as set by 8# LIC_FILES_CHKSUM. 9# TODO: 10# - There is a real issue revolving around license naming standards. 11 12LICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses" 13LICSSTATEDIR = "${WORKDIR}/license-destdir/" 14 15# Create extra package with license texts and add it to RRECOMMENDS:${PN} 16LICENSE_CREATE_PACKAGE[type] = "boolean" 17LICENSE_CREATE_PACKAGE ??= "0" 18LICENSE_PACKAGE_SUFFIX ??= "-lic" 19LICENSE_FILES_DIRECTORY ??= "${datadir}/licenses/" 20 21LICENSE_DEPLOY_PATHCOMPONENT = "${SSTATE_PKGARCH}" 22LICENSE_DEPLOY_PATHCOMPONENT:class-cross = "native" 23LICENSE_DEPLOY_PATHCOMPONENT:class-native = "native" 24# Ensure the *value* of SSTATE_PKGARCH is captured as it is used in the output paths 25LICENSE_DEPLOY_PATHCOMPONENT[vardepvalue] += "${LICENSE_DEPLOY_PATHCOMPONENT}" 26 27addtask populate_lic after do_patch before do_build 28do_populate_lic[dirs] = "${LICSSTATEDIR}/${LICENSE_DEPLOY_PATHCOMPONENT}/${PN}" 29do_populate_lic[cleandirs] = "${LICSSTATEDIR}" 30 31python do_populate_lic() { 32 """ 33 Populate LICENSE_DIRECTORY with licenses. 34 """ 35 lic_files_paths = find_license_files(d) 36 37 # The base directory we wrangle licenses to 38 destdir = os.path.join(d.getVar('LICSSTATEDIR'), d.getVar('LICENSE_DEPLOY_PATHCOMPONENT'), d.getVar('PN')) 39 copy_license_files(lic_files_paths, destdir) 40 info = get_recipe_info(d) 41 with open(os.path.join(destdir, "recipeinfo"), "w") as f: 42 for key in sorted(info.keys()): 43 f.write("%s: %s\n" % (key, info[key])) 44 oe.qa.exit_if_errors(d) 45} 46 47# it would be better to copy them in do_install:append, but find_license_files is python 48python perform_packagecopy:prepend () { 49 enabled = oe.data.typed_value('LICENSE_CREATE_PACKAGE', d) 50 if d.getVar('CLASSOVERRIDE') == 'class-target' and enabled: 51 lic_files_paths = find_license_files(d) 52 53 # LICENSE_FILES_DIRECTORY starts with '/' so os.path.join cannot be used to join D and LICENSE_FILES_DIRECTORY 54 destdir = d.getVar('D') + os.path.join(d.getVar('LICENSE_FILES_DIRECTORY'), d.getVar('PN')) 55 copy_license_files(lic_files_paths, destdir) 56 add_package_and_files(d) 57} 58perform_packagecopy[vardeps] += "LICENSE_CREATE_PACKAGE" 59 60def get_recipe_info(d): 61 info = {} 62 info["PV"] = d.getVar("PV") 63 info["PR"] = d.getVar("PR") 64 info["LICENSE"] = d.getVar("LICENSE") 65 return info 66 67def add_package_and_files(d): 68 packages = d.getVar('PACKAGES') 69 files = d.getVar('LICENSE_FILES_DIRECTORY') 70 pn = d.getVar('PN') 71 pn_lic = "%s%s" % (pn, d.getVar('LICENSE_PACKAGE_SUFFIX', False)) 72 if pn_lic in packages.split(): 73 bb.warn("%s package already existed in %s." % (pn_lic, pn)) 74 else: 75 # first in PACKAGES to be sure that nothing else gets LICENSE_FILES_DIRECTORY 76 d.setVar('PACKAGES', "%s %s" % (pn_lic, packages)) 77 d.setVar('FILES:' + pn_lic, files) 78 79def copy_license_files(lic_files_paths, destdir): 80 import shutil 81 import errno 82 83 bb.utils.mkdirhier(destdir) 84 for (basename, path, beginline, endline) in lic_files_paths: 85 try: 86 src = path 87 dst = os.path.join(destdir, basename) 88 if os.path.exists(dst): 89 os.remove(dst) 90 if os.path.islink(src): 91 src = os.path.realpath(src) 92 canlink = os.access(src, os.W_OK) and (os.stat(src).st_dev == os.stat(destdir).st_dev) and beginline is None and endline is None 93 if canlink: 94 try: 95 os.link(src, dst) 96 except OSError as err: 97 if err.errno == errno.EXDEV: 98 # Copy license files if hardlink is not possible even if st_dev is the 99 # same on source and destination (docker container with device-mapper?) 100 canlink = False 101 else: 102 raise 103 # Only chown if we did hardlink and we're running under pseudo 104 if canlink and os.environ.get('PSEUDO_DISABLED') == '0': 105 os.chown(dst,0,0) 106 if not canlink: 107 begin_idx = max(0, int(beginline) - 1) if beginline is not None else None 108 end_idx = max(0, int(endline)) if endline is not None else None 109 if begin_idx is None and end_idx is None: 110 shutil.copyfile(src, dst) 111 else: 112 with open(src, 'rb') as src_f: 113 with open(dst, 'wb') as dst_f: 114 dst_f.write(b''.join(src_f.readlines()[begin_idx:end_idx])) 115 116 except Exception as e: 117 bb.warn("Could not copy license file %s to %s: %s" % (src, dst, e)) 118 119def find_license_files(d): 120 """ 121 Creates list of files used in LIC_FILES_CHKSUM and generic LICENSE files. 122 """ 123 import shutil 124 import oe.license 125 from collections import defaultdict, OrderedDict 126 127 # All the license files for the package 128 lic_files = d.getVar('LIC_FILES_CHKSUM') or "" 129 pn = d.getVar('PN') 130 # The license files are located in S/LIC_FILE_CHECKSUM. 131 srcdir = d.getVar('S') 132 # Directory we store the generic licenses as set in the distro configuration 133 generic_directory = d.getVar('COMMON_LICENSE_DIR') 134 # List of basename, path tuples 135 lic_files_paths = [] 136 # hash for keep track generic lics mappings 137 non_generic_lics = {} 138 # Entries from LIC_FILES_CHKSUM 139 lic_chksums = {} 140 license_source_dirs = [] 141 license_source_dirs.append(generic_directory) 142 try: 143 additional_lic_dirs = d.getVar('LICENSE_PATH').split() 144 for lic_dir in additional_lic_dirs: 145 license_source_dirs.append(lic_dir) 146 except: 147 pass 148 149 class FindVisitor(oe.license.LicenseVisitor): 150 def visit_Str(self, node): 151 # 152 # Until I figure out what to do with 153 # the two modifiers I support (or greater = + 154 # and "with exceptions" being * 155 # we'll just strip out the modifier and put 156 # the base license. 157 find_licenses(node.s.replace("+", "").replace("*", "")) 158 self.generic_visit(node) 159 160 def visit_Constant(self, node): 161 find_licenses(node.value.replace("+", "").replace("*", "")) 162 self.generic_visit(node) 163 164 def find_licenses(license_type): 165 try: 166 bb.utils.mkdirhier(gen_lic_dest) 167 except: 168 pass 169 spdx_generic = None 170 license_source = None 171 # If the generic does not exist we need to check to see if there is an SPDX mapping to it, 172 # unless NO_GENERIC_LICENSE is set. 173 for lic_dir in license_source_dirs: 174 if not os.path.isfile(os.path.join(lic_dir, license_type)): 175 if d.getVarFlag('SPDXLICENSEMAP', license_type) != None: 176 # Great, there is an SPDXLICENSEMAP. We can copy! 177 bb.debug(1, "We need to use a SPDXLICENSEMAP for %s" % (license_type)) 178 spdx_generic = d.getVarFlag('SPDXLICENSEMAP', license_type) 179 license_source = lic_dir 180 break 181 elif os.path.isfile(os.path.join(lic_dir, license_type)): 182 spdx_generic = license_type 183 license_source = lic_dir 184 break 185 186 non_generic_lic = d.getVarFlag('NO_GENERIC_LICENSE', license_type) 187 if spdx_generic and license_source: 188 # we really should copy to generic_ + spdx_generic, however, that ends up messing the manifest 189 # audit up. This should be fixed in emit_pkgdata (or, we actually got and fix all the recipes) 190 191 lic_files_paths.append(("generic_" + license_type, os.path.join(license_source, spdx_generic), 192 None, None)) 193 194 # The user may attempt to use NO_GENERIC_LICENSE for a generic license which doesn't make sense 195 # and should not be allowed, warn the user in this case. 196 if d.getVarFlag('NO_GENERIC_LICENSE', license_type): 197 oe.qa.handle_error("license-no-generic", 198 "%s: %s is a generic license, please don't use NO_GENERIC_LICENSE for it." % (pn, license_type), d) 199 200 elif non_generic_lic and non_generic_lic in lic_chksums: 201 # if NO_GENERIC_LICENSE is set, we copy the license files from the fetched source 202 # of the package rather than the license_source_dirs. 203 lic_files_paths.append(("generic_" + license_type, 204 os.path.join(srcdir, non_generic_lic), None, None)) 205 non_generic_lics[non_generic_lic] = license_type 206 else: 207 # Explicitly avoid the CLOSED license because this isn't generic 208 if license_type != 'CLOSED': 209 # And here is where we warn people that their licenses are lousy 210 oe.qa.handle_error("license-exists", 211 "%s: No generic license file exists for: %s in any provider" % (pn, license_type), d) 212 pass 213 214 if not generic_directory: 215 bb.fatal("COMMON_LICENSE_DIR is unset. Please set this in your distro config") 216 217 for url in lic_files.split(): 218 try: 219 (method, host, path, user, pswd, parm) = bb.fetch.decodeurl(url) 220 if method != "file" or not path: 221 raise bb.fetch.MalformedUrl() 222 except bb.fetch.MalformedUrl: 223 bb.fatal("%s: LIC_FILES_CHKSUM contains an invalid URL: %s" % (d.getVar('PF'), url)) 224 # We want the license filename and path 225 chksum = parm.get('md5', None) 226 beginline = parm.get('beginline') 227 endline = parm.get('endline') 228 lic_chksums[path] = (chksum, beginline, endline) 229 230 v = FindVisitor() 231 try: 232 v.visit_string(d.getVar('LICENSE')) 233 except oe.license.InvalidLicense as exc: 234 bb.fatal('%s: %s' % (d.getVar('PF'), exc)) 235 except SyntaxError: 236 oe.qa.handle_error("license-syntax", 237 "%s: Failed to parse LICENSE: %s" % (d.getVar('PF'), d.getVar('LICENSE')), d) 238 # Add files from LIC_FILES_CHKSUM to list of license files 239 lic_chksum_paths = defaultdict(OrderedDict) 240 for path, data in sorted(lic_chksums.items()): 241 lic_chksum_paths[os.path.basename(path)][data] = (os.path.join(srcdir, path), data[1], data[2]) 242 for basename, files in lic_chksum_paths.items(): 243 if len(files) == 1: 244 # Don't copy again a LICENSE already handled as non-generic 245 if basename in non_generic_lics: 246 continue 247 data = list(files.values())[0] 248 lic_files_paths.append(tuple([basename] + list(data))) 249 else: 250 # If there are multiple different license files with identical 251 # basenames we rename them to <file>.0, <file>.1, ... 252 for i, data in enumerate(files.values()): 253 lic_files_paths.append(tuple(["%s.%d" % (basename, i)] + list(data))) 254 255 return lic_files_paths 256 257SSTATETASKS += "do_populate_lic" 258do_populate_lic[sstate-inputdirs] = "${LICSSTATEDIR}" 259do_populate_lic[sstate-outputdirs] = "${LICENSE_DIRECTORY}/" 260 261IMAGE_CLASSES:append = " license_image" 262 263python do_populate_lic_setscene () { 264 sstate_setscene(d) 265} 266addtask do_populate_lic_setscene 267