xref: /OK3568_Linux_fs/yocto/poky/meta/classes/license.bbclass (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Populates LICENSE_DIRECTORY as set in distro config with the license files as set by
2*4882a593Smuzhiyun# LIC_FILES_CHKSUM.
3*4882a593Smuzhiyun# TODO:
4*4882a593Smuzhiyun# - There is a real issue revolving around license naming standards.
5*4882a593Smuzhiyun
6*4882a593SmuzhiyunLICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses"
7*4882a593SmuzhiyunLICSSTATEDIR = "${WORKDIR}/license-destdir/"
8*4882a593Smuzhiyun
9*4882a593Smuzhiyun# Create extra package with license texts and add it to RRECOMMENDS:${PN}
10*4882a593SmuzhiyunLICENSE_CREATE_PACKAGE[type] = "boolean"
11*4882a593SmuzhiyunLICENSE_CREATE_PACKAGE ??= "0"
12*4882a593SmuzhiyunLICENSE_PACKAGE_SUFFIX ??= "-lic"
13*4882a593SmuzhiyunLICENSE_FILES_DIRECTORY ??= "${datadir}/licenses/"
14*4882a593Smuzhiyun
15*4882a593Smuzhiyunaddtask populate_lic after do_patch before do_build
16*4882a593Smuzhiyundo_populate_lic[dirs] = "${LICSSTATEDIR}/${PN}"
17*4882a593Smuzhiyundo_populate_lic[cleandirs] = "${LICSSTATEDIR}"
18*4882a593Smuzhiyun
19*4882a593Smuzhiyunpython do_populate_lic() {
20*4882a593Smuzhiyun    """
21*4882a593Smuzhiyun    Populate LICENSE_DIRECTORY with licenses.
22*4882a593Smuzhiyun    """
23*4882a593Smuzhiyun    lic_files_paths = find_license_files(d)
24*4882a593Smuzhiyun
25*4882a593Smuzhiyun    # The base directory we wrangle licenses to
26*4882a593Smuzhiyun    destdir = os.path.join(d.getVar('LICSSTATEDIR'), d.getVar('PN'))
27*4882a593Smuzhiyun    copy_license_files(lic_files_paths, destdir)
28*4882a593Smuzhiyun    info = get_recipe_info(d)
29*4882a593Smuzhiyun    with open(os.path.join(destdir, "recipeinfo"), "w") as f:
30*4882a593Smuzhiyun        for key in sorted(info.keys()):
31*4882a593Smuzhiyun            f.write("%s: %s\n" % (key, info[key]))
32*4882a593Smuzhiyun    oe.qa.exit_if_errors(d)
33*4882a593Smuzhiyun}
34*4882a593Smuzhiyun
35*4882a593SmuzhiyunPSEUDO_IGNORE_PATHS .= ",${@','.join(((d.getVar('COMMON_LICENSE_DIR') or '') + ' ' + (d.getVar('LICENSE_PATH') or '') + ' ' + d.getVar('COREBASE') + '/meta/COPYING').split())}"
36*4882a593Smuzhiyun# it would be better to copy them in do_install:append, but find_license_filesa is python
37*4882a593Smuzhiyunpython perform_packagecopy:prepend () {
38*4882a593Smuzhiyun    enabled = oe.data.typed_value('LICENSE_CREATE_PACKAGE', d)
39*4882a593Smuzhiyun    if d.getVar('CLASSOVERRIDE') == 'class-target' and enabled:
40*4882a593Smuzhiyun        lic_files_paths = find_license_files(d)
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun        # LICENSE_FILES_DIRECTORY starts with '/' so os.path.join cannot be used to join D and LICENSE_FILES_DIRECTORY
43*4882a593Smuzhiyun        destdir = d.getVar('D') + os.path.join(d.getVar('LICENSE_FILES_DIRECTORY'), d.getVar('PN'))
44*4882a593Smuzhiyun        copy_license_files(lic_files_paths, destdir)
45*4882a593Smuzhiyun        add_package_and_files(d)
46*4882a593Smuzhiyun}
47*4882a593Smuzhiyunperform_packagecopy[vardeps] += "LICENSE_CREATE_PACKAGE"
48*4882a593Smuzhiyun
49*4882a593Smuzhiyundef get_recipe_info(d):
50*4882a593Smuzhiyun    info = {}
51*4882a593Smuzhiyun    info["PV"] = d.getVar("PV")
52*4882a593Smuzhiyun    info["PR"] = d.getVar("PR")
53*4882a593Smuzhiyun    info["LICENSE"] = d.getVar("LICENSE")
54*4882a593Smuzhiyun    return info
55*4882a593Smuzhiyun
56*4882a593Smuzhiyundef add_package_and_files(d):
57*4882a593Smuzhiyun    packages = d.getVar('PACKAGES')
58*4882a593Smuzhiyun    files = d.getVar('LICENSE_FILES_DIRECTORY')
59*4882a593Smuzhiyun    pn = d.getVar('PN')
60*4882a593Smuzhiyun    pn_lic = "%s%s" % (pn, d.getVar('LICENSE_PACKAGE_SUFFIX', False))
61*4882a593Smuzhiyun    if pn_lic in packages.split():
62*4882a593Smuzhiyun        bb.warn("%s package already existed in %s." % (pn_lic, pn))
63*4882a593Smuzhiyun    else:
64*4882a593Smuzhiyun        # first in PACKAGES to be sure that nothing else gets LICENSE_FILES_DIRECTORY
65*4882a593Smuzhiyun        d.setVar('PACKAGES', "%s %s" % (pn_lic, packages))
66*4882a593Smuzhiyun        d.setVar('FILES:' + pn_lic, files)
67*4882a593Smuzhiyun
68*4882a593Smuzhiyundef copy_license_files(lic_files_paths, destdir):
69*4882a593Smuzhiyun    import shutil
70*4882a593Smuzhiyun    import errno
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun    bb.utils.mkdirhier(destdir)
73*4882a593Smuzhiyun    for (basename, path, beginline, endline) in lic_files_paths:
74*4882a593Smuzhiyun        try:
75*4882a593Smuzhiyun            src = path
76*4882a593Smuzhiyun            dst = os.path.join(destdir, basename)
77*4882a593Smuzhiyun            if os.path.exists(dst):
78*4882a593Smuzhiyun                os.remove(dst)
79*4882a593Smuzhiyun            if os.path.islink(src):
80*4882a593Smuzhiyun                src = os.path.realpath(src)
81*4882a593Smuzhiyun            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
82*4882a593Smuzhiyun            if canlink:
83*4882a593Smuzhiyun                try:
84*4882a593Smuzhiyun                    os.link(src, dst)
85*4882a593Smuzhiyun                except OSError as err:
86*4882a593Smuzhiyun                    if err.errno == errno.EXDEV:
87*4882a593Smuzhiyun                        # Copy license files if hardlink is not possible even if st_dev is the
88*4882a593Smuzhiyun                        # same on source and destination (docker container with device-mapper?)
89*4882a593Smuzhiyun                        canlink = False
90*4882a593Smuzhiyun                    else:
91*4882a593Smuzhiyun                        raise
92*4882a593Smuzhiyun                # Only chown if we did hardlink and we're running under pseudo
93*4882a593Smuzhiyun                if canlink and os.environ.get('PSEUDO_DISABLED') == '0':
94*4882a593Smuzhiyun                    os.chown(dst,0,0)
95*4882a593Smuzhiyun            if not canlink:
96*4882a593Smuzhiyun                begin_idx = max(0, int(beginline) - 1) if beginline is not None else None
97*4882a593Smuzhiyun                end_idx = max(0, int(endline)) if endline is not None else None
98*4882a593Smuzhiyun                if begin_idx is None and end_idx is None:
99*4882a593Smuzhiyun                    shutil.copyfile(src, dst)
100*4882a593Smuzhiyun                else:
101*4882a593Smuzhiyun                    with open(src, 'rb') as src_f:
102*4882a593Smuzhiyun                        with open(dst, 'wb') as dst_f:
103*4882a593Smuzhiyun                            dst_f.write(b''.join(src_f.readlines()[begin_idx:end_idx]))
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun        except Exception as e:
106*4882a593Smuzhiyun            bb.warn("Could not copy license file %s to %s: %s" % (src, dst, e))
107*4882a593Smuzhiyun
108*4882a593Smuzhiyundef find_license_files(d):
109*4882a593Smuzhiyun    """
110*4882a593Smuzhiyun    Creates list of files used in LIC_FILES_CHKSUM and generic LICENSE files.
111*4882a593Smuzhiyun    """
112*4882a593Smuzhiyun    import shutil
113*4882a593Smuzhiyun    import oe.license
114*4882a593Smuzhiyun    from collections import defaultdict, OrderedDict
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun    # All the license files for the package
117*4882a593Smuzhiyun    lic_files = d.getVar('LIC_FILES_CHKSUM') or ""
118*4882a593Smuzhiyun    pn = d.getVar('PN')
119*4882a593Smuzhiyun    # The license files are located in S/LIC_FILE_CHECKSUM.
120*4882a593Smuzhiyun    srcdir = d.getVar('S')
121*4882a593Smuzhiyun    # Directory we store the generic licenses as set in the distro configuration
122*4882a593Smuzhiyun    generic_directory = d.getVar('COMMON_LICENSE_DIR')
123*4882a593Smuzhiyun    # List of basename, path tuples
124*4882a593Smuzhiyun    lic_files_paths = []
125*4882a593Smuzhiyun    # hash for keep track generic lics mappings
126*4882a593Smuzhiyun    non_generic_lics = {}
127*4882a593Smuzhiyun    # Entries from LIC_FILES_CHKSUM
128*4882a593Smuzhiyun    lic_chksums = {}
129*4882a593Smuzhiyun    license_source_dirs = []
130*4882a593Smuzhiyun    license_source_dirs.append(generic_directory)
131*4882a593Smuzhiyun    try:
132*4882a593Smuzhiyun        additional_lic_dirs = d.getVar('LICENSE_PATH').split()
133*4882a593Smuzhiyun        for lic_dir in additional_lic_dirs:
134*4882a593Smuzhiyun            license_source_dirs.append(lic_dir)
135*4882a593Smuzhiyun    except:
136*4882a593Smuzhiyun        pass
137*4882a593Smuzhiyun
138*4882a593Smuzhiyun    class FindVisitor(oe.license.LicenseVisitor):
139*4882a593Smuzhiyun        def visit_Str(self, node):
140*4882a593Smuzhiyun            #
141*4882a593Smuzhiyun            # Until I figure out what to do with
142*4882a593Smuzhiyun            # the two modifiers I support (or greater = +
143*4882a593Smuzhiyun            # and "with exceptions" being *
144*4882a593Smuzhiyun            # we'll just strip out the modifier and put
145*4882a593Smuzhiyun            # the base license.
146*4882a593Smuzhiyun            find_license(node.s.replace("+", "").replace("*", ""))
147*4882a593Smuzhiyun            self.generic_visit(node)
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun        def visit_Constant(self, node):
150*4882a593Smuzhiyun            find_license(node.value.replace("+", "").replace("*", ""))
151*4882a593Smuzhiyun            self.generic_visit(node)
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun    def find_license(license_type):
154*4882a593Smuzhiyun        try:
155*4882a593Smuzhiyun            bb.utils.mkdirhier(gen_lic_dest)
156*4882a593Smuzhiyun        except:
157*4882a593Smuzhiyun            pass
158*4882a593Smuzhiyun        spdx_generic = None
159*4882a593Smuzhiyun        license_source = None
160*4882a593Smuzhiyun        # If the generic does not exist we need to check to see if there is an SPDX mapping to it,
161*4882a593Smuzhiyun        # unless NO_GENERIC_LICENSE is set.
162*4882a593Smuzhiyun        for lic_dir in license_source_dirs:
163*4882a593Smuzhiyun            if not os.path.isfile(os.path.join(lic_dir, license_type)):
164*4882a593Smuzhiyun                if d.getVarFlag('SPDXLICENSEMAP', license_type) != None:
165*4882a593Smuzhiyun                    # Great, there is an SPDXLICENSEMAP. We can copy!
166*4882a593Smuzhiyun                    bb.debug(1, "We need to use a SPDXLICENSEMAP for %s" % (license_type))
167*4882a593Smuzhiyun                    spdx_generic = d.getVarFlag('SPDXLICENSEMAP', license_type)
168*4882a593Smuzhiyun                    license_source = lic_dir
169*4882a593Smuzhiyun                    break
170*4882a593Smuzhiyun            elif os.path.isfile(os.path.join(lic_dir, license_type)):
171*4882a593Smuzhiyun                spdx_generic = license_type
172*4882a593Smuzhiyun                license_source = lic_dir
173*4882a593Smuzhiyun                break
174*4882a593Smuzhiyun
175*4882a593Smuzhiyun        non_generic_lic = d.getVarFlag('NO_GENERIC_LICENSE', license_type)
176*4882a593Smuzhiyun        if spdx_generic and license_source:
177*4882a593Smuzhiyun            # we really should copy to generic_ + spdx_generic, however, that ends up messing the manifest
178*4882a593Smuzhiyun            # audit up. This should be fixed in emit_pkgdata (or, we actually got and fix all the recipes)
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun            lic_files_paths.append(("generic_" + license_type, os.path.join(license_source, spdx_generic),
181*4882a593Smuzhiyun                                    None, None))
182*4882a593Smuzhiyun
183*4882a593Smuzhiyun            # The user may attempt to use NO_GENERIC_LICENSE for a generic license which doesn't make sense
184*4882a593Smuzhiyun            # and should not be allowed, warn the user in this case.
185*4882a593Smuzhiyun            if d.getVarFlag('NO_GENERIC_LICENSE', license_type):
186*4882a593Smuzhiyun                oe.qa.handle_error("license-no-generic",
187*4882a593Smuzhiyun                    "%s: %s is a generic license, please don't use NO_GENERIC_LICENSE for it." % (pn, license_type), d)
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun        elif non_generic_lic and non_generic_lic in lic_chksums:
190*4882a593Smuzhiyun            # if NO_GENERIC_LICENSE is set, we copy the license files from the fetched source
191*4882a593Smuzhiyun            # of the package rather than the license_source_dirs.
192*4882a593Smuzhiyun            lic_files_paths.append(("generic_" + license_type,
193*4882a593Smuzhiyun                                    os.path.join(srcdir, non_generic_lic), None, None))
194*4882a593Smuzhiyun            non_generic_lics[non_generic_lic] = license_type
195*4882a593Smuzhiyun        else:
196*4882a593Smuzhiyun            # Explicitly avoid the CLOSED license because this isn't generic
197*4882a593Smuzhiyun            if license_type != 'CLOSED':
198*4882a593Smuzhiyun                # And here is where we warn people that their licenses are lousy
199*4882a593Smuzhiyun                oe.qa.handle_error("license-exists",
200*4882a593Smuzhiyun                    "%s: No generic license file exists for: %s in any provider" % (pn, license_type), d)
201*4882a593Smuzhiyun            pass
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun    if not generic_directory:
204*4882a593Smuzhiyun        bb.fatal("COMMON_LICENSE_DIR is unset. Please set this in your distro config")
205*4882a593Smuzhiyun
206*4882a593Smuzhiyun    for url in lic_files.split():
207*4882a593Smuzhiyun        try:
208*4882a593Smuzhiyun            (method, host, path, user, pswd, parm) = bb.fetch.decodeurl(url)
209*4882a593Smuzhiyun            if method != "file" or not path:
210*4882a593Smuzhiyun                raise bb.fetch.MalformedUrl()
211*4882a593Smuzhiyun        except bb.fetch.MalformedUrl:
212*4882a593Smuzhiyun            bb.fatal("%s: LIC_FILES_CHKSUM contains an invalid URL:  %s" % (d.getVar('PF'), url))
213*4882a593Smuzhiyun        # We want the license filename and path
214*4882a593Smuzhiyun        chksum = parm.get('md5', None)
215*4882a593Smuzhiyun        beginline = parm.get('beginline')
216*4882a593Smuzhiyun        endline = parm.get('endline')
217*4882a593Smuzhiyun        lic_chksums[path] = (chksum, beginline, endline)
218*4882a593Smuzhiyun
219*4882a593Smuzhiyun    v = FindVisitor()
220*4882a593Smuzhiyun    try:
221*4882a593Smuzhiyun        v.visit_string(d.getVar('LICENSE'))
222*4882a593Smuzhiyun    except oe.license.InvalidLicense as exc:
223*4882a593Smuzhiyun        bb.fatal('%s: %s' % (d.getVar('PF'), exc))
224*4882a593Smuzhiyun    except SyntaxError:
225*4882a593Smuzhiyun        oe.qa.handle_error("license-syntax",
226*4882a593Smuzhiyun            "%s: Failed to parse it's LICENSE field." % (d.getVar('PF')), d)
227*4882a593Smuzhiyun    # Add files from LIC_FILES_CHKSUM to list of license files
228*4882a593Smuzhiyun    lic_chksum_paths = defaultdict(OrderedDict)
229*4882a593Smuzhiyun    for path, data in sorted(lic_chksums.items()):
230*4882a593Smuzhiyun        lic_chksum_paths[os.path.basename(path)][data] = (os.path.join(srcdir, path), data[1], data[2])
231*4882a593Smuzhiyun    for basename, files in lic_chksum_paths.items():
232*4882a593Smuzhiyun        if len(files) == 1:
233*4882a593Smuzhiyun            # Don't copy again a LICENSE already handled as non-generic
234*4882a593Smuzhiyun            if basename in non_generic_lics:
235*4882a593Smuzhiyun                continue
236*4882a593Smuzhiyun            data = list(files.values())[0]
237*4882a593Smuzhiyun            lic_files_paths.append(tuple([basename] + list(data)))
238*4882a593Smuzhiyun        else:
239*4882a593Smuzhiyun            # If there are multiple different license files with identical
240*4882a593Smuzhiyun            # basenames we rename them to <file>.0, <file>.1, ...
241*4882a593Smuzhiyun            for i, data in enumerate(files.values()):
242*4882a593Smuzhiyun                lic_files_paths.append(tuple(["%s.%d" % (basename, i)] + list(data)))
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun    return lic_files_paths
245*4882a593Smuzhiyun
246*4882a593Smuzhiyundef return_spdx(d, license):
247*4882a593Smuzhiyun    """
248*4882a593Smuzhiyun    This function returns the spdx mapping of a license if it exists.
249*4882a593Smuzhiyun     """
250*4882a593Smuzhiyun    return d.getVarFlag('SPDXLICENSEMAP', license)
251*4882a593Smuzhiyun
252*4882a593Smuzhiyundef canonical_license(d, license):
253*4882a593Smuzhiyun    """
254*4882a593Smuzhiyun    Return the canonical (SPDX) form of the license if available (so GPLv3
255*4882a593Smuzhiyun    becomes GPL-3.0-only) or the passed license if there is no canonical form.
256*4882a593Smuzhiyun    """
257*4882a593Smuzhiyun    return d.getVarFlag('SPDXLICENSEMAP', license) or license
258*4882a593Smuzhiyun
259*4882a593Smuzhiyundef expand_wildcard_licenses(d, wildcard_licenses):
260*4882a593Smuzhiyun    """
261*4882a593Smuzhiyun    There are some common wildcard values users may want to use. Support them
262*4882a593Smuzhiyun    here.
263*4882a593Smuzhiyun    """
264*4882a593Smuzhiyun    licenses = set(wildcard_licenses)
265*4882a593Smuzhiyun    mapping = {
266*4882a593Smuzhiyun        "AGPL-3.0*" : ["AGPL-3.0-only", "AGPL-3.0-or-later"],
267*4882a593Smuzhiyun        "GPL-3.0*" : ["GPL-3.0-only", "GPL-3.0-or-later"],
268*4882a593Smuzhiyun        "LGPL-3.0*" : ["LGPL-3.0-only", "LGPL-3.0-or-later"],
269*4882a593Smuzhiyun    }
270*4882a593Smuzhiyun    for k in mapping:
271*4882a593Smuzhiyun        if k in wildcard_licenses:
272*4882a593Smuzhiyun            licenses.remove(k)
273*4882a593Smuzhiyun            for item in mapping[k]:
274*4882a593Smuzhiyun                licenses.add(item)
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun    for l in licenses:
277*4882a593Smuzhiyun        if l in oe.license.obsolete_license_list():
278*4882a593Smuzhiyun            bb.fatal("Error, %s is an obsolete license, please use an SPDX reference in INCOMPATIBLE_LICENSE" % l)
279*4882a593Smuzhiyun        if "*" in l:
280*4882a593Smuzhiyun            bb.fatal("Error, %s is an invalid license wildcard entry" % l)
281*4882a593Smuzhiyun
282*4882a593Smuzhiyun    return list(licenses)
283*4882a593Smuzhiyun
284*4882a593Smuzhiyundef incompatible_license_contains(license, truevalue, falsevalue, d):
285*4882a593Smuzhiyun    license = canonical_license(d, license)
286*4882a593Smuzhiyun    bad_licenses = (d.getVar('INCOMPATIBLE_LICENSE') or "").split()
287*4882a593Smuzhiyun    bad_licenses = expand_wildcard_licenses(d, bad_licenses)
288*4882a593Smuzhiyun    return truevalue if license in bad_licenses else falsevalue
289*4882a593Smuzhiyun
290*4882a593Smuzhiyundef incompatible_pkg_license(d, dont_want_licenses, license):
291*4882a593Smuzhiyun    # Handles an "or" or two license sets provided by
292*4882a593Smuzhiyun    # flattened_licenses(), pick one that works if possible.
293*4882a593Smuzhiyun    def choose_lic_set(a, b):
294*4882a593Smuzhiyun        return a if all(oe.license.license_ok(canonical_license(d, lic),
295*4882a593Smuzhiyun                            dont_want_licenses) for lic in a) else b
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun    try:
298*4882a593Smuzhiyun        licenses = oe.license.flattened_licenses(license, choose_lic_set)
299*4882a593Smuzhiyun    except oe.license.LicenseError as exc:
300*4882a593Smuzhiyun        bb.fatal('%s: %s' % (d.getVar('P'), exc))
301*4882a593Smuzhiyun
302*4882a593Smuzhiyun    incompatible_lic = []
303*4882a593Smuzhiyun    for l in licenses:
304*4882a593Smuzhiyun        license = canonical_license(d, l)
305*4882a593Smuzhiyun        if not oe.license.license_ok(license, dont_want_licenses):
306*4882a593Smuzhiyun            incompatible_lic.append(license)
307*4882a593Smuzhiyun
308*4882a593Smuzhiyun    return sorted(incompatible_lic)
309*4882a593Smuzhiyun
310*4882a593Smuzhiyundef incompatible_license(d, dont_want_licenses, package=None):
311*4882a593Smuzhiyun    """
312*4882a593Smuzhiyun    This function checks if a recipe has only incompatible licenses. It also
313*4882a593Smuzhiyun    take into consideration 'or' operand.  dont_want_licenses should be passed
314*4882a593Smuzhiyun    as canonical (SPDX) names.
315*4882a593Smuzhiyun    """
316*4882a593Smuzhiyun    import oe.license
317*4882a593Smuzhiyun    license = d.getVar("LICENSE:%s" % package) if package else None
318*4882a593Smuzhiyun    if not license:
319*4882a593Smuzhiyun        license = d.getVar('LICENSE')
320*4882a593Smuzhiyun
321*4882a593Smuzhiyun    return incompatible_pkg_license(d, dont_want_licenses, license)
322*4882a593Smuzhiyun
323*4882a593Smuzhiyundef check_license_flags(d):
324*4882a593Smuzhiyun    """
325*4882a593Smuzhiyun    This function checks if a recipe has any LICENSE_FLAGS that
326*4882a593Smuzhiyun    aren't acceptable.
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun    If it does, it returns the all LICENSE_FLAGS missing from the list
329*4882a593Smuzhiyun    of acceptable license flags, or all of the LICENSE_FLAGS if there
330*4882a593Smuzhiyun    is no list of acceptable flags.
331*4882a593Smuzhiyun
332*4882a593Smuzhiyun    If everything is is acceptable, it returns None.
333*4882a593Smuzhiyun    """
334*4882a593Smuzhiyun
335*4882a593Smuzhiyun    def license_flag_matches(flag, acceptlist, pn):
336*4882a593Smuzhiyun        """
337*4882a593Smuzhiyun        Return True if flag matches something in acceptlist, None if not.
338*4882a593Smuzhiyun
339*4882a593Smuzhiyun        Before we test a flag against the acceptlist, we append _${PN}
340*4882a593Smuzhiyun        to it.  We then try to match that string against the
341*4882a593Smuzhiyun        acceptlist.  This covers the normal case, where we expect
342*4882a593Smuzhiyun        LICENSE_FLAGS to be a simple string like 'commercial', which
343*4882a593Smuzhiyun        the user typically matches exactly in the acceptlist by
344*4882a593Smuzhiyun        explicitly appending the package name e.g 'commercial_foo'.
345*4882a593Smuzhiyun        If we fail the match however, we then split the flag across
346*4882a593Smuzhiyun        '_' and append each fragment and test until we either match or
347*4882a593Smuzhiyun        run out of fragments.
348*4882a593Smuzhiyun        """
349*4882a593Smuzhiyun        flag_pn = ("%s_%s" % (flag, pn))
350*4882a593Smuzhiyun        for candidate in acceptlist:
351*4882a593Smuzhiyun            if flag_pn == candidate:
352*4882a593Smuzhiyun                    return True
353*4882a593Smuzhiyun
354*4882a593Smuzhiyun        flag_cur = ""
355*4882a593Smuzhiyun        flagments = flag_pn.split("_")
356*4882a593Smuzhiyun        flagments.pop() # we've already tested the full string
357*4882a593Smuzhiyun        for flagment in flagments:
358*4882a593Smuzhiyun            if flag_cur:
359*4882a593Smuzhiyun                flag_cur += "_"
360*4882a593Smuzhiyun            flag_cur += flagment
361*4882a593Smuzhiyun            for candidate in acceptlist:
362*4882a593Smuzhiyun                if flag_cur == candidate:
363*4882a593Smuzhiyun                    return True
364*4882a593Smuzhiyun        return False
365*4882a593Smuzhiyun
366*4882a593Smuzhiyun    def all_license_flags_match(license_flags, acceptlist):
367*4882a593Smuzhiyun        """ Return all unmatched flags, None if all flags match """
368*4882a593Smuzhiyun        pn = d.getVar('PN')
369*4882a593Smuzhiyun        split_acceptlist = acceptlist.split()
370*4882a593Smuzhiyun        flags = []
371*4882a593Smuzhiyun        for flag in license_flags.split():
372*4882a593Smuzhiyun            if not license_flag_matches(flag, split_acceptlist, pn):
373*4882a593Smuzhiyun                flags.append(flag)
374*4882a593Smuzhiyun        return flags if flags else None
375*4882a593Smuzhiyun
376*4882a593Smuzhiyun    license_flags = d.getVar('LICENSE_FLAGS')
377*4882a593Smuzhiyun    if license_flags:
378*4882a593Smuzhiyun        acceptlist = d.getVar('LICENSE_FLAGS_ACCEPTED')
379*4882a593Smuzhiyun        if not acceptlist:
380*4882a593Smuzhiyun            return license_flags.split()
381*4882a593Smuzhiyun        unmatched_flags = all_license_flags_match(license_flags, acceptlist)
382*4882a593Smuzhiyun        if unmatched_flags:
383*4882a593Smuzhiyun            return unmatched_flags
384*4882a593Smuzhiyun    return None
385*4882a593Smuzhiyun
386*4882a593Smuzhiyundef check_license_format(d):
387*4882a593Smuzhiyun    """
388*4882a593Smuzhiyun    This function checks if LICENSE is well defined,
389*4882a593Smuzhiyun        Validate operators in LICENSES.
390*4882a593Smuzhiyun        No spaces are allowed between LICENSES.
391*4882a593Smuzhiyun    """
392*4882a593Smuzhiyun    pn = d.getVar('PN')
393*4882a593Smuzhiyun    licenses = d.getVar('LICENSE')
394*4882a593Smuzhiyun    from oe.license import license_operator, license_operator_chars, license_pattern
395*4882a593Smuzhiyun
396*4882a593Smuzhiyun    elements = list(filter(lambda x: x.strip(), license_operator.split(licenses)))
397*4882a593Smuzhiyun    for pos, element in enumerate(elements):
398*4882a593Smuzhiyun        if license_pattern.match(element):
399*4882a593Smuzhiyun            if pos > 0 and license_pattern.match(elements[pos - 1]):
400*4882a593Smuzhiyun                oe.qa.handle_error('license-format',
401*4882a593Smuzhiyun                        '%s: LICENSE value "%s" has an invalid format - license names ' \
402*4882a593Smuzhiyun                        'must be separated by the following characters to indicate ' \
403*4882a593Smuzhiyun                        'the license selection: %s' %
404*4882a593Smuzhiyun                        (pn, licenses, license_operator_chars), d)
405*4882a593Smuzhiyun        elif not license_operator.match(element):
406*4882a593Smuzhiyun            oe.qa.handle_error('license-format',
407*4882a593Smuzhiyun                    '%s: LICENSE value "%s" has an invalid separator "%s" that is not ' \
408*4882a593Smuzhiyun                    'in the valid list of separators (%s)' %
409*4882a593Smuzhiyun                    (pn, licenses, element, license_operator_chars), d)
410*4882a593Smuzhiyun
411*4882a593SmuzhiyunSSTATETASKS += "do_populate_lic"
412*4882a593Smuzhiyundo_populate_lic[sstate-inputdirs] = "${LICSSTATEDIR}"
413*4882a593Smuzhiyundo_populate_lic[sstate-outputdirs] = "${LICENSE_DIRECTORY}/"
414*4882a593Smuzhiyun
415*4882a593SmuzhiyunIMAGE_CLASSES:append = " license_image"
416*4882a593Smuzhiyun
417*4882a593Smuzhiyunpython do_populate_lic_setscene () {
418*4882a593Smuzhiyun    sstate_setscene(d)
419*4882a593Smuzhiyun}
420*4882a593Smuzhiyunaddtask do_populate_lic_setscene
421