summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2024-10-24 13:03:07 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2024-10-25 15:37:10 +0100
commit71760081f782cdf1e53c3ff03a6376fac3605196 (patch)
tree5976208e344a0ec405cd19942468571349a70874
parentde1fa1e0b0a01ae8a30bbb848e681f0fa2db7ad4 (diff)
downloadpoky-71760081f782cdf1e53c3ff03a6376fac3605196.tar.gz
classes-global/license: Move functions to library code
Moves several of the functions in license.bbclass to be library code New function dependencies were manually verified using bitbake-dumpsigs to ensure that bitbake identified the same dependencies even though they are now in library code (although the new function names mean that the task hashes still change) (From OE-Core rev: 0333e04e353991260c5f67a72f80f3ab9dcf526a) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/classes-global/base.bbclass10
-rw-r--r--meta/classes-global/license.bbclass165
-rw-r--r--meta/classes-recipe/license_image.bbclass14
-rw-r--r--meta/lib/oe/license.py163
4 files changed, 175 insertions, 177 deletions
diff --git a/meta/classes-global/base.bbclass b/meta/classes-global/base.bbclass
index b6940bbb6f..88b932fc3f 100644
--- a/meta/classes-global/base.bbclass
+++ b/meta/classes-global/base.bbclass
@@ -528,8 +528,8 @@ python () {
528 bb.fatal('This recipe does not have the LICENSE field set (%s)' % pn) 528 bb.fatal('This recipe does not have the LICENSE field set (%s)' % pn)
529 529
530 if bb.data.inherits_class('license', d): 530 if bb.data.inherits_class('license', d):
531 check_license_format(d) 531 oe.license.check_license_format(d)
532 unmatched_license_flags = check_license_flags(d) 532 unmatched_license_flags = oe.license.check_license_flags(d)
533 if unmatched_license_flags: 533 if unmatched_license_flags:
534 for unmatched in unmatched_license_flags: 534 for unmatched in unmatched_license_flags:
535 message = "Has a restricted license '%s' which is not listed in your LICENSE_FLAGS_ACCEPTED." % unmatched 535 message = "Has a restricted license '%s' which is not listed in your LICENSE_FLAGS_ACCEPTED." % unmatched
@@ -583,7 +583,7 @@ python () {
583 check_license = False 583 check_license = False
584 584
585 if check_license and bad_licenses: 585 if check_license and bad_licenses:
586 bad_licenses = expand_wildcard_licenses(d, bad_licenses) 586 bad_licenses = oe.license.expand_wildcard_licenses(d, bad_licenses)
587 587
588 exceptions = (d.getVar("INCOMPATIBLE_LICENSE_EXCEPTIONS") or "").split() 588 exceptions = (d.getVar("INCOMPATIBLE_LICENSE_EXCEPTIONS") or "").split()
589 589
@@ -599,7 +599,7 @@ python () {
599 for pkg in pkgs: 599 for pkg in pkgs:
600 remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions) 600 remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions)
601 601
602 incompatible_lic = incompatible_license(d, remaining_bad_licenses, pkg) 602 incompatible_lic = oe.license.incompatible_license(d, remaining_bad_licenses, pkg)
603 if incompatible_lic: 603 if incompatible_lic:
604 skipped_pkgs[pkg] = incompatible_lic 604 skipped_pkgs[pkg] = incompatible_lic
605 else: 605 else:
@@ -612,7 +612,7 @@ python () {
612 for pkg in unskipped_pkgs: 612 for pkg in unskipped_pkgs:
613 bb.debug(1, "Including the package %s" % pkg) 613 bb.debug(1, "Including the package %s" % pkg)
614 else: 614 else:
615 incompatible_lic = incompatible_license(d, bad_licenses) 615 incompatible_lic = oe.license.incompatible_license(d, bad_licenses)
616 for pkg in skipped_pkgs: 616 for pkg in skipped_pkgs:
617 incompatible_lic += skipped_pkgs[pkg] 617 incompatible_lic += skipped_pkgs[pkg]
618 incompatible_lic = sorted(list(set(incompatible_lic))) 618 incompatible_lic = sorted(list(set(incompatible_lic)))
diff --git a/meta/classes-global/license.bbclass b/meta/classes-global/license.bbclass
index 043715fcc3..94dcc7f331 100644
--- a/meta/classes-global/license.bbclass
+++ b/meta/classes-global/license.bbclass
@@ -255,171 +255,6 @@ def find_license_files(d):
255 255
256 return lic_files_paths 256 return lic_files_paths
257 257
258def return_spdx(d, license):
259 """
260 This function returns the spdx mapping of a license if it exists.
261 """
262 return d.getVarFlag('SPDXLICENSEMAP', license)
263
264def canonical_license(d, license):
265 """
266 Return the canonical (SPDX) form of the license if available (so GPLv3
267 becomes GPL-3.0-only) or the passed license if there is no canonical form.
268 """
269 return d.getVarFlag('SPDXLICENSEMAP', license) or license
270
271def expand_wildcard_licenses(d, wildcard_licenses):
272 """
273 There are some common wildcard values users may want to use. Support them
274 here.
275 """
276 licenses = set(wildcard_licenses)
277 mapping = {
278 "AGPL-3.0*" : ["AGPL-3.0-only", "AGPL-3.0-or-later"],
279 "GPL-3.0*" : ["GPL-3.0-only", "GPL-3.0-or-later"],
280 "LGPL-3.0*" : ["LGPL-3.0-only", "LGPL-3.0-or-later"],
281 }
282 for k in mapping:
283 if k in wildcard_licenses:
284 licenses.remove(k)
285 for item in mapping[k]:
286 licenses.add(item)
287
288 for l in licenses:
289 if l in oe.license.obsolete_license_list():
290 bb.fatal("Error, %s is an obsolete license, please use an SPDX reference in INCOMPATIBLE_LICENSE" % l)
291 if "*" in l:
292 bb.fatal("Error, %s is an invalid license wildcard entry" % l)
293
294 return list(licenses)
295
296def incompatible_license_contains(license, truevalue, falsevalue, d):
297 license = canonical_license(d, license)
298 bad_licenses = (d.getVar('INCOMPATIBLE_LICENSE') or "").split()
299 bad_licenses = expand_wildcard_licenses(d, bad_licenses)
300 return truevalue if license in bad_licenses else falsevalue
301
302def incompatible_pkg_license(d, dont_want_licenses, license):
303 # Handles an "or" or two license sets provided by
304 # flattened_licenses(), pick one that works if possible.
305 def choose_lic_set(a, b):
306 return a if all(oe.license.license_ok(canonical_license(d, lic),
307 dont_want_licenses) for lic in a) else b
308
309 try:
310 licenses = oe.license.flattened_licenses(license, choose_lic_set)
311 except oe.license.LicenseError as exc:
312 bb.fatal('%s: %s' % (d.getVar('P'), exc))
313
314 incompatible_lic = []
315 for l in licenses:
316 license = canonical_license(d, l)
317 if not oe.license.license_ok(license, dont_want_licenses):
318 incompatible_lic.append(license)
319
320 return sorted(incompatible_lic)
321
322def incompatible_license(d, dont_want_licenses, package=None):
323 """
324 This function checks if a recipe has only incompatible licenses. It also
325 take into consideration 'or' operand. dont_want_licenses should be passed
326 as canonical (SPDX) names.
327 """
328 import oe.license
329 license = d.getVar("LICENSE:%s" % package) if package else None
330 if not license:
331 license = d.getVar('LICENSE')
332
333 return incompatible_pkg_license(d, dont_want_licenses, license)
334
335def check_license_flags(d):
336 """
337 This function checks if a recipe has any LICENSE_FLAGS that
338 aren't acceptable.
339
340 If it does, it returns the all LICENSE_FLAGS missing from the list
341 of acceptable license flags, or all of the LICENSE_FLAGS if there
342 is no list of acceptable flags.
343
344 If everything is is acceptable, it returns None.
345 """
346
347 def license_flag_matches(flag, acceptlist, pn):
348 """
349 Return True if flag matches something in acceptlist, None if not.
350
351 Before we test a flag against the acceptlist, we append _${PN}
352 to it. We then try to match that string against the
353 acceptlist. This covers the normal case, where we expect
354 LICENSE_FLAGS to be a simple string like 'commercial', which
355 the user typically matches exactly in the acceptlist by
356 explicitly appending the package name e.g 'commercial_foo'.
357 If we fail the match however, we then split the flag across
358 '_' and append each fragment and test until we either match or
359 run out of fragments.
360 """
361 flag_pn = ("%s_%s" % (flag, pn))
362 for candidate in acceptlist:
363 if flag_pn == candidate:
364 return True
365
366 flag_cur = ""
367 flagments = flag_pn.split("_")
368 flagments.pop() # we've already tested the full string
369 for flagment in flagments:
370 if flag_cur:
371 flag_cur += "_"
372 flag_cur += flagment
373 for candidate in acceptlist:
374 if flag_cur == candidate:
375 return True
376 return False
377
378 def all_license_flags_match(license_flags, acceptlist):
379 """ Return all unmatched flags, None if all flags match """
380 pn = d.getVar('PN')
381 split_acceptlist = acceptlist.split()
382 flags = []
383 for flag in license_flags.split():
384 if not license_flag_matches(flag, split_acceptlist, pn):
385 flags.append(flag)
386 return flags if flags else None
387
388 license_flags = d.getVar('LICENSE_FLAGS')
389 if license_flags:
390 acceptlist = d.getVar('LICENSE_FLAGS_ACCEPTED')
391 if not acceptlist:
392 return license_flags.split()
393 unmatched_flags = all_license_flags_match(license_flags, acceptlist)
394 if unmatched_flags:
395 return unmatched_flags
396 return None
397
398def check_license_format(d):
399 """
400 This function checks if LICENSE is well defined,
401 Validate operators in LICENSES.
402 No spaces are allowed between LICENSES.
403 """
404 pn = d.getVar('PN')
405 licenses = d.getVar('LICENSE')
406 from oe.license import license_operator, license_operator_chars, license_pattern
407
408 elements = list(filter(lambda x: x.strip(), license_operator.split(licenses)))
409 for pos, element in enumerate(elements):
410 if license_pattern.match(element):
411 if pos > 0 and license_pattern.match(elements[pos - 1]):
412 oe.qa.handle_error('license-format',
413 '%s: LICENSE value "%s" has an invalid format - license names ' \
414 'must be separated by the following characters to indicate ' \
415 'the license selection: %s' %
416 (pn, licenses, license_operator_chars), d)
417 elif not license_operator.match(element):
418 oe.qa.handle_error('license-format',
419 '%s: LICENSE value "%s" has an invalid separator "%s" that is not ' \
420 'in the valid list of separators (%s)' %
421 (pn, licenses, element, license_operator_chars), d)
422
423SSTATETASKS += "do_populate_lic" 258SSTATETASKS += "do_populate_lic"
424do_populate_lic[sstate-inputdirs] = "${LICSSTATEDIR}" 259do_populate_lic[sstate-inputdirs] = "${LICSSTATEDIR}"
425do_populate_lic[sstate-outputdirs] = "${LICENSE_DIRECTORY}/" 260do_populate_lic[sstate-outputdirs] = "${LICENSE_DIRECTORY}/"
diff --git a/meta/classes-recipe/license_image.bbclass b/meta/classes-recipe/license_image.bbclass
index 0e953856a6..d2c5ab902c 100644
--- a/meta/classes-recipe/license_image.bbclass
+++ b/meta/classes-recipe/license_image.bbclass
@@ -58,7 +58,7 @@ def write_license_files(d, license_manifest, pkg_dic, rootfs=True):
58 import stat 58 import stat
59 59
60 bad_licenses = (d.getVar("INCOMPATIBLE_LICENSE") or "").split() 60 bad_licenses = (d.getVar("INCOMPATIBLE_LICENSE") or "").split()
61 bad_licenses = expand_wildcard_licenses(d, bad_licenses) 61 bad_licenses = oe.license.expand_wildcard_licenses(d, bad_licenses)
62 pkgarchs = d.getVar("SSTATE_ARCHS").split() 62 pkgarchs = d.getVar("SSTATE_ARCHS").split()
63 pkgarchs.reverse() 63 pkgarchs.reverse()
64 64
@@ -66,17 +66,17 @@ def write_license_files(d, license_manifest, pkg_dic, rootfs=True):
66 with open(license_manifest, "w") as license_file: 66 with open(license_manifest, "w") as license_file:
67 for pkg in sorted(pkg_dic): 67 for pkg in sorted(pkg_dic):
68 remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions) 68 remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions)
69 incompatible_licenses = incompatible_pkg_license(d, remaining_bad_licenses, pkg_dic[pkg]["LICENSE"]) 69 incompatible_licenses = oe.license.incompatible_pkg_license(d, remaining_bad_licenses, pkg_dic[pkg]["LICENSE"])
70 if incompatible_licenses: 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))) 71 bb.fatal("Package %s cannot be installed into the image because it has incompatible license(s): %s" %(pkg, ' '.join(incompatible_licenses)))
72 else: 72 else:
73 incompatible_licenses = incompatible_pkg_license(d, bad_licenses, pkg_dic[pkg]["LICENSE"]) 73 incompatible_licenses = oe.license.incompatible_pkg_license(d, bad_licenses, pkg_dic[pkg]["LICENSE"])
74 if incompatible_licenses: 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) 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: 76 try:
77 (pkg_dic[pkg]["LICENSE"], pkg_dic[pkg]["LICENSES"]) = \ 77 (pkg_dic[pkg]["LICENSE"], pkg_dic[pkg]["LICENSES"]) = \
78 oe.license.manifest_licenses(pkg_dic[pkg]["LICENSE"], 78 oe.license.manifest_licenses(pkg_dic[pkg]["LICENSE"],
79 remaining_bad_licenses, canonical_license, d) 79 remaining_bad_licenses, oe.license.canonical_license, d)
80 except oe.license.LicenseError as exc: 80 except oe.license.LicenseError as exc:
81 bb.fatal('%s: %s' % (d.getVar('P'), exc)) 81 bb.fatal('%s: %s' % (d.getVar('P'), exc))
82 82
@@ -144,7 +144,7 @@ def write_license_files(d, license_manifest, pkg_dic, rootfs=True):
144 if not os.path.exists(pkg_license_dir ): 144 if not os.path.exists(pkg_license_dir ):
145 bb.fatal("Couldn't find license information for dependency %s" % pkg) 145 bb.fatal("Couldn't find license information for dependency %s" % pkg)
146 146
147 pkg_manifest_licenses = [canonical_license(d, lic) \ 147 pkg_manifest_licenses = [oe.license.canonical_license(d, lic) \
148 for lic in pkg_dic[pkg]["LICENSES"]] 148 for lic in pkg_dic[pkg]["LICENSES"]]
149 149
150 licenses = os.listdir(pkg_license_dir) 150 licenses = os.listdir(pkg_license_dir)
@@ -153,7 +153,7 @@ def write_license_files(d, license_manifest, pkg_dic, rootfs=True):
153 pkg_rootfs_license = os.path.join(pkg_rootfs_license_dir, lic) 153 pkg_rootfs_license = os.path.join(pkg_rootfs_license_dir, lic)
154 154
155 if re.match(r"^generic_.*$", lic): 155 if re.match(r"^generic_.*$", lic):
156 generic_lic = canonical_license(d, 156 generic_lic = oe.license.canonical_license(d,
157 re.search(r"^generic_(.*)$", lic).group(1)) 157 re.search(r"^generic_(.*)$", lic).group(1))
158 158
159 # Do not copy generic license into package if isn't 159 # Do not copy generic license into package if isn't
@@ -176,7 +176,7 @@ def write_license_files(d, license_manifest, pkg_dic, rootfs=True):
176 if not os.path.exists(pkg_rootfs_license): 176 if not os.path.exists(pkg_rootfs_license):
177 os.symlink(os.path.join('..', generic_lic_file), pkg_rootfs_license) 177 os.symlink(os.path.join('..', generic_lic_file), pkg_rootfs_license)
178 else: 178 else:
179 if (oe.license.license_ok(canonical_license(d, 179 if (oe.license.license_ok(oe.license.canonical_license(d,
180 lic), bad_licenses) == False or 180 lic), bad_licenses) == False or
181 os.path.exists(pkg_rootfs_license)): 181 os.path.exists(pkg_rootfs_license)):
182 continue 182 continue
diff --git a/meta/lib/oe/license.py b/meta/lib/oe/license.py
index d9c8d94da4..7739697c40 100644
--- a/meta/lib/oe/license.py
+++ b/meta/lib/oe/license.py
@@ -259,3 +259,166 @@ def apply_pkg_license_exception(pkg, bad_licenses, exceptions):
259 """Return remaining bad licenses after removing any package exceptions""" 259 """Return remaining bad licenses after removing any package exceptions"""
260 260
261 return [lic for lic in bad_licenses if pkg + ':' + lic not in exceptions] 261 return [lic for lic in bad_licenses if pkg + ':' + lic not in exceptions]
262
263def return_spdx(d, license):
264 """
265 This function returns the spdx mapping of a license if it exists.
266 """
267 return d.getVarFlag('SPDXLICENSEMAP', license)
268
269def canonical_license(d, license):
270 """
271 Return the canonical (SPDX) form of the license if available (so GPLv3
272 becomes GPL-3.0-only) or the passed license if there is no canonical form.
273 """
274 return d.getVarFlag('SPDXLICENSEMAP', license) or license
275
276def expand_wildcard_licenses(d, wildcard_licenses):
277 """
278 There are some common wildcard values users may want to use. Support them
279 here.
280 """
281 licenses = set(wildcard_licenses)
282 mapping = {
283 "AGPL-3.0*" : ["AGPL-3.0-only", "AGPL-3.0-or-later"],
284 "GPL-3.0*" : ["GPL-3.0-only", "GPL-3.0-or-later"],
285 "LGPL-3.0*" : ["LGPL-3.0-only", "LGPL-3.0-or-later"],
286 }
287 for k in mapping:
288 if k in wildcard_licenses:
289 licenses.remove(k)
290 for item in mapping[k]:
291 licenses.add(item)
292
293 for l in licenses:
294 if l in obsolete_license_list():
295 bb.fatal("Error, %s is an obsolete license, please use an SPDX reference in INCOMPATIBLE_LICENSE" % l)
296 if "*" in l:
297 bb.fatal("Error, %s is an invalid license wildcard entry" % l)
298
299 return list(licenses)
300
301def incompatible_license_contains(license, truevalue, falsevalue, d):
302 license = canonical_license(d, license)
303 bad_licenses = (d.getVar('INCOMPATIBLE_LICENSE') or "").split()
304 bad_licenses = expand_wildcard_licenses(d, bad_licenses)
305 return truevalue if license in bad_licenses else falsevalue
306
307def incompatible_pkg_license(d, dont_want_licenses, license):
308 # Handles an "or" or two license sets provided by
309 # flattened_licenses(), pick one that works if possible.
310 def choose_lic_set(a, b):
311 return a if all(license_ok(canonical_license(d, lic),
312 dont_want_licenses) for lic in a) else b
313
314 try:
315 licenses = flattened_licenses(license, choose_lic_set)
316 except LicenseError as exc:
317 bb.fatal('%s: %s' % (d.getVar('P'), exc))
318
319 incompatible_lic = []
320 for l in licenses:
321 license = canonical_license(d, l)
322 if not license_ok(license, dont_want_licenses):
323 incompatible_lic.append(license)
324
325 return sorted(incompatible_lic)
326
327def incompatible_license(d, dont_want_licenses, package=None):
328 """
329 This function checks if a recipe has only incompatible licenses. It also
330 take into consideration 'or' operand. dont_want_licenses should be passed
331 as canonical (SPDX) names.
332 """
333 license = d.getVar("LICENSE:%s" % package) if package else None
334 if not license:
335 license = d.getVar('LICENSE')
336
337 return incompatible_pkg_license(d, dont_want_licenses, license)
338
339def check_license_flags(d):
340 """
341 This function checks if a recipe has any LICENSE_FLAGS that
342 aren't acceptable.
343
344 If it does, it returns the all LICENSE_FLAGS missing from the list
345 of acceptable license flags, or all of the LICENSE_FLAGS if there
346 is no list of acceptable flags.
347
348 If everything is is acceptable, it returns None.
349 """
350
351 def license_flag_matches(flag, acceptlist, pn):
352 """
353 Return True if flag matches something in acceptlist, None if not.
354
355 Before we test a flag against the acceptlist, we append _${PN}
356 to it. We then try to match that string against the
357 acceptlist. This covers the normal case, where we expect
358 LICENSE_FLAGS to be a simple string like 'commercial', which
359 the user typically matches exactly in the acceptlist by
360 explicitly appending the package name e.g 'commercial_foo'.
361 If we fail the match however, we then split the flag across
362 '_' and append each fragment and test until we either match or
363 run out of fragments.
364 """
365 flag_pn = ("%s_%s" % (flag, pn))
366 for candidate in acceptlist:
367 if flag_pn == candidate:
368 return True
369
370 flag_cur = ""
371 flagments = flag_pn.split("_")
372 flagments.pop() # we've already tested the full string
373 for flagment in flagments:
374 if flag_cur:
375 flag_cur += "_"
376 flag_cur += flagment
377 for candidate in acceptlist:
378 if flag_cur == candidate:
379 return True
380 return False
381
382 def all_license_flags_match(license_flags, acceptlist):
383 """ Return all unmatched flags, None if all flags match """
384 pn = d.getVar('PN')
385 split_acceptlist = acceptlist.split()
386 flags = []
387 for flag in license_flags.split():
388 if not license_flag_matches(flag, split_acceptlist, pn):
389 flags.append(flag)
390 return flags if flags else None
391
392 license_flags = d.getVar('LICENSE_FLAGS')
393 if license_flags:
394 acceptlist = d.getVar('LICENSE_FLAGS_ACCEPTED')
395 if not acceptlist:
396 return license_flags.split()
397 unmatched_flags = all_license_flags_match(license_flags, acceptlist)
398 if unmatched_flags:
399 return unmatched_flags
400 return None
401
402def check_license_format(d):
403 """
404 This function checks if LICENSE is well defined,
405 Validate operators in LICENSES.
406 No spaces are allowed between LICENSES.
407 """
408 pn = d.getVar('PN')
409 licenses = d.getVar('LICENSE')
410
411 elements = list(filter(lambda x: x.strip(), license_operator.split(licenses)))
412 for pos, element in enumerate(elements):
413 if license_pattern.match(element):
414 if pos > 0 and license_pattern.match(elements[pos - 1]):
415 oe.qa.handle_error('license-format',
416 '%s: LICENSE value "%s" has an invalid format - license names ' \
417 'must be separated by the following characters to indicate ' \
418 'the license selection: %s' %
419 (pn, licenses, license_operator_chars), d)
420 elif not license_operator.match(element):
421 oe.qa.handle_error('license-format',
422 '%s: LICENSE value "%s" has an invalid separator "%s" that is not ' \
423 'in the valid list of separators (%s)' %
424 (pn, licenses, element, license_operator_chars), d)