diff options
Diffstat (limited to 'scripts/lib/recipetool/create.py')
-rw-r--r-- | scripts/lib/recipetool/create.py | 311 |
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 | |||
18 | import hashlib | 18 | import hashlib |
19 | import bb.fetch2 | 19 | import bb.fetch2 |
20 | logger = logging.getLogger('recipetool') | 20 | logger = logging.getLogger('recipetool') |
21 | from oe.license import tidy_licenses | ||
22 | from oe.license_finder import find_licenses | ||
21 | 23 | ||
22 | tinfoil = None | 24 | tinfoil = None |
23 | plugins = None | 25 | plugins = 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): | |||
366 | def reformat_git_uri(uri): | 368 | def 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 | ||
948 | def fixup_license(value): | ||
949 | # Ensure licenses with OR starts and ends with brackets | ||
950 | if '|' in value: | ||
951 | return '(' + value + ')' | ||
952 | return value | ||
953 | |||
920 | def handle_license_vars(srctree, lines_before, handled, extravalues, d): | 954 | def 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 | ||
1005 | def 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 | |||
1056 | def 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 | |||
1130 | def 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 | |||
1157 | def split_pkg_licenses(licvalues, packages, outlines, fallback_licenses=None, pn='${PN}'): | 1040 | def 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 | ||
1071 | def 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 | |||
1184 | def read_pkgconfig_provides(d): | 1079 | def 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 | |||