summaryrefslogtreecommitdiffstats
path: root/scripts/lib/recipetool/create.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/recipetool/create.py')
-rw-r--r--scripts/lib/recipetool/create.py360
1 files changed, 232 insertions, 128 deletions
diff --git a/scripts/lib/recipetool/create.py b/scripts/lib/recipetool/create.py
index 566c75369a..8e9ff38db6 100644
--- a/scripts/lib/recipetool/create.py
+++ b/scripts/lib/recipetool/create.py
@@ -115,8 +115,8 @@ class RecipeHandler(object):
115 for line in f: 115 for line in f:
116 if line.startswith('PN:'): 116 if line.startswith('PN:'):
117 pn = line.split(':', 1)[-1].strip() 117 pn = line.split(':', 1)[-1].strip()
118 elif line.startswith('FILES_INFO:'): 118 elif line.startswith('FILES_INFO:%s:' % pkg):
119 val = line.split(':', 1)[1].strip() 119 val = line.split(': ', 1)[1].strip()
120 dictval = json.loads(val) 120 dictval = json.loads(val)
121 for fullpth in sorted(dictval): 121 for fullpth in sorted(dictval):
122 if fullpth.startswith(includedir) and fullpth.endswith('.h'): 122 if fullpth.startswith(includedir) and fullpth.endswith('.h'):
@@ -366,7 +366,7 @@ def supports_srcrev(uri):
366def reformat_git_uri(uri): 366def reformat_git_uri(uri):
367 '''Convert any http[s]://....git URI into git://...;protocol=http[s]''' 367 '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
368 checkuri = uri.split(';', 1)[0] 368 checkuri = uri.split(';', 1)[0]
369 if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://github.com/[^/]+/[^/]+/?$', checkuri): 369 if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://git(hub|lab).com/[^/]+/[^/]+/?$', checkuri):
370 # Appends scheme if the scheme is missing 370 # Appends scheme if the scheme is missing
371 if not '://' in uri: 371 if not '://' in uri:
372 uri = 'git://' + uri 372 uri = 'git://' + uri
@@ -423,6 +423,36 @@ def create_recipe(args):
423 storeTagName = '' 423 storeTagName = ''
424 pv_srcpv = False 424 pv_srcpv = False
425 425
426 handled = []
427 classes = []
428
429 # Find all plugins that want to register handlers
430 logger.debug('Loading recipe handlers')
431 raw_handlers = []
432 for plugin in plugins:
433 if hasattr(plugin, 'register_recipe_handlers'):
434 plugin.register_recipe_handlers(raw_handlers)
435 # Sort handlers by priority
436 handlers = []
437 for i, handler in enumerate(raw_handlers):
438 if isinstance(handler, tuple):
439 handlers.append((handler[0], handler[1], i))
440 else:
441 handlers.append((handler, 0, i))
442 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
443 for handler, priority, _ in handlers:
444 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
445 setattr(handler, '_devtool', args.devtool)
446 handlers = [item[0] for item in handlers]
447
448 fetchuri = None
449 for handler in handlers:
450 if hasattr(handler, 'process_url'):
451 ret = handler.process_url(args, classes, handled, extravalues)
452 if 'url' in handled and ret:
453 fetchuri = ret
454 break
455
426 if os.path.isfile(source): 456 if os.path.isfile(source):
427 source = 'file://%s' % os.path.abspath(source) 457 source = 'file://%s' % os.path.abspath(source)
428 458
@@ -431,11 +461,12 @@ def create_recipe(args):
431 if re.match(r'https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source): 461 if re.match(r'https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source):
432 logger.warning('github archive files are not guaranteed to be stable and may be re-generated over time. If the latter occurs, the checksums will likely change and the recipe will fail at do_fetch. It is recommended that you point to an actual commit or tag in the repository instead (using the repository URL in conjunction with the -S/--srcrev option).') 462 logger.warning('github archive files are not guaranteed to be stable and may be re-generated over time. If the latter occurs, the checksums will likely change and the recipe will fail at do_fetch. It is recommended that you point to an actual commit or tag in the repository instead (using the repository URL in conjunction with the -S/--srcrev option).')
433 # Fetch a URL 463 # Fetch a URL
434 fetchuri = reformat_git_uri(urldefrag(source)[0]) 464 if not fetchuri:
465 fetchuri = reformat_git_uri(urldefrag(source)[0])
435 if args.binary: 466 if args.binary:
436 # Assume the archive contains the directory structure verbatim 467 # Assume the archive contains the directory structure verbatim
437 # so we need to extract to a subdirectory 468 # so we need to extract to a subdirectory
438 fetchuri += ';subdir=${BP}' 469 fetchuri += ';subdir=${BPN}'
439 srcuri = fetchuri 470 srcuri = fetchuri
440 rev_re = re.compile(';rev=([^;]+)') 471 rev_re = re.compile(';rev=([^;]+)')
441 res = rev_re.search(srcuri) 472 res = rev_re.search(srcuri)
@@ -478,6 +509,9 @@ def create_recipe(args):
478 storeTagName = params['tag'] 509 storeTagName = params['tag']
479 params['nobranch'] = '1' 510 params['nobranch'] = '1'
480 del params['tag'] 511 del params['tag']
512 # Assume 'master' branch if not set
513 if scheme in ['git', 'gitsm'] and 'branch' not in params and 'nobranch' not in params:
514 params['branch'] = 'master'
481 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params)) 515 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
482 516
483 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR') 517 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
@@ -527,10 +561,9 @@ def create_recipe(args):
527 # Remove HEAD reference point and drop remote prefix 561 # Remove HEAD reference point and drop remote prefix
528 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')] 562 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
529 if 'master' in get_branch: 563 if 'master' in get_branch:
530 # If it is master, we do not need to append 'branch=master' as this is default.
531 # Even with the case where get_branch has multiple objects, if 'master' is one 564 # Even with the case where get_branch has multiple objects, if 'master' is one
532 # of them, we should default take from 'master' 565 # of them, we should default take from 'master'
533 srcbranch = '' 566 srcbranch = 'master'
534 elif len(get_branch) == 1: 567 elif len(get_branch) == 1:
535 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch' 568 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
536 srcbranch = get_branch[0] 569 srcbranch = get_branch[0]
@@ -543,8 +576,8 @@ def create_recipe(args):
543 # Since we might have a value in srcbranch, we need to 576 # Since we might have a value in srcbranch, we need to
544 # recontruct the srcuri to include 'branch' in params. 577 # recontruct the srcuri to include 'branch' in params.
545 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri) 578 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
546 if srcbranch: 579 if scheme in ['git', 'gitsm']:
547 params['branch'] = srcbranch 580 params['branch'] = srcbranch or 'master'
548 581
549 if storeTagName and scheme in ['git', 'gitsm']: 582 if storeTagName and scheme in ['git', 'gitsm']:
550 # Check srcrev using tag and check validity of the tag 583 # Check srcrev using tag and check validity of the tag
@@ -603,7 +636,7 @@ def create_recipe(args):
603 splitline = line.split() 636 splitline = line.split()
604 if len(splitline) > 1: 637 if len(splitline) > 1:
605 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]): 638 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
606 srcuri = reformat_git_uri(splitline[1]) 639 srcuri = reformat_git_uri(splitline[1]) + ';branch=master'
607 srcsubdir = 'git' 640 srcsubdir = 'git'
608 break 641 break
609 642
@@ -636,8 +669,6 @@ def create_recipe(args):
636 # We'll come back and replace this later in handle_license_vars() 669 # We'll come back and replace this later in handle_license_vars()
637 lines_before.append('##LICENSE_PLACEHOLDER##') 670 lines_before.append('##LICENSE_PLACEHOLDER##')
638 671
639 handled = []
640 classes = []
641 672
642 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this 673 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
643 pn = None 674 pn = None
@@ -675,8 +706,10 @@ def create_recipe(args):
675 if not srcuri: 706 if not srcuri:
676 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)') 707 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
677 lines_before.append('SRC_URI = "%s"' % srcuri) 708 lines_before.append('SRC_URI = "%s"' % srcuri)
709 shown_checksums = ["%ssum" % s for s in bb.fetch2.SHOWN_CHECKSUM_LIST]
678 for key, value in sorted(checksums.items()): 710 for key, value in sorted(checksums.items()):
679 lines_before.append('SRC_URI[%s] = "%s"' % (key, value)) 711 if key in shown_checksums:
712 lines_before.append('SRC_URI[%s] = "%s"' % (key, value))
680 if srcuri and supports_srcrev(srcuri): 713 if srcuri and supports_srcrev(srcuri):
681 lines_before.append('') 714 lines_before.append('')
682 lines_before.append('# Modify these as desired') 715 lines_before.append('# Modify these as desired')
@@ -688,7 +721,7 @@ def create_recipe(args):
688 srcpvprefix = 'svnr' 721 srcpvprefix = 'svnr'
689 else: 722 else:
690 srcpvprefix = scheme 723 srcpvprefix = scheme
691 lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix)) 724 lines_before.append('PV = "%s+%s"' % (realpv or '1.0', srcpvprefix))
692 pv_srcpv = True 725 pv_srcpv = True
693 if not args.autorev and srcrev == '${AUTOREV}': 726 if not args.autorev and srcrev == '${AUTOREV}':
694 if os.path.exists(os.path.join(srctree, '.git')): 727 if os.path.exists(os.path.join(srctree, '.git')):
@@ -710,31 +743,12 @@ def create_recipe(args):
710 lines_after.append('') 743 lines_after.append('')
711 744
712 if args.binary: 745 if args.binary:
713 lines_after.append('INSANE_SKIP_${PN} += "already-stripped"') 746 lines_after.append('INSANE_SKIP:${PN} += "already-stripped"')
714 lines_after.append('') 747 lines_after.append('')
715 748
716 if args.npm_dev: 749 if args.npm_dev:
717 extravalues['NPM_INSTALL_DEV'] = 1 750 extravalues['NPM_INSTALL_DEV'] = 1
718 751
719 # Find all plugins that want to register handlers
720 logger.debug('Loading recipe handlers')
721 raw_handlers = []
722 for plugin in plugins:
723 if hasattr(plugin, 'register_recipe_handlers'):
724 plugin.register_recipe_handlers(raw_handlers)
725 # Sort handlers by priority
726 handlers = []
727 for i, handler in enumerate(raw_handlers):
728 if isinstance(handler, tuple):
729 handlers.append((handler[0], handler[1], i))
730 else:
731 handlers.append((handler, 0, i))
732 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
733 for handler, priority, _ in handlers:
734 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
735 setattr(handler, '_devtool', args.devtool)
736 handlers = [item[0] for item in handlers]
737
738 # Apply the handlers 752 # Apply the handlers
739 if args.binary: 753 if args.binary:
740 classes.append('bin_package') 754 classes.append('bin_package')
@@ -743,6 +757,10 @@ def create_recipe(args):
743 for handler in handlers: 757 for handler in handlers:
744 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues) 758 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
745 759
760 # native and nativesdk classes are special and must be inherited last
761 # If present, put them at the end of the classes list
762 classes.sort(key=lambda c: c in ("native", "nativesdk"))
763
746 extrafiles = extravalues.pop('extrafiles', {}) 764 extrafiles = extravalues.pop('extrafiles', {})
747 extra_pn = extravalues.pop('PN', None) 765 extra_pn = extravalues.pop('PN', None)
748 extra_pv = extravalues.pop('PV', None) 766 extra_pv = extravalues.pop('PV', None)
@@ -867,8 +885,10 @@ def create_recipe(args):
867 outlines.append('') 885 outlines.append('')
868 outlines.extend(lines_after) 886 outlines.extend(lines_after)
869 887
888 outlines = [ line.rstrip('\n') +"\n" for line in outlines]
889
870 if extravalues: 890 if extravalues:
871 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False) 891 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=True)
872 892
873 if args.extract_to: 893 if args.extract_to:
874 scriptutils.git_convert_standalone_clone(srctree) 894 scriptutils.git_convert_standalone_clone(srctree)
@@ -884,7 +904,7 @@ def create_recipe(args):
884 log_info_cond('Source extracted to %s' % args.extract_to, args.devtool) 904 log_info_cond('Source extracted to %s' % args.extract_to, args.devtool)
885 905
886 if outfile == '-': 906 if outfile == '-':
887 sys.stdout.write('\n'.join(outlines) + '\n') 907 sys.stdout.write(''.join(outlines) + '\n')
888 else: 908 else:
889 with open(outfile, 'w') as f: 909 with open(outfile, 'w') as f:
890 lastline = None 910 lastline = None
@@ -892,9 +912,10 @@ def create_recipe(args):
892 if not lastline and not line: 912 if not lastline and not line:
893 # Skip extra blank lines 913 # Skip extra blank lines
894 continue 914 continue
895 f.write('%s\n' % line) 915 f.write('%s' % line)
896 lastline = line 916 lastline = line
897 log_info_cond('Recipe %s has been created; further editing may be required to make it fully functional' % outfile, args.devtool) 917 log_info_cond('Recipe %s has been created; further editing may be required to make it fully functional' % outfile, args.devtool)
918 tinfoil.modified_files()
898 919
899 if tempsrc: 920 if tempsrc:
900 if args.keep_temp: 921 if args.keep_temp:
@@ -917,6 +938,22 @@ def split_value(value):
917 else: 938 else:
918 return value 939 return value
919 940
941def fixup_license(value):
942 # Ensure licenses with OR starts and ends with brackets
943 if '|' in value:
944 return '(' + value + ')'
945 return value
946
947def tidy_licenses(value):
948 """Flat, split and sort licenses"""
949 from oe.license import flattened_licenses
950 def _choose(a, b):
951 str_a, str_b = sorted((" & ".join(a), " & ".join(b)), key=str.casefold)
952 return ["(%s | %s)" % (str_a, str_b)]
953 if not isinstance(value, str):
954 value = " & ".join(value)
955 return sorted(list(set(flattened_licenses(value, _choose))), key=str.casefold)
956
920def handle_license_vars(srctree, lines_before, handled, extravalues, d): 957def handle_license_vars(srctree, lines_before, handled, extravalues, d):
921 lichandled = [x for x in handled if x[0] == 'license'] 958 lichandled = [x for x in handled if x[0] == 'license']
922 if lichandled: 959 if lichandled:
@@ -930,10 +967,13 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
930 lines = [] 967 lines = []
931 if licvalues: 968 if licvalues:
932 for licvalue in licvalues: 969 for licvalue in licvalues:
933 if not licvalue[0] in licenses: 970 license = licvalue[0]
934 licenses.append(licvalue[0]) 971 lics = tidy_licenses(fixup_license(license))
972 lics = [lic for lic in lics if lic not in licenses]
973 if len(lics):
974 licenses.extend(lics)
935 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2])) 975 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
936 if licvalue[0] == 'Unknown': 976 if license == 'Unknown':
937 lic_unknown.append(licvalue[1]) 977 lic_unknown.append(licvalue[1])
938 if lic_unknown: 978 if lic_unknown:
939 lines.append('#') 979 lines.append('#')
@@ -942,9 +982,7 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
942 for licfile in lic_unknown: 982 for licfile in lic_unknown:
943 lines.append('# %s' % licfile) 983 lines.append('# %s' % licfile)
944 984
945 extra_license = split_value(extravalues.pop('LICENSE', [])) 985 extra_license = tidy_licenses(extravalues.pop('LICENSE', ''))
946 if '&' in extra_license:
947 extra_license.remove('&')
948 if extra_license: 986 if extra_license:
949 if licenses == ['Unknown']: 987 if licenses == ['Unknown']:
950 licenses = extra_license 988 licenses = extra_license
@@ -985,7 +1023,7 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
985 lines.append('# instead of &. If there is any doubt, check the accompanying documentation') 1023 lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
986 lines.append('# to determine which situation is applicable.') 1024 lines.append('# to determine which situation is applicable.')
987 1025
988 lines.append('LICENSE = "%s"' % ' & '.join(licenses)) 1026 lines.append('LICENSE = "%s"' % ' & '.join(sorted(licenses, key=str.casefold)))
989 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum)) 1027 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
990 lines.append('') 1028 lines.append('')
991 1029
@@ -1002,118 +1040,170 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
1002 handled.append(('license', licvalues)) 1040 handled.append(('license', licvalues))
1003 return licvalues 1041 return licvalues
1004 1042
1005def get_license_md5sums(d, static_only=False): 1043def get_license_md5sums(d, static_only=False, linenumbers=False):
1006 import bb.utils 1044 import bb.utils
1045 import csv
1007 md5sums = {} 1046 md5sums = {}
1008 if not static_only: 1047 if not static_only and not linenumbers:
1009 # Gather md5sums of license files in common license dir 1048 # Gather md5sums of license files in common license dir
1010 commonlicdir = d.getVar('COMMON_LICENSE_DIR') 1049 commonlicdir = d.getVar('COMMON_LICENSE_DIR')
1011 for fn in os.listdir(commonlicdir): 1050 for fn in os.listdir(commonlicdir):
1012 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn)) 1051 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
1013 md5sums[md5value] = fn 1052 md5sums[md5value] = fn
1053
1014 # The following were extracted from common values in various recipes 1054 # The following were extracted from common values in various recipes
1015 # (double checking the license against the license file itself, not just 1055 # (double checking the license against the license file itself, not just
1016 # the LICENSE value in the recipe) 1056 # the LICENSE value in the recipe)
1017 md5sums['94d55d512a9ba36caa9b7df079bae19f'] = 'GPLv2' 1057
1018 md5sums['b234ee4d69f5fce4486a80fdaf4a4263'] = 'GPLv2' 1058 # Read license md5sums from csv file
1019 md5sums['59530bdf33659b29e73d4adb9f9f6552'] = 'GPLv2' 1059 scripts_path = os.path.dirname(os.path.realpath(__file__))
1020 md5sums['0636e73ff0215e8d672dc4c32c317bb3'] = 'GPLv2' 1060 for path in (d.getVar('BBPATH').split(':')
1021 md5sums['eb723b61539feef013de476e68b5c50a'] = 'GPLv2' 1061 + [os.path.join(scripts_path, '..', '..')]):
1022 md5sums['751419260aa954499f7abaabaa882bbe'] = 'GPLv2' 1062 csv_path = os.path.join(path, 'lib', 'recipetool', 'licenses.csv')
1023 md5sums['393a5ca445f6965873eca0259a17f833'] = 'GPLv2' 1063 if os.path.isfile(csv_path):
1024 md5sums['12f884d2ae1ff87c09e5b7ccc2c4ca7e'] = 'GPLv2' 1064 with open(csv_path, newline='') as csv_file:
1025 md5sums['8ca43cbc842c2336e835926c2166c28b'] = 'GPLv2' 1065 fieldnames = ['md5sum', 'license', 'beginline', 'endline', 'md5']
1026 md5sums['ebb5c50ab7cab4baeffba14977030c07'] = 'GPLv2' 1066 reader = csv.DictReader(csv_file, delimiter=',', fieldnames=fieldnames)
1027 md5sums['c93c0550bd3173f4504b2cbd8991e50b'] = 'GPLv2' 1067 for row in reader:
1028 md5sums['9ac2e7cff1ddaf48b6eab6028f23ef88'] = 'GPLv2' 1068 if linenumbers:
1029 md5sums['4325afd396febcb659c36b49533135d4'] = 'GPLv2' 1069 md5sums[row['md5sum']] = (
1030 md5sums['18810669f13b87348459e611d31ab760'] = 'GPLv2' 1070 row['license'], row['beginline'], row['endline'], row['md5'])
1031 md5sums['d7810fab7487fb0aad327b76f1be7cd7'] = 'GPLv2' # the Linux kernel's COPYING file 1071 else:
1032 md5sums['bbb461211a33b134d42ed5ee802b37ff'] = 'LGPLv2.1' 1072 md5sums[row['md5sum']] = row['license']
1033 md5sums['7fbc338309ac38fefcd64b04bb903e34'] = 'LGPLv2.1' 1073
1034 md5sums['4fbd65380cdd255951079008b364516c'] = 'LGPLv2.1'
1035 md5sums['2d5025d4aa3495befef8f17206a5b0a1'] = 'LGPLv2.1'
1036 md5sums['fbc093901857fcd118f065f900982c24'] = 'LGPLv2.1'
1037 md5sums['a6f89e2100d9b6cdffcea4f398e37343'] = 'LGPLv2.1'
1038 md5sums['d8045f3b8f929c1cb29a1e3fd737b499'] = 'LGPLv2.1'
1039 md5sums['fad9b3332be894bab9bc501572864b29'] = 'LGPLv2.1'
1040 md5sums['3bf50002aefd002f49e7bb854063f7e7'] = 'LGPLv2'
1041 md5sums['9f604d8a4f8e74f4f5140845a21b6674'] = 'LGPLv2'
1042 md5sums['5f30f0716dfdd0d91eb439ebec522ec2'] = 'LGPLv2'
1043 md5sums['55ca817ccb7d5b5b66355690e9abc605'] = 'LGPLv2'
1044 md5sums['252890d9eee26aab7b432e8b8a616475'] = 'LGPLv2'
1045 md5sums['3214f080875748938ba060314b4f727d'] = 'LGPLv2'
1046 md5sums['db979804f025cf55aabec7129cb671ed'] = 'LGPLv2'
1047 md5sums['d32239bcb673463ab874e80d47fae504'] = 'GPLv3'
1048 md5sums['f27defe1e96c2e1ecd4e0c9be8967949'] = 'GPLv3'
1049 md5sums['6a6a8e020838b23406c81b19c1d46df6'] = 'LGPLv3'
1050 md5sums['3b83ef96387f14655fc854ddc3c6bd57'] = 'Apache-2.0'
1051 md5sums['385c55653886acac3821999a3ccd17b3'] = 'Artistic-1.0 | GPL-2.0' # some perl modules
1052 md5sums['54c7042be62e169199200bc6477f04d1'] = 'BSD-3-Clause'
1053 md5sums['bfe1f75d606912a4111c90743d6c7325'] = 'MPL-1.1'
1054 return md5sums 1074 return md5sums
1055 1075
1056def crunch_license(licfile): 1076def crunch_known_licenses(d):
1057 ''' 1077 '''
1058 Remove non-material text from a license file and then check 1078 Calculate the MD5 checksums for the crunched versions of all common
1059 its md5sum against a known list. This works well for licenses 1079 licenses. Also add additional known checksums.
1060 which contain a copyright statement, but is also a useful way
1061 to handle people's insistence upon reformatting the license text
1062 slightly (with no material difference to the text of the
1063 license).
1064 ''' 1080 '''
1081
1082 crunched_md5sums = {}
1065 1083
1066 import oe.utils 1084 # common licenses
1085 crunched_md5sums['ad4e9d34a2e966dfe9837f18de03266d'] = 'GFDL-1.1-only'
1086 crunched_md5sums['d014fb11a34eb67dc717fdcfc97e60ed'] = 'GFDL-1.2-only'
1087 crunched_md5sums['e020ca655b06c112def28e597ab844f1'] = 'GFDL-1.3-only'
1067 1088
1068 # Note: these are carefully constructed!
1069 license_title_re = re.compile(r'^\(?(#+ *)?(The )?.{1,10} [Ll]icen[sc]e( \(.{1,10}\))?\)?:?$')
1070 license_statement_re = re.compile(r'^(This (project|software) is( free software)? (released|licen[sc]ed)|(Released|Licen[cs]ed)) under the .{1,10} [Ll]icen[sc]e:?$')
1071 copyright_re = re.compile('^(#+)? *Copyright .*$')
1072
1073 crunched_md5sums = {}
1074 # The following two were gleaned from the "forever" npm package 1089 # The following two were gleaned from the "forever" npm package
1075 crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC' 1090 crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
1076 crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
1077 # https://github.com/vasi/pixz/blob/master/LICENSE
1078 crunched_md5sums['2f03392b40bbe663597b5bd3cc5ebdb9'] = 'BSD-2-Clause'
1079 # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt 1091 # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
1080 crunched_md5sums['e72e5dfef0b1a4ca8a3d26a60587db66'] = 'BSD-2-Clause' 1092 crunched_md5sums['50fab24ce589d69af8964fdbfe414c60'] = 'BSD-2-Clause'
1081 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE 1093 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
1082 crunched_md5sums['8be76ac6d191671f347ee4916baa637e'] = 'GPLv2' 1094 crunched_md5sums['88a4355858a1433fea99fae34a44da88'] = 'GPL-2.0-only'
1083 # https://github.com/datto/dattobd/blob/master/COPYING
1084 # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/GPLv2.TXT
1085 crunched_md5sums['1d65c5ad4bf6489f85f4812bf08ae73d'] = 'GPLv2'
1086 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt 1095 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
1087 # http://git.neil.brown.name/?p=mdadm.git;a=blob;f=COPYING;h=d159169d1050894d3ea3b98e1c965c4058208fe1;hb=HEAD 1096 crunched_md5sums['063b5c3ebb5f3aa4c85a2ed18a31fbe7'] = 'GPL-2.0-only'
1088 crunched_md5sums['fb530f66a7a89ce920f0e912b5b66d4b'] = 'GPLv2'
1089 # https://github.com/gkos/nrf24/blob/master/COPYING
1090 crunched_md5sums['7b6aaa4daeafdfa6ed5443fd2684581b'] = 'GPLv2'
1091 # https://github.com/josch09/resetusb/blob/master/COPYING
1092 crunched_md5sums['8b8ac1d631a4d220342e83bcf1a1fbc3'] = 'GPLv3'
1093 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1 1097 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
1094 crunched_md5sums['2ea316ed973ae176e502e2297b574bb3'] = 'LGPLv2.1' 1098 crunched_md5sums['7f5202f4d44ed15dcd4915f5210417d8'] = 'LGPL-2.1-only'
1095 # unixODBC-2.3.4 COPYING 1099 # unixODBC-2.3.4 COPYING
1096 crunched_md5sums['1daebd9491d1e8426900b4fa5a422814'] = 'LGPLv2.1' 1100 crunched_md5sums['3debde09238a8c8e1f6a847e1ec9055b'] = 'LGPL-2.1-only'
1097 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3 1101 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
1098 crunched_md5sums['2ebfb3bb49b9a48a075cc1425e7f4129'] = 'LGPLv3' 1102 crunched_md5sums['f90c613c51aa35da4d79dd55fc724ceb'] = 'LGPL-3.0-only'
1099 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10 1103 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
1100 crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0' 1104 crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
1101 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/edl-v10 1105
1102 crunched_md5sums['0a9c78c0a398d1bbce4a166757d60387'] = 'EDL-1.0' 1106 # https://raw.githubusercontent.com/jquery/esprima/3.1.3/LICENSE.BSD
1107 crunched_md5sums['80fa7b56a28e8c902e6af194003220a5'] = 'BSD-2-Clause'
1108 # https://raw.githubusercontent.com/npm/npm-install-checks/master/LICENSE
1109 crunched_md5sums['e659f77bfd9002659e112d0d3d59b2c1'] = 'BSD-2-Clause'
1110 # https://raw.githubusercontent.com/silverwind/default-gateway/4.2.0/LICENSE
1111 crunched_md5sums['4c641f2d995c47f5cb08bdb4b5b6ea05'] = 'BSD-2-Clause'
1112 # https://raw.githubusercontent.com/tad-lispy/node-damerau-levenshtein/v1.0.5/LICENSE
1113 crunched_md5sums['2b8c039b2b9a25f0feb4410c4542d346'] = 'BSD-2-Clause'
1114 # https://raw.githubusercontent.com/terser/terser/v3.17.0/LICENSE
1115 crunched_md5sums['8bd23871802951c9ad63855151204c2c'] = 'BSD-2-Clause'
1116 # https://raw.githubusercontent.com/alexei/sprintf.js/1.0.3/LICENSE
1117 crunched_md5sums['008c22318c8ea65928bf730ddd0273e3'] = 'BSD-3-Clause'
1118 # https://raw.githubusercontent.com/Caligatio/jsSHA/v3.2.0/LICENSE
1119 crunched_md5sums['0e46634a01bfef056892949acaea85b1'] = 'BSD-3-Clause'
1120 # https://raw.githubusercontent.com/d3/d3-path/v1.0.9/LICENSE
1121 crunched_md5sums['b5f72aef53d3b2b432702c30b0215666'] = 'BSD-3-Clause'
1122 # https://raw.githubusercontent.com/feross/ieee754/v1.1.13/LICENSE
1123 crunched_md5sums['a39327c997c20da0937955192d86232d'] = 'BSD-3-Clause'
1124 # https://raw.githubusercontent.com/joyent/node-extsprintf/v1.3.0/LICENSE
1125 crunched_md5sums['721f23a96ff4161ca3a5f071bbe18108'] = 'MIT'
1126 # https://raw.githubusercontent.com/pvorb/clone/v0.2.0/LICENSE
1127 crunched_md5sums['b376d29a53c9573006b9970709231431'] = 'MIT'
1128 # https://raw.githubusercontent.com/andris9/encoding/v0.1.12/LICENSE
1129 crunched_md5sums['85d8a977ee9d7c5ab4ac03c9b95431c4'] = 'MIT-0'
1130 # https://raw.githubusercontent.com/faye/websocket-driver-node/0.7.3/LICENSE.md
1131 crunched_md5sums['b66384e7137e41a9b1904ef4d39703b6'] = 'Apache-2.0'
1132 # https://raw.githubusercontent.com/less/less.js/v4.1.1/LICENSE
1133 crunched_md5sums['b27575459e02221ccef97ec0bfd457ae'] = 'Apache-2.0'
1134 # https://raw.githubusercontent.com/microsoft/TypeScript/v3.5.3/LICENSE.txt
1135 crunched_md5sums['a54a1a6a39e7f9dbb4a23a42f5c7fd1c'] = 'Apache-2.0'
1136 # https://raw.githubusercontent.com/request/request/v2.87.0/LICENSE
1137 crunched_md5sums['1034431802e57486b393d00c5d262b8a'] = 'Apache-2.0'
1138 # https://raw.githubusercontent.com/dchest/tweetnacl-js/v0.14.5/LICENSE
1139 crunched_md5sums['75605e6bdd564791ab698fca65c94a4f'] = 'Unlicense'
1140 # https://raw.githubusercontent.com/stackgl/gl-mat3/v2.0.0/LICENSE.md
1141 crunched_md5sums['75512892d6f59dddb6d1c7e191957e9c'] = 'Zlib'
1142
1143 commonlicdir = d.getVar('COMMON_LICENSE_DIR')
1144 for fn in sorted(os.listdir(commonlicdir)):
1145 md5value, lictext = crunch_license(os.path.join(commonlicdir, fn))
1146 if md5value not in crunched_md5sums:
1147 crunched_md5sums[md5value] = fn
1148 elif fn != crunched_md5sums[md5value]:
1149 bb.debug(2, "crunched_md5sums['%s'] is already set to '%s' rather than '%s'" % (md5value, crunched_md5sums[md5value], fn))
1150 else:
1151 bb.debug(2, "crunched_md5sums['%s'] is already set to '%s'" % (md5value, crunched_md5sums[md5value]))
1152
1153 return crunched_md5sums
1154
1155def crunch_license(licfile):
1156 '''
1157 Remove non-material text from a license file and then calculate its
1158 md5sum. This works well for licenses that contain a copyright statement,
1159 but is also a useful way to handle people's insistence upon reformatting
1160 the license text slightly (with no material difference to the text of the
1161 license).
1162 '''
1163
1164 import oe.utils
1165
1166 # Note: these are carefully constructed!
1167 license_title_re = re.compile(r'^#*\(? *(This is )?([Tt]he )?.{0,15} ?[Ll]icen[sc]e( \(.{1,10}\))?\)?[:\.]? ?#*$')
1168 license_statement_re = re.compile(r'^((This (project|software)|.{1,10}) is( free software)? (released|licen[sc]ed)|(Released|Licen[cs]ed)) under the .{1,10} [Ll]icen[sc]e:?$')
1169 copyright_re = re.compile(r'^ *[#\*]* *(Modified work |MIT LICENSED )?Copyright ?(\([cC]\))? .*$')
1170 disclaimer_re = re.compile(r'^ *\*? ?All [Rr]ights [Rr]eserved\.$')
1171 email_re = re.compile(r'^.*<[\w\.-]*@[\w\.\-]*>$')
1172 header_re = re.compile(r'^(\/\**!?)? ?[\-=\*]* ?(\*\/)?$')
1173 tag_re = re.compile(r'^ *@?\(?([Ll]icense|MIT)\)?$')
1174 url_re = re.compile(r'^ *[#\*]* *https?:\/\/[\w\.\/\-]+$')
1175
1103 lictext = [] 1176 lictext = []
1104 with open(licfile, 'r', errors='surrogateescape') as f: 1177 with open(licfile, 'r', errors='surrogateescape') as f:
1105 for line in f: 1178 for line in f:
1106 # Drop opening statements 1179 # Drop opening statements
1107 if copyright_re.match(line): 1180 if copyright_re.match(line):
1108 continue 1181 continue
1182 elif disclaimer_re.match(line):
1183 continue
1184 elif email_re.match(line):
1185 continue
1186 elif header_re.match(line):
1187 continue
1188 elif tag_re.match(line):
1189 continue
1190 elif url_re.match(line):
1191 continue
1109 elif license_title_re.match(line): 1192 elif license_title_re.match(line):
1110 continue 1193 continue
1111 elif license_statement_re.match(line): 1194 elif license_statement_re.match(line):
1112 continue 1195 continue
1113 # Squash spaces, and replace smart quotes, double quotes 1196 # Strip comment symbols
1114 # and backticks with single quotes 1197 line = line.replace('*', '') \
1198 .replace('#', '')
1199 # Unify spelling
1200 line = line.replace('sub-license', 'sublicense')
1201 # Squash spaces
1115 line = oe.utils.squashspaces(line.strip()) 1202 line = oe.utils.squashspaces(line.strip())
1203 # Replace smart quotes, double quotes and backticks with single quotes
1116 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'') 1204 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
1205 # Unify brackets
1206 line = line.replace("{", "[").replace("}", "]")
1117 if line: 1207 if line:
1118 lictext.append(line) 1208 lictext.append(line)
1119 1209
@@ -1124,31 +1214,40 @@ def crunch_license(licfile):
1124 except UnicodeEncodeError: 1214 except UnicodeEncodeError:
1125 md5val = None 1215 md5val = None
1126 lictext = '' 1216 lictext = ''
1127 license = crunched_md5sums.get(md5val, None) 1217 return md5val, lictext
1128 return license, md5val, lictext
1129 1218
1130def guess_license(srctree, d): 1219def guess_license(srctree, d):
1131 import bb 1220 import bb
1132 md5sums = get_license_md5sums(d) 1221 md5sums = get_license_md5sums(d)
1133 1222
1223 crunched_md5sums = crunch_known_licenses(d)
1224
1134 licenses = [] 1225 licenses = []
1135 licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10'] 1226 licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10']
1227 skip_extensions = (".html", ".js", ".json", ".svg", ".ts", ".go")
1136 licfiles = [] 1228 licfiles = []
1137 for root, dirs, files in os.walk(srctree): 1229 for root, dirs, files in os.walk(srctree):
1138 for fn in files: 1230 for fn in files:
1231 if fn.endswith(skip_extensions):
1232 continue
1139 for spec in licspecs: 1233 for spec in licspecs:
1140 if fnmatch.fnmatch(fn, spec): 1234 if fnmatch.fnmatch(fn, spec):
1141 fullpath = os.path.join(root, fn) 1235 fullpath = os.path.join(root, fn)
1142 if not fullpath in licfiles: 1236 if not fullpath in licfiles:
1143 licfiles.append(fullpath) 1237 licfiles.append(fullpath)
1144 for licfile in licfiles: 1238 for licfile in sorted(licfiles):
1145 md5value = bb.utils.md5_file(licfile) 1239 md5value = bb.utils.md5_file(licfile)
1146 license = md5sums.get(md5value, None) 1240 license = md5sums.get(md5value, None)
1147 if not license: 1241 if not license:
1148 license, crunched_md5, lictext = crunch_license(licfile) 1242 crunched_md5, lictext = crunch_license(licfile)
1149 if not license: 1243 license = crunched_md5sums.get(crunched_md5, None)
1244 if lictext and not license:
1150 license = 'Unknown' 1245 license = 'Unknown'
1151 licenses.append((license, os.path.relpath(licfile, srctree), md5value)) 1246 logger.info("Please add the following line for '%s' to a 'lib/recipetool/licenses.csv' " \
1247 "and replace `Unknown` with the license:\n" \
1248 "%s,Unknown" % (os.path.relpath(licfile, srctree), md5value))
1249 if license:
1250 licenses.append((license, os.path.relpath(licfile, srctree), md5value))
1152 1251
1153 # FIXME should we grab at least one source file with a license header and add that too? 1252 # FIXME should we grab at least one source file with a license header and add that too?
1154 1253
@@ -1162,6 +1261,7 @@ def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn
1162 """ 1261 """
1163 pkglicenses = {pn: []} 1262 pkglicenses = {pn: []}
1164 for license, licpath, _ in licvalues: 1263 for license, licpath, _ in licvalues:
1264 license = fixup_license(license)
1165 for pkgname, pkgpath in packages.items(): 1265 for pkgname, pkgpath in packages.items():
1166 if licpath.startswith(pkgpath + '/'): 1266 if licpath.startswith(pkgpath + '/'):
1167 if pkgname in pkglicenses: 1267 if pkgname in pkglicenses:
@@ -1174,11 +1274,14 @@ def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn
1174 pkglicenses[pn].append(license) 1274 pkglicenses[pn].append(license)
1175 outlicenses = {} 1275 outlicenses = {}
1176 for pkgname in packages: 1276 for pkgname in packages:
1177 license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown' 1277 # Assume AND operator between license files
1178 if license == 'Unknown' and pkgname in fallback_licenses: 1278 license = ' & '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
1279 if license == 'Unknown' and fallback_licenses and pkgname in fallback_licenses:
1179 license = fallback_licenses[pkgname] 1280 license = fallback_licenses[pkgname]
1180 outlines.append('LICENSE_%s = "%s"' % (pkgname, license)) 1281 licenses = tidy_licenses(license)
1181 outlicenses[pkgname] = license.split() 1282 license = ' & '.join(licenses)
1283 outlines.append('LICENSE:%s = "%s"' % (pkgname, license))
1284 outlicenses[pkgname] = licenses
1182 return outlicenses 1285 return outlicenses
1183 1286
1184def read_pkgconfig_provides(d): 1287def read_pkgconfig_provides(d):
@@ -1311,6 +1414,7 @@ def register_commands(subparsers):
1311 parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)') 1414 parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)')
1312 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)') 1415 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
1313 parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies') 1416 parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies')
1417 parser_create.add_argument('--no-pypi', action="store_true", help='Do not inherit pypi class')
1314 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS) 1418 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
1315 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).') 1419 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
1316 parser_create.set_defaults(func=create_recipe) 1420 parser_create.set_defaults(func=create_recipe)