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.py311
1 files changed, 103 insertions, 208 deletions
diff --git a/scripts/lib/recipetool/create.py b/scripts/lib/recipetool/create.py
index 566c75369a..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
@@ -115,8 +117,8 @@ class RecipeHandler(object):
115 for line in f: 117 for line in f:
116 if line.startswith('PN:'): 118 if line.startswith('PN:'):
117 pn = line.split(':', 1)[-1].strip() 119 pn = line.split(':', 1)[-1].strip()
118 elif line.startswith('FILES_INFO:'): 120 elif line.startswith('FILES_INFO:%s:' % pkg):
119 val = line.split(':', 1)[1].strip() 121 val = line.split(': ', 1)[1].strip()
120 dictval = json.loads(val) 122 dictval = json.loads(val)
121 for fullpth in sorted(dictval): 123 for fullpth in sorted(dictval):
122 if fullpth.startswith(includedir) and fullpth.endswith('.h'): 124 if fullpth.startswith(includedir) and fullpth.endswith('.h'):
@@ -366,7 +368,7 @@ def supports_srcrev(uri):
366def reformat_git_uri(uri): 368def reformat_git_uri(uri):
367 '''Convert any http[s]://....git URI into git://...;protocol=http[s]''' 369 '''Convert any http[s]://....git URI into git://...;protocol=http[s]'''
368 checkuri = uri.split(';', 1)[0] 370 checkuri = uri.split(';', 1)[0]
369 if checkuri.endswith('.git') or '/git/' in checkuri or re.match('https?://github.com/[^/]+/[^/]+/?$', checkuri): 371 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 372 # Appends scheme if the scheme is missing
371 if not '://' in uri: 373 if not '://' in uri:
372 uri = 'git://' + uri 374 uri = 'git://' + uri
@@ -423,6 +425,36 @@ def create_recipe(args):
423 storeTagName = '' 425 storeTagName = ''
424 pv_srcpv = False 426 pv_srcpv = False
425 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
426 if os.path.isfile(source): 458 if os.path.isfile(source):
427 source = 'file://%s' % os.path.abspath(source) 459 source = 'file://%s' % os.path.abspath(source)
428 460
@@ -431,11 +463,12 @@ def create_recipe(args):
431 if re.match(r'https?://github.com/[^/]+/[^/]+/archive/.+(\.tar\..*|\.zip)$', source): 463 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).') 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).')
433 # Fetch a URL 465 # Fetch a URL
434 fetchuri = reformat_git_uri(urldefrag(source)[0]) 466 if not fetchuri:
467 fetchuri = reformat_git_uri(urldefrag(source)[0])
435 if args.binary: 468 if args.binary:
436 # Assume the archive contains the directory structure verbatim 469 # Assume the archive contains the directory structure verbatim
437 # so we need to extract to a subdirectory 470 # so we need to extract to a subdirectory
438 fetchuri += ';subdir=${BP}' 471 fetchuri += ';subdir=${BPN}'
439 srcuri = fetchuri 472 srcuri = fetchuri
440 rev_re = re.compile(';rev=([^;]+)') 473 rev_re = re.compile(';rev=([^;]+)')
441 res = rev_re.search(srcuri) 474 res = rev_re.search(srcuri)
@@ -478,6 +511,9 @@ def create_recipe(args):
478 storeTagName = params['tag'] 511 storeTagName = params['tag']
479 params['nobranch'] = '1' 512 params['nobranch'] = '1'
480 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'
481 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params)) 517 fetchuri = bb.fetch2.encodeurl((scheme, network, path, user, passwd, params))
482 518
483 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR') 519 tmpparent = tinfoil.config_data.getVar('BASE_WORKDIR')
@@ -494,7 +530,7 @@ def create_recipe(args):
494 if ftmpdir and args.keep_temp: 530 if ftmpdir and args.keep_temp:
495 logger.info('Fetch temp directory is %s' % ftmpdir) 531 logger.info('Fetch temp directory is %s' % ftmpdir)
496 532
497 dirlist = scriptutils.filter_src_subdirs(srctree) 533 dirlist = os.listdir(srctree)
498 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))
499 if len(dirlist) == 1: 535 if len(dirlist) == 1:
500 singleitem = os.path.join(srctree, dirlist[0]) 536 singleitem = os.path.join(srctree, dirlist[0])
@@ -527,10 +563,9 @@ def create_recipe(args):
527 # Remove HEAD reference point and drop remote prefix 563 # 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')] 564 get_branch = [x.split('/', 1)[1] for x in get_branch if not x.startswith('origin/HEAD')]
529 if 'master' in get_branch: 565 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 566 # Even with the case where get_branch has multiple objects, if 'master' is one
532 # of them, we should default take from 'master' 567 # of them, we should default take from 'master'
533 srcbranch = '' 568 srcbranch = 'master'
534 elif len(get_branch) == 1: 569 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' 570 # If 'master' isn't in get_branch and get_branch contains only ONE object, then store result into 'srcbranch'
536 srcbranch = get_branch[0] 571 srcbranch = get_branch[0]
@@ -543,8 +578,8 @@ def create_recipe(args):
543 # Since we might have a value in srcbranch, we need to 578 # Since we might have a value in srcbranch, we need to
544 # recontruct the srcuri to include 'branch' in params. 579 # recontruct the srcuri to include 'branch' in params.
545 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri) 580 scheme, network, path, user, passwd, params = bb.fetch2.decodeurl(srcuri)
546 if srcbranch: 581 if scheme in ['git', 'gitsm']:
547 params['branch'] = srcbranch 582 params['branch'] = srcbranch or 'master'
548 583
549 if storeTagName and scheme in ['git', 'gitsm']: 584 if storeTagName and scheme in ['git', 'gitsm']:
550 # Check srcrev using tag and check validity of the tag 585 # Check srcrev using tag and check validity of the tag
@@ -603,8 +638,7 @@ def create_recipe(args):
603 splitline = line.split() 638 splitline = line.split()
604 if len(splitline) > 1: 639 if len(splitline) > 1:
605 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]): 640 if splitline[0] == 'origin' and scriptutils.is_src_url(splitline[1]):
606 srcuri = reformat_git_uri(splitline[1]) 641 srcuri = reformat_git_uri(splitline[1]) + ';branch=master'
607 srcsubdir = 'git'
608 break 642 break
609 643
610 if args.src_subdir: 644 if args.src_subdir:
@@ -636,8 +670,6 @@ def create_recipe(args):
636 # 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()
637 lines_before.append('##LICENSE_PLACEHOLDER##') 671 lines_before.append('##LICENSE_PLACEHOLDER##')
638 672
639 handled = []
640 classes = []
641 673
642 # 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
643 pn = None 675 pn = None
@@ -675,8 +707,10 @@ def create_recipe(args):
675 if not srcuri: 707 if not srcuri:
676 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)')
677 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]
678 for key, value in sorted(checksums.items()): 711 for key, value in sorted(checksums.items()):
679 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))
680 if srcuri and supports_srcrev(srcuri): 714 if srcuri and supports_srcrev(srcuri):
681 lines_before.append('') 715 lines_before.append('')
682 lines_before.append('# Modify these as desired') 716 lines_before.append('# Modify these as desired')
@@ -688,7 +722,7 @@ def create_recipe(args):
688 srcpvprefix = 'svnr' 722 srcpvprefix = 'svnr'
689 else: 723 else:
690 srcpvprefix = scheme 724 srcpvprefix = scheme
691 lines_before.append('PV = "%s+%s${SRCPV}"' % (realpv or '1.0', srcpvprefix)) 725 lines_before.append('PV = "%s+%s"' % (realpv or '1.0', srcpvprefix))
692 pv_srcpv = True 726 pv_srcpv = True
693 if not args.autorev and srcrev == '${AUTOREV}': 727 if not args.autorev and srcrev == '${AUTOREV}':
694 if os.path.exists(os.path.join(srctree, '.git')): 728 if os.path.exists(os.path.join(srctree, '.git')):
@@ -702,7 +736,7 @@ def create_recipe(args):
702 if srcsubdir and not args.binary: 736 if srcsubdir and not args.binary:
703 # (for binary packages we explicitly specify subdir= when fetching to 737 # (for binary packages we explicitly specify subdir= when fetching to
704 # 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)
705 lines_before.append('S = "${WORKDIR}/%s"' % srcsubdir) 739 lines_before.append('S = "${UNPACKDIR}/%s"' % srcsubdir)
706 lines_before.append('') 740 lines_before.append('')
707 741
708 if pkgarch: 742 if pkgarch:
@@ -710,31 +744,12 @@ def create_recipe(args):
710 lines_after.append('') 744 lines_after.append('')
711 745
712 if args.binary: 746 if args.binary:
713 lines_after.append('INSANE_SKIP_${PN} += "already-stripped"') 747 lines_after.append('INSANE_SKIP:${PN} += "already-stripped"')
714 lines_after.append('') 748 lines_after.append('')
715 749
716 if args.npm_dev: 750 if args.npm_dev:
717 extravalues['NPM_INSTALL_DEV'] = 1 751 extravalues['NPM_INSTALL_DEV'] = 1
718 752
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 753 # Apply the handlers
739 if args.binary: 754 if args.binary:
740 classes.append('bin_package') 755 classes.append('bin_package')
@@ -743,9 +758,14 @@ def create_recipe(args):
743 for handler in handlers: 758 for handler in handlers:
744 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues) 759 handler.process(srctree_use, classes, lines_before, lines_after, handled, extravalues)
745 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
746 extrafiles = extravalues.pop('extrafiles', {}) 765 extrafiles = extravalues.pop('extrafiles', {})
747 extra_pn = extravalues.pop('PN', None) 766 extra_pn = extravalues.pop('PN', None)
748 extra_pv = extravalues.pop('PV', None) 767 extra_pv = extravalues.pop('PV', None)
768 run_tasks = extravalues.pop('run_tasks', "").split()
749 769
750 if extra_pv and not realpv: 770 if extra_pv and not realpv:
751 realpv = extra_pv 771 realpv = extra_pv
@@ -806,7 +826,8 @@ def create_recipe(args):
806 extraoutdir = os.path.join(os.path.dirname(outfile), pn) 826 extraoutdir = os.path.join(os.path.dirname(outfile), pn)
807 bb.utils.mkdirhier(extraoutdir) 827 bb.utils.mkdirhier(extraoutdir)
808 for destfn, extrafile in extrafiles.items(): 828 for destfn, extrafile in extrafiles.items():
809 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))
810 831
811 lines = lines_before 832 lines = lines_before
812 lines_before = [] 833 lines_before = []
@@ -821,7 +842,7 @@ def create_recipe(args):
821 line = line.replace(realpv, '${PV}') 842 line = line.replace(realpv, '${PV}')
822 if pn: 843 if pn:
823 line = line.replace(pn, '${BPN}') 844 line = line.replace(pn, '${BPN}')
824 if line == 'S = "${WORKDIR}/${BPN}-${PV}"': 845 if line == 'S = "${UNPACKDIR}/${BPN}-${PV}"' or 'tmp-recipetool-' in line:
825 skipblank = True 846 skipblank = True
826 continue 847 continue
827 elif line.startswith('SRC_URI = '): 848 elif line.startswith('SRC_URI = '):
@@ -867,8 +888,10 @@ def create_recipe(args):
867 outlines.append('') 888 outlines.append('')
868 outlines.extend(lines_after) 889 outlines.extend(lines_after)
869 890
891 outlines = [ line.rstrip('\n') +"\n" for line in outlines]
892
870 if extravalues: 893 if extravalues:
871 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=False) 894 _, outlines = oe.recipeutils.patch_recipe_lines(outlines, extravalues, trailing_newline=True)
872 895
873 if args.extract_to: 896 if args.extract_to:
874 scriptutils.git_convert_standalone_clone(srctree) 897 scriptutils.git_convert_standalone_clone(srctree)
@@ -884,7 +907,7 @@ def create_recipe(args):
884 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)
885 908
886 if outfile == '-': 909 if outfile == '-':
887 sys.stdout.write('\n'.join(outlines) + '\n') 910 sys.stdout.write(''.join(outlines) + '\n')
888 else: 911 else:
889 with open(outfile, 'w') as f: 912 with open(outfile, 'w') as f:
890 lastline = None 913 lastline = None
@@ -892,9 +915,14 @@ def create_recipe(args):
892 if not lastline and not line: 915 if not lastline and not line:
893 # Skip extra blank lines 916 # Skip extra blank lines
894 continue 917 continue
895 f.write('%s\n' % line) 918 f.write('%s' % line)
896 lastline = line 919 lastline = line
897 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)
898 926
899 if tempsrc: 927 if tempsrc:
900 if args.keep_temp: 928 if args.keep_temp:
@@ -917,23 +945,32 @@ def split_value(value):
917 else: 945 else:
918 return value 946 return value
919 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
920def handle_license_vars(srctree, lines_before, handled, extravalues, d): 954def handle_license_vars(srctree, lines_before, handled, extravalues, d):
921 lichandled = [x for x in handled if x[0] == 'license'] 955 lichandled = [x for x in handled if x[0] == 'license']
922 if lichandled: 956 if lichandled:
923 # 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
924 return lichandled[0][1] 958 return lichandled[0][1]
925 959
926 licvalues = guess_license(srctree, d) 960 licvalues = find_licenses(srctree, d)
927 licenses = [] 961 licenses = []
928 lic_files_chksum = [] 962 lic_files_chksum = []
929 lic_unknown = [] 963 lic_unknown = []
930 lines = [] 964 lines = []
931 if licvalues: 965 if licvalues:
932 for licvalue in licvalues: 966 for licvalue in licvalues:
933 if not licvalue[0] in licenses: 967 license = licvalue[0]
934 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)
935 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]))
936 if licvalue[0] == 'Unknown': 973 if license == 'Unknown':
937 lic_unknown.append(licvalue[1]) 974 lic_unknown.append(licvalue[1])
938 if lic_unknown: 975 if lic_unknown:
939 lines.append('#') 976 lines.append('#')
@@ -942,9 +979,7 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
942 for licfile in lic_unknown: 979 for licfile in lic_unknown:
943 lines.append('# %s' % licfile) 980 lines.append('# %s' % licfile)
944 981
945 extra_license = split_value(extravalues.pop('LICENSE', [])) 982 extra_license = tidy_licenses(extravalues.pop('LICENSE', ''))
946 if '&' in extra_license:
947 extra_license.remove('&')
948 if extra_license: 983 if extra_license:
949 if licenses == ['Unknown']: 984 if licenses == ['Unknown']:
950 licenses = extra_license 985 licenses = extra_license
@@ -985,7 +1020,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') 1020 lines.append('# instead of &. If there is any doubt, check the accompanying documentation')
986 lines.append('# to determine which situation is applicable.') 1021 lines.append('# to determine which situation is applicable.')
987 1022
988 lines.append('LICENSE = "%s"' % ' & '.join(licenses)) 1023 lines.append('LICENSE = "%s"' % ' & '.join(sorted(licenses, key=str.casefold)))
989 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum)) 1024 lines.append('LIC_FILES_CHKSUM = "%s"' % ' \\\n '.join(lic_files_chksum))
990 lines.append('') 1025 lines.append('')
991 1026
@@ -1002,166 +1037,15 @@ def handle_license_vars(srctree, lines_before, handled, extravalues, d):
1002 handled.append(('license', licvalues)) 1037 handled.append(('license', licvalues))
1003 return licvalues 1038 return licvalues
1004 1039
1005def get_license_md5sums(d, static_only=False):
1006 import bb.utils
1007 md5sums = {}
1008 if not static_only:
1009 # Gather md5sums of license files in common license dir
1010 commonlicdir = d.getVar('COMMON_LICENSE_DIR')
1011 for fn in os.listdir(commonlicdir):
1012 md5value = bb.utils.md5_file(os.path.join(commonlicdir, fn))
1013 md5sums[md5value] = fn
1014 # The following were extracted from common values in various recipes
1015 # (double checking the license against the license file itself, not just
1016 # the LICENSE value in the recipe)
1017 md5sums['94d55d512a9ba36caa9b7df079bae19f'] = 'GPLv2'
1018 md5sums['b234ee4d69f5fce4486a80fdaf4a4263'] = 'GPLv2'
1019 md5sums['59530bdf33659b29e73d4adb9f9f6552'] = 'GPLv2'
1020 md5sums['0636e73ff0215e8d672dc4c32c317bb3'] = 'GPLv2'
1021 md5sums['eb723b61539feef013de476e68b5c50a'] = 'GPLv2'
1022 md5sums['751419260aa954499f7abaabaa882bbe'] = 'GPLv2'
1023 md5sums['393a5ca445f6965873eca0259a17f833'] = 'GPLv2'
1024 md5sums['12f884d2ae1ff87c09e5b7ccc2c4ca7e'] = 'GPLv2'
1025 md5sums['8ca43cbc842c2336e835926c2166c28b'] = 'GPLv2'
1026 md5sums['ebb5c50ab7cab4baeffba14977030c07'] = 'GPLv2'
1027 md5sums['c93c0550bd3173f4504b2cbd8991e50b'] = 'GPLv2'
1028 md5sums['9ac2e7cff1ddaf48b6eab6028f23ef88'] = 'GPLv2'
1029 md5sums['4325afd396febcb659c36b49533135d4'] = 'GPLv2'
1030 md5sums['18810669f13b87348459e611d31ab760'] = 'GPLv2'
1031 md5sums['d7810fab7487fb0aad327b76f1be7cd7'] = 'GPLv2' # the Linux kernel's COPYING file
1032 md5sums['bbb461211a33b134d42ed5ee802b37ff'] = 'LGPLv2.1'
1033 md5sums['7fbc338309ac38fefcd64b04bb903e34'] = 'LGPLv2.1'
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
1055
1056def crunch_license(licfile):
1057 '''
1058 Remove non-material text from a license file and then check
1059 its md5sum against a known list. This works well for licenses
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 '''
1065
1066 import oe.utils
1067
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
1075 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
1080 crunched_md5sums['e72e5dfef0b1a4ca8a3d26a60587db66'] = 'BSD-2-Clause'
1081 # https://github.com/spigwitmer/fakeds1963s/blob/master/LICENSE
1082 crunched_md5sums['8be76ac6d191671f347ee4916baa637e'] = 'GPLv2'
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
1087 # http://git.neil.brown.name/?p=mdadm.git;a=blob;f=COPYING;h=d159169d1050894d3ea3b98e1c965c4058208fe1;hb=HEAD
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
1094 crunched_md5sums['2ea316ed973ae176e502e2297b574bb3'] = 'LGPLv2.1'
1095 # unixODBC-2.3.4 COPYING
1096 crunched_md5sums['1daebd9491d1e8426900b4fa5a422814'] = 'LGPLv2.1'
1097 # https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.LGPLv3
1098 crunched_md5sums['2ebfb3bb49b9a48a075cc1425e7f4129'] = 'LGPLv3'
1099 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/epl-v10
1100 crunched_md5sums['efe2cb9a35826992b9df68224e3c2628'] = 'EPL-1.0'
1101 # https://raw.githubusercontent.com/eclipse/mosquitto/v1.4.14/edl-v10
1102 crunched_md5sums['0a9c78c0a398d1bbce4a166757d60387'] = 'EDL-1.0'
1103 lictext = []
1104 with open(licfile, 'r', errors='surrogateescape') as f:
1105 for line in f:
1106 # Drop opening statements
1107 if copyright_re.match(line):
1108 continue
1109 elif license_title_re.match(line):
1110 continue
1111 elif license_statement_re.match(line):
1112 continue
1113 # Squash spaces, and replace smart quotes, double quotes
1114 # and backticks with single quotes
1115 line = oe.utils.squashspaces(line.strip())
1116 line = line.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u201c","'").replace(u"\u201d", "'").replace('"', '\'').replace('`', '\'')
1117 if line:
1118 lictext.append(line)
1119
1120 m = hashlib.md5()
1121 try:
1122 m.update(' '.join(lictext).encode('utf-8'))
1123 md5val = m.hexdigest()
1124 except UnicodeEncodeError:
1125 md5val = None
1126 lictext = ''
1127 license = crunched_md5sums.get(md5val, None)
1128 return license, md5val, lictext
1129
1130def guess_license(srctree, d):
1131 import bb
1132 md5sums = get_license_md5sums(d)
1133
1134 licenses = []
1135 licspecs = ['*LICEN[CS]E*', 'COPYING*', '*[Ll]icense*', 'LEGAL*', '[Ll]egal*', '*GPL*', 'README.lic*', 'COPYRIGHT*', '[Cc]opyright*', 'e[dp]l-v10']
1136 licfiles = []
1137 for root, dirs, files in os.walk(srctree):
1138 for fn in files:
1139 for spec in licspecs:
1140 if fnmatch.fnmatch(fn, spec):
1141 fullpath = os.path.join(root, fn)
1142 if not fullpath in licfiles:
1143 licfiles.append(fullpath)
1144 for licfile in licfiles:
1145 md5value = bb.utils.md5_file(licfile)
1146 license = md5sums.get(md5value, None)
1147 if not license:
1148 license, crunched_md5, lictext = crunch_license(licfile)
1149 if not license:
1150 license = 'Unknown'
1151 licenses.append((license, os.path.relpath(licfile, srctree), md5value))
1152
1153 # FIXME should we grab at least one source file with a license header and add that too?
1154
1155 return licenses
1156
1157def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'): 1040def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'):
1158 """ 1041 """
1159 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(),
1160 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
1161 package-specific LICENSE values. 1044 package-specific LICENSE values.
1162 """ 1045 """
1163 pkglicenses = {pn: []} 1046 pkglicenses = {pn: []}
1164 for license, licpath, _ in licvalues: 1047 for license, licpath, _ in licvalues:
1048 license = fixup_license(license)
1165 for pkgname, pkgpath in packages.items(): 1049 for pkgname, pkgpath in packages.items():
1166 if licpath.startswith(pkgpath + '/'): 1050 if licpath.startswith(pkgpath + '/'):
1167 if pkgname in pkglicenses: 1051 if pkgname in pkglicenses:
@@ -1174,13 +1058,24 @@ def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn
1174 pkglicenses[pn].append(license) 1058 pkglicenses[pn].append(license)
1175 outlicenses = {} 1059 outlicenses = {}
1176 for pkgname in packages: 1060 for pkgname in packages:
1177 license = ' '.join(list(set(pkglicenses.get(pkgname, ['Unknown'])))) or 'Unknown' 1061 # Assume AND operator between license files
1178 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:
1179 license = fallback_licenses[pkgname] 1064 license = fallback_licenses[pkgname]
1180 outlines.append('LICENSE_%s = "%s"' % (pkgname, license)) 1065 licenses = tidy_licenses(license)
1181 outlicenses[pkgname] = license.split() 1066 license = ' & '.join(licenses)
1067 outlines.append('LICENSE:%s = "%s"' % (pkgname, license))
1068 outlicenses[pkgname] = licenses
1182 return outlicenses 1069 return outlicenses
1183 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
1184def read_pkgconfig_provides(d): 1079def read_pkgconfig_provides(d):
1185 pkgdatadir = d.getVar('PKGDATA_DIR') 1080 pkgdatadir = d.getVar('PKGDATA_DIR')
1186 pkgmap = {} 1081 pkgmap = {}
@@ -1311,7 +1206,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)') 1206 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)') 1207 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') 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')
1314 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS) 1210 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).') 1211 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) 1212 parser_create.set_defaults(func=create_recipe)
1317