def perform_packagecopy(d): enabled = oe.data.typed_value('LICENSE_CREATE_PACKAGE', d) if d.getVar('CLASSOVERRIDE') == 'class-target' and enabled: lic_files_paths = find_license_files(d) # LICENSE_FILES_DIRECTORY starts with '/' so os.path.join cannot be used to join D and LICENSE_FILES_DIRECTORY destdir = d.getVar('D') + os.path.join(d.getVar('LICENSE_FILES_DIRECTORY'), d.getVar('PN')) copy_license_files(lic_files_paths, destdir) add_package_and_files(d) import subprocess dest = d.getVar('D') dvar = d.getVar('PKGD') # Start by package population by taking a copy of the installed # files to operate on # Preserve sparse files and hard links cmd = 'tar -cf - -C %s -p -S . | tar -xf - -C %s' % (dest, dvar) subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) # replace RPATHs for the nativesdk binaries, to make them relocatable if bb.data.inherits_class('nativesdk', d) or bb.data.inherits_class('cross-canadian', d): rpath_replace (dvar, d) perform_packagecopy(d) def find_license_files(d): """ Creates list of files used in LIC_FILES_CHKSUM and generic LICENSE files. """ import shutil import oe.license from collections import defaultdict, OrderedDict # All the license files for the package lic_files = d.getVar('LIC_FILES_CHKSUM') or "" pn = d.getVar('PN') # The license files are located in S/LIC_FILE_CHECKSUM. srcdir = d.getVar('S') # Directory we store the generic licenses as set in the distro configuration generic_directory = d.getVar('COMMON_LICENSE_DIR') # List of basename, path tuples lic_files_paths = [] # hash for keep track generic lics mappings non_generic_lics = {} # Entries from LIC_FILES_CHKSUM lic_chksums = {} license_source_dirs = [] license_source_dirs.append(generic_directory) try: additional_lic_dirs = d.getVar('LICENSE_PATH').split() for lic_dir in additional_lic_dirs: license_source_dirs.append(lic_dir) except: pass class FindVisitor(oe.license.LicenseVisitor): def visit_Str(self, node): # # Until I figure out what to do with # the two modifiers I support (or greater = + # and "with exceptions" being * # we'll just strip out the modifier and put # the base license. find_license(node.s.replace("+", "").replace("*", "")) self.generic_visit(node) def find_license(license_type): try: bb.utils.mkdirhier(gen_lic_dest) except: pass spdx_generic = None license_source = None # If the generic does not exist we need to check to see if there is an SPDX mapping to it, # unless NO_GENERIC_LICENSE is set. for lic_dir in license_source_dirs: if not os.path.isfile(os.path.join(lic_dir, license_type)): if d.getVarFlag('SPDXLICENSEMAP', license_type) != None: # Great, there is an SPDXLICENSEMAP. We can copy! bb.debug(1, "We need to use a SPDXLICENSEMAP for %s" % (license_type)) spdx_generic = d.getVarFlag('SPDXLICENSEMAP', license_type) license_source = lic_dir break elif os.path.isfile(os.path.join(lic_dir, license_type)): spdx_generic = license_type license_source = lic_dir break non_generic_lic = d.getVarFlag('NO_GENERIC_LICENSE', license_type) if spdx_generic and license_source: # we really should copy to generic_ + spdx_generic, however, that ends up messing the manifest # audit up. This should be fixed in emit_pkgdata (or, we actually got and fix all the recipes) lic_files_paths.append(("generic_" + license_type, os.path.join(license_source, spdx_generic), None, None)) # The user may attempt to use NO_GENERIC_LICENSE for a generic license which doesn't make sense # and should not be allowed, warn the user in this case. if d.getVarFlag('NO_GENERIC_LICENSE', license_type): bb.warn("%s: %s is a generic license, please don't use NO_GENERIC_LICENSE for it." % (pn, license_type)) elif non_generic_lic and non_generic_lic in lic_chksums: # if NO_GENERIC_LICENSE is set, we copy the license files from the fetched source # of the package rather than the license_source_dirs. lic_files_paths.append(("generic_" + license_type, os.path.join(srcdir, non_generic_lic), None, None)) non_generic_lics[non_generic_lic] = license_type else: # Add explicity avoid of CLOSED license because this isn't generic if license_type != 'CLOSED': # And here is where we warn people that their licenses are lousy bb.warn("%s: No generic license file exists for: %s in any provider" % (pn, license_type)) pass if not generic_directory: bb.fatal("COMMON_LICENSE_DIR is unset. Please set this in your distro config") for url in lic_files.split(): try: (method, host, path, user, pswd, parm) = bb.fetch.decodeurl(url) if method != "file" or not path: raise bb.fetch.MalformedUrl() except bb.fetch.MalformedUrl: bb.fatal("%s: LIC_FILES_CHKSUM contains an invalid URL: %s" % (d.getVar('PF'), url)) # We want the license filename and path chksum = parm.get('md5', None) beginline = parm.get('beginline') endline = parm.get('endline') lic_chksums[path] = (chksum, beginline, endline) v = FindVisitor() try: v.visit_string(d.getVar('LICENSE')) except oe.license.InvalidLicense as exc: bb.fatal('%s: %s' % (d.getVar('PF'), exc)) except SyntaxError: bb.warn("%s: Failed to parse it's LICENSE field." % (d.getVar('PF'))) # Add files from LIC_FILES_CHKSUM to list of license files lic_chksum_paths = defaultdict(OrderedDict) for path, data in sorted(lic_chksums.items()): lic_chksum_paths[os.path.basename(path)][data] = (os.path.join(srcdir, path), data[1], data[2]) for basename, files in lic_chksum_paths.items(): if len(files) == 1: # Don't copy again a LICENSE already handled as non-generic if basename in non_generic_lics: continue data = list(files.values())[0] lic_files_paths.append(tuple([basename] + list(data))) else: # If there are multiple different license files with identical # basenames we rename them to .0, .1, ... for i, data in enumerate(files.values()): lic_files_paths.append(tuple(["%s.%d" % (basename, i)] + list(data))) return lic_files_paths def add_package_and_files(d): packages = d.getVar('PACKAGES') files = d.getVar('LICENSE_FILES_DIRECTORY') pn = d.getVar('PN') pn_lic = "%s%s" % (pn, d.getVar('LICENSE_PACKAGE_SUFFIX', False)) if pn_lic in packages.split(): bb.warn("%s package already existed in %s." % (pn_lic, pn)) else: # first in PACKAGES to be sure that nothing else gets LICENSE_FILES_DIRECTORY d.setVar('PACKAGES', "%s %s" % (pn_lic, packages)) d.setVar('FILES_' + pn_lic, files) for pn in packages.split(): if pn == pn_lic: continue rrecommends_pn = d.getVar('RRECOMMENDS_' + pn) if rrecommends_pn: d.setVar('RRECOMMENDS_' + pn, "%s %s" % (pn_lic, rrecommends_pn)) else: d.setVar('RRECOMMENDS_' + pn, "%s" % (pn_lic)) def rpath_replace (path, d): bindirs = d.expand("${bindir} ${sbindir} ${base_sbindir} ${base_bindir} ${libdir} ${base_libdir} ${libexecdir} ${PREPROCESS_RELOCATE_DIRS}").split() for bindir in bindirs: #bb.note ("Processing directory " + bindir) directory = path + "/" + bindir process_dir (path, directory, d) def copy_license_files(lic_files_paths, destdir): import shutil import errno bb.utils.mkdirhier(destdir) for (basename, path, beginline, endline) in lic_files_paths: try: src = path dst = os.path.join(destdir, basename) if os.path.exists(dst): os.remove(dst) if os.path.islink(src): src = os.path.realpath(src) 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 if canlink: try: os.link(src, dst) except OSError as err: if err.errno == errno.EXDEV: # Copy license files if hard-link is not possible even if st_dev is the # same on source and destination (docker container with device-mapper?) canlink = False else: raise # Only chown if we did hardling, and, we're running under pseudo if canlink and os.environ.get('PSEUDO_DISABLED') == '0': os.chown(dst,0,0) if not canlink: begin_idx = int(beginline)-1 if beginline is not None else None end_idx = int(endline) if endline is not None else None if begin_idx is None and end_idx is None: shutil.copyfile(src, dst) else: with open(src, 'rb') as src_f: with open(dst, 'wb') as dst_f: dst_f.write(b''.join(src_f.readlines()[begin_idx:end_idx])) except Exception as e: bb.warn("Could not copy license file %s to %s: %s" % (src, dst, e)) def process_dir(rootdir, directory, d, break_hardlinks = False): bb.debug(2, "Checking %s for binaries to process" % directory) if not os.path.exists(directory): return import stat rootdir = os.path.normpath(rootdir) cmd = d.expand('${CHRPATH_BIN}') tmpdir = os.path.normpath(d.getVar('TMPDIR', False)) baseprefix = os.path.normpath(d.expand('${base_prefix}')) hostos = d.getVar("HOST_OS") if "linux" in hostos: process_file = process_file_linux elif "darwin" in hostos: process_file = process_file_darwin else: # Relocations not supported return dirs = os.listdir(directory) for file in dirs: fpath = directory + "/" + file fpath = os.path.normpath(fpath) if os.path.islink(fpath): # Skip symlinks continue if os.path.isdir(fpath): process_dir(rootdir, fpath, d, break_hardlinks = break_hardlinks) else: #bb.note("Testing %s for relocatability" % fpath) # We need read and write permissions for chrpath, if we don't have # them then set them temporarily. Take a copy of the files # permissions so that we can restore them afterwards. perms = os.stat(fpath)[stat.ST_MODE] if os.access(fpath, os.W_OK|os.R_OK): perms = None else: # Temporarily make the file writeable so we can chrpath it os.chmod(fpath, perms|stat.S_IRWXU) process_file(cmd, fpath, rootdir, baseprefix, tmpdir, d, break_hardlinks = break_hardlinks) if perms: os.chmod(fpath, perms)