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.py368
1 files changed, 99 insertions, 269 deletions
diff --git a/scripts/lib/recipetool/create.py b/scripts/lib/recipetool/create.py
index 4f6e01c639..ef0ba974a9 100644
--- a/scripts/lib/recipetool/create.py
+++ b/scripts/lib/recipetool/create.py
@@ -18,6 +18,8 @@ from urllib.parse import urlparse, urldefrag, urlsplit
18import hashlib 18import hashlib
19import bb.fetch2 19import bb.fetch2
20logger = logging.getLogger('recipetool') 20logger = logging.getLogger('recipetool')
21from oe.license import tidy_licenses
22from oe.license_finder import find_licenses
21 23
22tinfoil = None 24tinfoil = None
23plugins = None 25plugins = None
@@ -389,9 +391,6 @@ def reformat_git_uri(uri):
389 parms.update({('protocol', 'ssh')}) 391 parms.update({('protocol', 'ssh')})
390 elif (scheme == "http" or scheme == 'https' or scheme == 'ssh') and not ('protocol' in parms): 392 elif (scheme == "http" or scheme == 'https' or scheme == 'ssh') and not ('protocol' in parms):
391 parms.update({('protocol', scheme)}) 393 parms.update({('protocol', scheme)})
392 # We assume 'master' branch if not set
393 if not 'branch' in parms:
394 parms.update({('branch', 'master')})
395 # Always append 'git://' 394 # Always append 'git://'
396 fUrl = bb.fetch2.encodeurl(('git', host, path, user, pswd, parms)) 395 fUrl = bb.fetch2.encodeurl(('git', host, path, user, pswd, parms))
397 return fUrl 396 return fUrl
@@ -426,6 +425,36 @@ def create_recipe(args):
426 storeTagName = '' 425 storeTagName = ''
427 pv_srcpv = False 426 pv_srcpv = False
428 427
428 handled = []
429 classes = []
430
431 # Find all plugins that want to register handlers
432 logger.debug('Loading recipe handlers')
433 raw_handlers = []
434 for plugin in plugins:
435 if hasattr(plugin, 'register_recipe_handlers'):
436 plugin.register_recipe_handlers(raw_handlers)
437 # Sort handlers by priority
438 handlers = []
439 for i, handler in enumerate(raw_handlers):
440 if isinstance(handler, tuple):
441 handlers.append((handler[0], handler[1], i))
442 else:
443 handlers.append((handler, 0, i))
444 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
445 for handler, priority, _ in handlers:
446 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
447 setattr(handler, '_devtool', args.devtool)
448 handlers = [item[0] for item in handlers]
449
450 fetchuri = None
451 for handler in handlers:
452 if hasattr(handler, 'process_url'):
453 ret = handler.process_url(args, classes, handled, extravalues)
454 if 'url' in handled and ret:
455 fetchuri = ret
456 break
457
429 if os.path.isfile(source): 458 if os.path.isfile(source):
430 source = 'file://%s' % os.path.abspath(source) 459 source = 'file://%s' % os.path.abspath(source)
431 460
@@ -434,11 +463,12 @@ def create_recipe(args):
434 if re.match(r'https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source): 463 if re.match(r'https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source):
435 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).') 464 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).')
436 # Fetch a URL 465 # Fetch a URL
437 fetchuri = reformat_git_uri(urldefrag(source)[0]) 466 if not fetchuri:
467 fetchuri = reformat_git_uri(urldefrag(source)[0])
438 if args.binary: 468 if args.binary:
439 # Assume the archive contains the directory structure verbatim 469 # Assume the archive contains the directory structure verbatim
440 # so we need to extract to a subdirectory 470 # so we need to extract to a subdirectory
441 fetchuri += ';subdir=${BP}' 471 fetchuri += ';subdir=${BPN}'
442 srcuri = fetchuri 472 srcuri = fetchuri
443 rev_re = re.compile(';rev=([^;]+)') 473 rev_re = re.compile(';rev=([^;]+)')
444 res = rev_re.search(srcuri) 474 res = rev_re.search(srcuri)
@@ -481,6 +511,9 @@ def create_recipe(args):
481 storeTagName = params['tag'] 511 storeTagName = params['tag']
482 params['nobranch'] = '1' 512 params['nobranch'] = '1'
483 del params['tag'] 513 del params['tag']
514 # Assume 'master' branch if not set
515 if scheme in ['git', 'gitsm'] and 'branch' not in params and 'nobranch' not in params:
516 params['branch'] = 'master'
484 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params)) 517 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
485 518
486 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR') 519 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
@@ -497,7 +530,7 @@ def create_recipe(args):
497 if ftmpdir and args.keep_temp: 530 if ftmpdir and args.keep_temp:
498 logger.info('Fetch temp directory is %s' % ftmpdir) 531 logger.info('Fetch temp directory is %s' % ftmpdir)
499 532
500 dirlist = scriptutils.filter_src_subdirs(srctree) 533 dirlist = os.listdir(srctree)
501 logger.debug('Directory listing (excluding filtered out):\n %s' % '\n '.join(dirlist)) 534 logger.debug('Directory listing (excluding filtered out):\n %s' % '\n '.join(dirlist))
502 if len(dirlist) == 1: 535 if len(dirlist) == 1:
503 singleitem = os.path.join(srctree, dirlist[0]) 536 singleitem = os.path.join(srctree, dirlist[0])
@@ -530,10 +563,9 @@ def create_recipe(args):
530 # Remove HEAD reference point and drop remote prefix 563 # Remove HEAD reference point and drop remote prefix
531 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')] 564 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
532 if 'master' in get_branch: 565 if 'master' in get_branch:
533 # If it is master, we do not need to append 'branch=master' as this is default.
534 # Even with the case where get_branch has multiple objects, if 'master' is one 566 # Even with the case where get_branch has multiple objects, if 'master' is one
535 # of them, we should default take from 'master' 567 # of them, we should default take from 'master'
536 srcbranch = '' 568 srcbranch = 'master'
537 elif len(get_branch) == 1: 569 elif len(get_branch) == 1:
538 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch' 570 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
539 srcbranch = get_branch[0] 571 srcbranch = get_branch[0]
@@ -546,8 +578,8 @@ def create_recipe(args):
546 # Since we might have a value in srcbranch, we need to 578 # Since we might have a value in srcbranch, we need to
547 # recontruct the srcuri to include 'branch' in params. 579 # recontruct the srcuri to include 'branch' in params.
548 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri) 580 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
549 if srcbranch: 581 if scheme in ['git', 'gitsm']:
550 params['branch'] = srcbranch 582 params['branch'] = srcbranch or 'master'
551 583
552 if storeTagName and scheme in ['git', 'gitsm']: 584 if storeTagName and scheme in ['git', 'gitsm']:
553 # Check srcrev using tag and check validity of the tag 585 # Check srcrev using tag and check validity of the tag
@@ -606,8 +638,7 @@ def create_recipe(args):
606 splitline = line.split() 638 splitline = line.split()
607 if len(splitline) > 1: 639 if len(splitline) > 1:
608 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]): 640 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
609 srcuri = reformat_git_uri(splitline[1]) 641 srcuri = reformat_git_uri(splitline[1]) + ';branch=master'
610 srcsubdir = 'git'
611 break 642 break
612 643
613 if args.src_subdir: 644 if args.src_subdir:
@@ -639,8 +670,6 @@ def create_recipe(args):
639 # We'll come back and replace this later in handle_license_vars() 670 # We'll come back and replace this later in handle_license_vars()
640 lines_before.append('##LICENSE_PLACEHOLDER##') 671 lines_before.append('##LICENSE_PLACEHOLDER##')
641 672
642 handled = []
643 classes = []
644 673
645 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this 674 # FIXME This is kind of a hack, we probably ought to be using bitbake to do this
646 pn = None 675 pn = None
@@ -678,8 +707,10 @@ def create_recipe(args):
678 if not srcuri: 707 if not srcuri:
679 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)') 708 lines_before.append('# No information for SRC_URI yet (only an external source tree was specified)')
680 lines_before.append('SRC_URI = "%s"' % srcuri) 709 lines_before.append('SRC_URI = "%s"' % srcuri)
710 shown_checksums = ["%ssum" % s for s in bb.fetch2.SHOWN_CHECKSUM_LIST]
681 for key, value in sorted(checksums.items()): 711 for key, value in sorted(checksums.items()):
682 lines_before.append('SRC_URI[%s] = "%s"' % (key, value)) 712 if key in shown_checksums:
713 lines_before.append('SRC_URI[%s] = "%s"' % (key, value))
683 if srcuri and supports_srcrev(srcuri): 714 if srcuri and supports_srcrev(srcuri):
684 lines_before.append('') 715 lines_before.append('')
685 lines_before.append('# Modify these as desired') 716 lines_before.append('# Modify these as desired')
@@ -691,7 +722,7 @@ def create_recipe(args):
691 srcpvprefix = 'svnr' 722 srcpvprefix = 'svnr'
692 else: 723 else:
693 srcpvprefix = scheme 724 srcpvprefix = scheme
694 lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix)) 725 lines_before.append('PV = "%s+%s"' % (realpv or '1.0', srcpvprefix))
695 pv_srcpv = True 726 pv_srcpv = True
696 if not args.autorev and srcrev == '${AUTOREV}': 727 if not args.autorev and srcrev == '${AUTOREV}':
697 if os.path.exists(os.path.join(srctree, '.git')): 728 if os.path.exists(os.path.join(srctree, '.git')):
@@ -705,7 +736,7 @@ def create_recipe(args):
705 if srcsubdir and not args.binary: 736 if srcsubdir and not args.binary:
706 # (for binary packages we explicitly specify subdir= when fetching to 737 # (for binary packages we explicitly specify subdir= when fetching to
707 # match the default value of S, so we don't need to set it in that case) 738 # match the default value of S, so we don't need to set it in that case)
708 lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir) 739 lines_before.append('S = "${UNPACKDIR}/%s"' % srcsubdir)
709 lines_before.append('') 740 lines_before.append('')
710 741
711 if pkgarch: 742 if pkgarch:
@@ -719,25 +750,6 @@ def create_recipe(args):
719 if args.npm_dev: 750 if args.npm_dev:
720 extravalues['NPM_INSTALL_DEV'] = 1 751 extravalues['NPM_INSTALL_DEV'] = 1
721 752
722 # Find all plugins that want to register handlers
723 logger.debug('Loading recipe handlers')
724 raw_handlers = []
725 for plugin in plugins:
726 if hasattr(plugin, 'register_recipe_handlers'):
727 plugin.register_recipe_handlers(raw_handlers)
728 # Sort handlers by priority
729 handlers = []
730 for i, handler in enumerate(raw_handlers):
731 if isinstance(handler, tuple):
732 handlers.append((handler[0], handler[1], i))
733 else:
734 handlers.append((handler, 0, i))
735 handlers.sort(key=lambda item: (item[1], -item[2]), reverse=True)
736 for handler, priority, _ in handlers:
737 logger.debug('Handler: %s (priority %d)' % (handler.__class__.__name__, priority))
738 setattr(handler, '_devtool', args.devtool)
739 handlers = [item[0] for item in handlers]
740
741 # Apply the handlers 753 # Apply the handlers
742 if args.binary: 754 if args.binary:
743 classes.append('bin_package') 755 classes.append('bin_package')
@@ -746,9 +758,14 @@ def create_recipe(args):
746 for handler in handlers: 758 for handler in handlers:
747 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues) 759 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
748 760
761 # native and nativesdk classes are special and must be inherited last
762 # If present, put them at the end of the classes list
763 classes.sort(key=lambda c: c in ("native", "nativesdk"))
764
749 extrafiles = extravalues.pop('extrafiles', {}) 765 extrafiles = extravalues.pop('extrafiles', {})
750 extra_pn = extravalues.pop('PN', None) 766 extra_pn = extravalues.pop('PN', None)
751 extra_pv = extravalues.pop('PV', None) 767 extra_pv = extravalues.pop('PV', None)
768 run_tasks = extravalues.pop('run_tasks', "").split()
752 769
753 if extra_pv and not realpv: 770 if extra_pv and not realpv:
754 realpv = extra_pv 771 realpv = extra_pv
@@ -809,7 +826,8 @@ def create_recipe(args):
809 extraoutdir = os.path.join(os.path.dirname(outfile), pn) 826 extraoutdir = os.path.join(os.path.dirname(outfile), pn)
810 bb.utils.mkdirhier(extraoutdir) 827 bb.utils.mkdirhier(extraoutdir)
811 for destfn, extrafile in extrafiles.items(): 828 for destfn, extrafile in extrafiles.items():
812 shutil.move(extrafile, os.path.join(extraoutdir, destfn)) 829 fn = destfn.format(pn=pn, pv=realpv)
830 shutil.move(extrafile, os.path.join(extraoutdir, fn))
813 831
814 lines = lines_before 832 lines = lines_before
815 lines_before = [] 833 lines_before = []
@@ -824,7 +842,7 @@ def create_recipe(args):
824 line = line.replace(realpv, '${PV}') 842 line = line.replace(realpv, '${PV}')
825 if pn: 843 if pn:
826 line = line.replace(pn, '${BPN}') 844 line = line.replace(pn, '${BPN}')
827 if line == 'S = "${WORKDIR}/${BPN}-${PV}"': 845 if line == 'S = "${UNPACKDIR}/${BPN}-${PV}"' or 'tmp-recipetool-' in line:
828 skipblank = True 846 skipblank = True
829 continue 847 continue
830 elif line.startswith('SRC_URI = '): 848 elif line.startswith('SRC_URI = '):
@@ -870,8 +888,10 @@ def create_recipe(args):
870 outlines.append('') 888 outlines.append('')
871 outlines.extend(lines_after) 889 outlines.extend(lines_after)
872 890
891 outlines = [ line.rstrip('\n') +"\n" for line in outlines]
892
873 if extravalues: 893 if extravalues:
874 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False) 894 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=True)
875 895
876 if args.extract_to: 896 if args.extract_to:
877 scriptutils.git_convert_standalone_clone(srctree) 897 scriptutils.git_convert_standalone_clone(srctree)
@@ -887,7 +907,7 @@ def create_recipe(args):
887 log_info_cond('Source extracted to %s' % args.extract_to, args.devtool) 907 log_info_cond('Source extracted to %s' % args.extract_to, args.devtool)
888 908
889 if outfile == '-': 909 if outfile == '-':
890 sys.stdout.write('\n'.join(outlines) + '\n') 910 sys.stdout.write(''.join(outlines) + '\n')
891 else: 911 else:
892 with open(outfile, 'w') as f: 912 with open(outfile, 'w') as f:
893 lastline = None 913 lastline = None
@@ -895,9 +915,14 @@ def create_recipe(args):
895 if not lastline and not line: 915 if not lastline and not line:
896 # Skip extra blank lines 916 # Skip extra blank lines
897 continue 917 continue
898 f.write('%s\n' % line) 918 f.write('%s' % line)
899 lastline = line 919 lastline = line
900 log_info_cond('Recipe %s has been created; further editing may be required to make it fully functional' % outfile, args.devtool) 920 log_info_cond('Recipe %s has been created; further editing may be required to make it fully functional' % outfile, args.devtool)
921 tinfoil.modified_files()
922
923 for task in run_tasks:
924 logger.info("Running task %s" % task)
925 tinfoil.build_file_sync(outfile, task)
901 926
902 if tempsrc: 927 if tempsrc:
903 if args.keep_temp: 928 if args.keep_temp:
@@ -920,23 +945,32 @@ def split_value(value):
920 else: 945 else:
921 return value 946 return value
922 947
948def fixup_license(value):
949 # Ensure licenses with OR starts and ends with brackets
950 if '|' in value:
951 return '(' + value + ')'
952 return value
953
923def handle_license_vars(srctree, lines_before, handled, extravalues, d): 954def handle_license_vars(srctree, lines_before, handled, extravalues, d):
924 lichandled = [x for x in handled if x[0] == 'license'] 955 lichandled = [x for x in handled if x[0] == 'license']
925 if lichandled: 956 if lichandled:
926 # Someone else has already handled the license vars, just return their value 957 # Someone else has already handled the license vars, just return their value
927 return lichandled[0][1] 958 return lichandled[0][1]
928 959
929 licvalues = guess_license(srctree, d) 960 licvalues = find_licenses(srctree, d)
930 licenses = [] 961 licenses = []
931 lic_files_chksum = [] 962 lic_files_chksum = []
932 lic_unknown = [] 963 lic_unknown = []
933 lines = [] 964 lines = []
934 if licvalues: 965 if licvalues:
935 for licvalue in licvalues: 966 for licvalue in licvalues:
936 if not licvalue[0] in licenses: 967 license = licvalue[0]
937 licenses.append(licvalue[0]) 968 lics = tidy_licenses(fixup_license(license))
969 lics = [lic for lic in lics if lic not in licenses]
970 if len(lics):
971 licenses.extend(lics)
938 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2])) 972 lic_files_chksum.append('file://%s;md5=%s' % (licvalue[1], licvalue[2]))
939 if licvalue[0] == 'Unknown': 973 if license == 'Unknown':
940 lic_unknown.append(licvalue[1]) 974 lic_unknown.append(licvalue[1])
941 if lic_unknown: 975 if lic_unknown:
942 lines.append('#') 976 lines.append('#')
@@ -945,9 +979,7 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
945 for licfile in lic_unknown: 979 for licfile in lic_unknown:
946 lines.append('# %s' % licfile) 980 lines.append('# %s' % licfile)
947 981
948 extra_license = split_value(extravalues.pop('LICENSE', [])) 982 extra_license = tidy_licenses(extravalues.pop('LICENSE', ''))
949 if '&' in extra_license:
950 extra_license.remove('&')
951 if extra_license: 983 if extra_license:
952 if licenses == ['Unknown']: 984 if licenses == ['Unknown']:
953 licenses = extra_license 985 licenses = extra_license
@@ -988,7 +1020,7 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
988 lines.append('# instead of &. If there is any doubt, check the accompanying documentation') 1020 lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
989 lines.append('# to determine which situation is applicable.') 1021 lines.append('# to determine which situation is applicable.')
990 1022
991 lines.append('LICENSE = "%s"' % ' & '.join(licenses)) 1023 lines.append('LICENSE = "%s"' % ' & '.join(sorted(licenses, key=str.casefold)))
992 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum)) 1024 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
993 lines.append('') 1025 lines.append('')
994 1026
@@ -1005,228 +1037,15 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
1005 handled.append(('license', licvalues)) 1037 handled.append(('license', licvalues))
1006 return licvalues 1038 return licvalues
1007 1039
1008def get_license_md5sums(d, static_only=False, linenumbers=False): 1040def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'):
1009 import bb.utils
1010 import csv
1011 md5sums = {}
1012 if not static_only and not linenumbers:
1013 # Gather md5sums of license files in common license dir
1014 commonlicdir = d.getVar('COMMON_LICENSE_DIR')
1015 for fn in os.listdir(commonlicdir):
1016 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
1017 md5sums[md5value] = fn
1018
1019 # The following were extracted from common values in various recipes
1020 # (double checking the license against the license file itself, not just
1021 # the LICENSE value in the recipe)
1022
1023 # Read license md5sums from csv file
1024 scripts_path = os.path.dirname(os.path.realpath(__file__))
1025 for path in (d.getVar('BBPATH').split(':')
1026 + [os.path.join(scripts_path, '..', '..')]):
1027 csv_path = os.path.join(path, 'lib', 'recipetool', 'licenses.csv')
1028 if os.path.isfile(csv_path):
1029 with open(csv_path, newline='') as csv_file:
1030 fieldnames = ['md5sum', 'license', 'beginline', 'endline', 'md5']
1031 reader = csv.DictReader(csv_file, delimiter=',', fieldnames=fieldnames)
1032 for row in reader:
1033 if linenumbers:
1034 md5sums[row['md5sum']] = (
1035 row['license'], row['beginline'], row['endline'], row['md5'])
1036 else:
1037 md5sums[row['md5sum']] = row['license']
1038
1039 return md5sums
1040
1041def crunch_license(licfile):
1042 '''
1043 Remove non-material text from a license file and then check
1044 its md5sum against a known list. This works well for licenses
1045 which contain a copyright statement, but is also a useful way
1046 to handle people's insistence upon reformatting the license text
1047 slightly (with no material difference to the text of the
1048 license).
1049 '''
1050
1051 import oe.utils
1052
1053 # Note: these are carefully constructed!
1054 license_title_re = re.compile(r'^#*\(? *(This is )?([Tt]he )?.{0,15} ?[Ll]icen[sc]e( \(.{1,10}\))?\)?[:\.]? ?#*$')
1055 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:?$')
1056 copyright_re = re.compile('^ *[#\*]* *(Modified work |MIT LICENSED )?Copyright ?(\([cC]\))? .*$')
1057 disclaimer_re = re.compile('^ *\*? ?All [Rr]ights [Rr]eserved\.$')
1058 email_re = re.compile('^.*<[\w\.-]*@[\w\.\-]*>$')
1059 header_re = re.compile('^(\/\**!?)? ?[\-=\*]* ?(\*\/)?$')
1060 tag_re = re.compile('^ *@?\(?([Ll]icense|MIT)\)?$')
1061 url_re = re.compile('^ *[#\*]* *https?:\/\/[\w\.\/\-]+$')
1062
1063 crunched_md5sums = {}
1064
1065 # common licenses
1066 crunched_md5sums['89f3bf322f30a1dcfe952e09945842f0'] = 'Apache-2.0'
1067 crunched_md5sums['13b6fe3075f8f42f2270a748965bf3a1'] = 'BSD-0-Clause'
1068 crunched_md5sums['ba87a7d7c20719c8df4b8beed9b78c43'] = 'BSD-2-Clause'
1069 crunched_md5sums['7f8892c03b72de419c27be4ebfa253f8'] = 'BSD-3-Clause'
1070 crunched_md5sums['21128c0790b23a8a9f9e260d5f6b3619'] = 'BSL-1.0'
1071 crunched_md5sums['975742a59ae1b8abdea63a97121f49f4'] = 'EDL-1.0'
1072 crunched_md5sums['5322cee4433d84fb3aafc9e253116447'] = 'EPL-1.0'
1073 crunched_md5sums['6922352e87de080f42419bed93063754'] = 'EPL-2.0'
1074 crunched_md5sums['793475baa22295cae1d3d4046a3a0ceb'] = 'GPL-2.0-only'
1075 crunched_md5sums['ff9047f969b02c20f0559470df5cb433'] = 'GPL-2.0-or-later'
1076 crunched_md5sums['ea6de5453fcadf534df246e6cdafadcd'] = 'GPL-3.0-only'
1077 crunched_md5sums['b419257d4d153a6fde92ddf96acf5b67'] = 'GPL-3.0-or-later'
1078 crunched_md5sums['228737f4c49d3ee75b8fb3706b090b84'] = 'ISC'
1079 crunched_md5sums['c6a782e826ca4e85bf7f8b89435a677d'] = 'LGPL-2.0-only'
1080 crunched_md5sums['32d8f758a066752f0db09bd7624b8090'] = 'LGPL-2.0-or-later'
1081 crunched_md5sums['4820937eb198b4f84c52217ed230be33'] = 'LGPL-2.1-only'
1082 crunched_md5sums['db13fe9f3a13af7adab2dc7a76f9e44a'] = 'LGPL-2.1-or-later'
1083 crunched_md5sums['d7a0f2e4e0950e837ac3eabf5bd1d246'] = 'LGPL-3.0-only'
1084 crunched_md5sums['abbf328e2b434f9153351f06b9f79d02'] = 'LGPL-3.0-or-later'
1085 crunched_md5sums['eecf6429523cbc9693547cf2db790b5c'] = 'MIT'
1086 crunched_md5sums['b218b0e94290b9b818c4be67c8e1cc82'] = 'MIT-0'
1087 crunched_md5sums['ddc18131d6748374f0f35a621c245b49'] = 'Unlicense'
1088 crunched_md5sums['51f9570ff32571fc0a443102285c5e33'] = 'WTFPL'
1089
1090 # The following two were gleaned from the "forever" npm package
1091 crunched_md5sums['0a97f8e4cbaf889d6fa51f84b89a79f6'] = 'ISC'
1092 # https://github.com/waffle-gl/waffle/blob/master/LICENSE.txt
1093 crunched_md5sums['50fab24ce589d69af8964fdbfe414c60'] = 'BSD-2-Clause'
1094 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
1095 crunched_md5sums['88a4355858a1433fea99fae34a44da88'] = 'GPLv2'
1096 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
1097 crunched_md5sums['063b5c3ebb5f3aa4c85a2ed18a31fbe7'] = 'GPLv2'
1098 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv2.1
1099 crunched_md5sums['7f5202f4d44ed15dcd4915f5210417d8'] = 'LGPLv2.1'
1100 # unixODBC-2.3.4 COPYING
1101 crunched_md5sums['3debde09238a8c8e1f6a847e1ec9055b'] = 'LGPLv2.1'
1102 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
1103 crunched_md5sums['f90c613c51aa35da4d79dd55fc724ceb'] = 'LGPLv3'
1104 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
1105 crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
1106
1107 # https://raw.githubusercontent.com/jquery/esprima/3.1.3/LICENSE.BSD
1108 crunched_md5sums['80fa7b56a28e8c902e6af194003220a5'] = 'BSD-2-Clause'
1109 # https://raw.githubusercontent.com/npm/npm-install-checks/master/LICENSE
1110 crunched_md5sums['e659f77bfd9002659e112d0d3d59b2c1'] = 'BSD-2-Clause'
1111 # https://raw.githubusercontent.com/silverwind/default-gateway/4.2.0/LICENSE
1112 crunched_md5sums['4c641f2d995c47f5cb08bdb4b5b6ea05'] = 'BSD-2-Clause'
1113 # https://raw.githubusercontent.com/tad-lispy/node-damerau-levenshtein/v1.0.5/LICENSE
1114 crunched_md5sums['2b8c039b2b9a25f0feb4410c4542d346'] = 'BSD-2-Clause'
1115 # https://raw.githubusercontent.com/terser/terser/v3.17.0/LICENSE
1116 crunched_md5sums['8bd23871802951c9ad63855151204c2c'] = 'BSD-2-Clause'
1117 # https://raw.githubusercontent.com/alexei/sprintf.js/1.0.3/LICENSE
1118 crunched_md5sums['008c22318c8ea65928bf730ddd0273e3'] = 'BSD-3-Clause'
1119 # https://raw.githubusercontent.com/Caligatio/jsSHA/v3.2.0/LICENSE
1120 crunched_md5sums['0e46634a01bfef056892949acaea85b1'] = 'BSD-3-Clause'
1121 # https://raw.githubusercontent.com/d3/d3-path/v1.0.9/LICENSE
1122 crunched_md5sums['b5f72aef53d3b2b432702c30b0215666'] = 'BSD-3-Clause'
1123 # https://raw.githubusercontent.com/feross/ieee754/v1.1.13/LICENSE
1124 crunched_md5sums['a39327c997c20da0937955192d86232d'] = 'BSD-3-Clause'
1125 # https://raw.githubusercontent.com/joyent/node-extsprintf/v1.3.0/LICENSE
1126 crunched_md5sums['721f23a96ff4161ca3a5f071bbe18108'] = 'MIT'
1127 # https://raw.githubusercontent.com/pvorb/clone/v0.2.0/LICENSE
1128 crunched_md5sums['b376d29a53c9573006b9970709231431'] = 'MIT'
1129 # https://raw.githubusercontent.com/andris9/encoding/v0.1.12/LICENSE
1130 crunched_md5sums['85d8a977ee9d7c5ab4ac03c9b95431c4'] = 'MIT-0'
1131 # https://raw.githubusercontent.com/faye/websocket-driver-node/0.7.3/LICENSE.md
1132 crunched_md5sums['b66384e7137e41a9b1904ef4d39703b6'] = 'Apache-2.0'
1133 # https://raw.githubusercontent.com/less/less.js/v4.1.1/LICENSE
1134 crunched_md5sums['b27575459e02221ccef97ec0bfd457ae'] = 'Apache-2.0'
1135 # https://raw.githubusercontent.com/microsoft/TypeScript/v3.5.3/LICENSE.txt
1136 crunched_md5sums['a54a1a6a39e7f9dbb4a23a42f5c7fd1c'] = 'Apache-2.0'
1137 # https://raw.githubusercontent.com/request/request/v2.87.0/LICENSE
1138 crunched_md5sums['1034431802e57486b393d00c5d262b8a'] = 'Apache-2.0'
1139 # https://raw.githubusercontent.com/dchest/tweetnacl-js/v0.14.5/LICENSE
1140 crunched_md5sums['75605e6bdd564791ab698fca65c94a4f'] = 'Unlicense'
1141 # https://raw.githubusercontent.com/stackgl/gl-mat3/v2.0.0/LICENSE.md
1142 crunched_md5sums['75512892d6f59dddb6d1c7e191957e9c'] = 'Zlib'
1143
1144 lictext = []
1145 with open(licfile, 'r', errors='surrogateescape') as f:
1146 for line in f:
1147 # Drop opening statements
1148 if copyright_re.match(line):
1149 continue
1150 elif disclaimer_re.match(line):
1151 continue
1152 elif email_re.match(line):
1153 continue
1154 elif header_re.match(line):
1155 continue
1156 elif tag_re.match(line):
1157 continue
1158 elif url_re.match(line):
1159 continue
1160 elif license_title_re.match(line):
1161 continue
1162 elif license_statement_re.match(line):
1163 continue
1164 # Strip comment symbols
1165 line = line.replace('*', '') \
1166 .replace('#', '')
1167 # Unify spelling
1168 line = line.replace('sub-license', 'sublicense')
1169 # Squash spaces
1170 line = oe.utils.squashspaces(line.strip())
1171 # Replace smart quotes, double quotes and backticks with single quotes
1172 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
1173 # Unify brackets
1174 line = line.replace("{", "[").replace("}", "]")
1175 if line:
1176 lictext.append(line)
1177
1178 m = hashlib.md5()
1179 try:
1180 m.update(' '.join(lictext).encode('utf-8'))
1181 md5val = m.hexdigest()
1182 except UnicodeEncodeError:
1183 md5val = None
1184 lictext = ''
1185 license = crunched_md5sums.get(md5val, None)
1186 return license, md5val, lictext
1187
1188def guess_license(srctree, d):
1189 import bb
1190 md5sums = get_license_md5sums(d)
1191
1192 licenses = []
1193 licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10']
1194 skip_extensions = (".html", ".js", ".json", ".svg", ".ts")
1195 licfiles = []
1196 for root, dirs, files in os.walk(srctree):
1197 for fn in files:
1198 if fn.endswith(skip_extensions):
1199 continue
1200 for spec in licspecs:
1201 if fnmatch.fnmatch(fn, spec):
1202 fullpath = os.path.join(root, fn)
1203 if not fullpath in licfiles:
1204 licfiles.append(fullpath)
1205 for licfile in licfiles:
1206 md5value = bb.utils.md5_file(licfile)
1207 license = md5sums.get(md5value, None)
1208 if not license:
1209 license, crunched_md5, lictext = crunch_license(licfile)
1210 if lictext and not license:
1211 license = 'Unknown'
1212 logger.info("Please add the following line for '%s' to a 'lib/recipetool/licenses.csv' " \
1213 "and replace `Unknown` with the license:\n" \
1214 "%s,Unknown" % (os.path.relpath(licfile, srctree), md5value))
1215 if license:
1216 licenses.append((license, os.path.relpath(licfile, srctree), md5value))
1217
1218 # FIXME should we grab at least one source file with a license header and add that too?
1219
1220 return licenses
1221
1222def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=[], pn='${PN}'):
1223 """ 1041 """
1224 Given a list of (license, path, md5sum) as returned by guess_license(), 1042 Given a list of (license, path, md5sum) as returned by match_licenses(),
1225 a dict of package name to path mappings, write out a set of 1043 a dict of package name to path mappings, write out a set of
1226 package-specific LICENSE values. 1044 package-specific LICENSE values.
1227 """ 1045 """
1228 pkglicenses = {pn: []} 1046 pkglicenses = {pn: []}
1229 for license, licpath, _ in licvalues: 1047 for license, licpath, _ in licvalues:
1048 license = fixup_license(license)
1230 for pkgname, pkgpath in packages.items(): 1049 for pkgname, pkgpath in packages.items():
1231 if licpath.startswith(pkgpath + '/'): 1050 if licpath.startswith(pkgpath + '/'):
1232 if pkgname in pkglicenses: 1051 if pkgname in pkglicenses:
@@ -1239,13 +1058,24 @@ def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=[], pn='
1239 pkglicenses[pn].append(license) 1058 pkglicenses[pn].append(license)
1240 outlicenses = {} 1059 outlicenses = {}
1241 for pkgname in packages: 1060 for pkgname in packages:
1242 license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown' 1061 # Assume AND operator between license files
1243 if license == 'Unknown' and pkgname in fallback_licenses: 1062 license = ' & '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown'
1063 if license == 'Unknown' and fallback_licenses and pkgname in fallback_licenses:
1244 license = fallback_licenses[pkgname] 1064 license = fallback_licenses[pkgname]
1065 licenses = tidy_licenses(license)
1066 license = ' & '.join(licenses)
1245 outlines.append('LICENSE:%s = "%s"' % (pkgname, license)) 1067 outlines.append('LICENSE:%s = "%s"' % (pkgname, license))
1246 outlicenses[pkgname] = license.split() 1068 outlicenses[pkgname] = licenses
1247 return outlicenses 1069 return outlicenses
1248 1070
1071def generate_common_licenses_chksums(common_licenses, d):
1072 lic_files_chksums = []
1073 for license in tidy_licenses(common_licenses):
1074 licfile = '${COMMON_LICENSE_DIR}/' + license
1075 md5value = bb.utils.md5_file(d.expand(licfile))
1076 lic_files_chksums.append('file://%s;md5=%s' % (licfile, md5value))
1077 return lic_files_chksums
1078
1249def read_pkgconfig_provides(d): 1079def read_pkgconfig_provides(d):
1250 pkgdatadir = d.getVar('PKGDATA_DIR') 1080 pkgdatadir = d.getVar('PKGDATA_DIR')
1251 pkgmap = {} 1081 pkgmap = {}
@@ -1376,7 +1206,7 @@ def register_commands(subparsers):
1376 parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)') 1206 parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)')
1377 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)') 1207 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
1378 parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies') 1208 parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies')
1209 parser_create.add_argument('--no-pypi', action="store_true", help='Do not inherit pypi class')
1379 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS) 1210 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
1380 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).') 1211 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
1381 parser_create.set_defaults(func=create_recipe) 1212 parser_create.set_defaults(func=create_recipe)
1382