diff options
| -rw-r--r-- | meta/classes/license.bbclass | 59 | ||||
| -rw-r--r-- | meta/lib/oe/license.py | 32 | ||||
| -rw-r--r-- | meta/lib/oe/tests/test_license.py | 38 |
3 files changed, 90 insertions, 39 deletions
diff --git a/meta/classes/license.bbclass b/meta/classes/license.bbclass index 4d036b171e..8c6e2d2c9b 100644 --- a/meta/classes/license.bbclass +++ b/meta/classes/license.bbclass | |||
| @@ -1,17 +1,17 @@ | |||
| 1 | # Populates LICENSE_DIRECTORY as set in distro config with the license files as set by | 1 | # Populates LICENSE_DIRECTORY as set in distro config with the license files as set by |
| 2 | # LIC_FILES_CHKSUM. | 2 | # LIC_FILES_CHKSUM. |
| 3 | # TODO: | 3 | # TODO: |
| 4 | # - We should also enable the ability to put the generated license directory onto the | 4 | # - We should also enable the ability to put the generated license directory onto the |
| 5 | # rootfs | 5 | # rootfs |
| 6 | # - Gather up more generic licenses | 6 | # - Gather up more generic licenses |
| 7 | # - There is a real issue revolving around license naming standards. See license names | 7 | # - There is a real issue revolving around license naming standards. See license names |
| 8 | # licenses.conf and compare them to the license names in the recipes. You'll see some | 8 | # licenses.conf and compare them to the license names in the recipes. You'll see some |
| 9 | # differences and that should be corrected. | 9 | # differences and that should be corrected. |
| 10 | 10 | ||
| 11 | LICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses" | 11 | LICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses" |
| 12 | LICSSTATEDIR = "${WORKDIR}/license-destdir/" | 12 | LICSSTATEDIR = "${WORKDIR}/license-destdir/" |
| 13 | 13 | ||
| 14 | addtask populate_lic after do_patch before do_package | 14 | addtask populate_lic after do_patch before do_package |
| 15 | do_populate_lic[dirs] = "${LICSSTATEDIR}/${PN}" | 15 | do_populate_lic[dirs] = "${LICSSTATEDIR}/${PN}" |
| 16 | do_populate_lic[cleandirs] = "${LICSSTATEDIR}" | 16 | do_populate_lic[cleandirs] = "${LICSSTATEDIR}" |
| 17 | 17 | ||
| @@ -20,7 +20,7 @@ do_populate_lic[cleandirs] = "${LICSSTATEDIR}" | |||
| 20 | # break the non-standardized license names that we find in LICENSE, we'll set | 20 | # break the non-standardized license names that we find in LICENSE, we'll set |
| 21 | # up a bunch of VarFlags to accomodate non-SPDX license names. | 21 | # up a bunch of VarFlags to accomodate non-SPDX license names. |
| 22 | # | 22 | # |
| 23 | # We should really discuss standardizing this field, but that's a longer term goal. | 23 | # We should really discuss standardizing this field, but that's a longer term goal. |
| 24 | # For now, we can do this and it should grab the most common LICENSE naming variations. | 24 | # For now, we can do this and it should grab the most common LICENSE naming variations. |
| 25 | 25 | ||
| 26 | #GPL variations | 26 | #GPL variations |
| @@ -57,37 +57,25 @@ python do_populate_lic() { | |||
| 57 | import os | 57 | import os |
| 58 | import bb | 58 | import bb |
| 59 | import shutil | 59 | import shutil |
| 60 | import ast | 60 | import oe.license |
| 61 | |||
| 62 | class LicenseVisitor(ast.NodeVisitor): | ||
| 63 | def generic_visit(self, node): | ||
| 64 | ast.NodeVisitor.generic_visit(self, node) | ||
| 65 | 61 | ||
| 62 | class FindVisitor(oe.license.LicenseVisitor): | ||
| 66 | def visit_Str(self, node): | 63 | def visit_Str(self, node): |
| 67 | # | 64 | # |
| 68 | # Until I figure out what to do with | 65 | # Until I figure out what to do with |
| 69 | # the two modifiers I support (or greater = + | 66 | # the two modifiers I support (or greater = + |
| 70 | # and "with exceptions" being * | 67 | # and "with exceptions" being * |
| 71 | # we'll just strip out the modifier and put | 68 | # we'll just strip out the modifier and put |
| 72 | # the base license. | 69 | # the base license. |
| 73 | find_license(node.s.replace("+", "").replace("*", "")) | 70 | find_license(node.s.replace("+", "").replace("*", "")) |
| 74 | ast.NodeVisitor.generic_visit(self, node) | 71 | self.generic_visit(node) |
| 75 | |||
| 76 | def visit_BinOp(self, node): | ||
| 77 | op = node.op | ||
| 78 | if isinstance(op, ast.BitOr): | ||
| 79 | x = LicenseVisitor() | ||
| 80 | x.visit(node.left) | ||
| 81 | x.visit(node.right) | ||
| 82 | else: | ||
| 83 | ast.NodeVisitor.generic_visit(self, node) | ||
| 84 | 72 | ||
| 85 | def copy_license(source, destination, file_name): | 73 | def copy_license(source, destination, file_name): |
| 86 | try: | 74 | try: |
| 87 | bb.copyfile(os.path.join(source, file_name), os.path.join(destination, file_name)) | 75 | bb.copyfile(os.path.join(source, file_name), os.path.join(destination, file_name)) |
| 88 | except: | 76 | except: |
| 89 | bb.warn("%s: No generic license file exists for: %s at %s" % (pn, file_name, source)) | 77 | bb.warn("%s: No generic license file exists for: %s at %s" % (pn, file_name, source)) |
| 90 | pass | 78 | pass |
| 91 | 79 | ||
| 92 | def link_license(source, destination, file_name): | 80 | def link_license(source, destination, file_name): |
| 93 | try: | 81 | try: |
| @@ -108,8 +96,8 @@ python do_populate_lic() { | |||
| 108 | # Great, there is an SPDXLICENSEMAP. We can copy! | 96 | # Great, there is an SPDXLICENSEMAP. We can copy! |
| 109 | bb.note("We need to use a SPDXLICENSEMAP for %s" % (license_type)) | 97 | bb.note("We need to use a SPDXLICENSEMAP for %s" % (license_type)) |
| 110 | spdx_generic = d.getVarFlag('SPDXLICENSEMAP', license_type) | 98 | spdx_generic = d.getVarFlag('SPDXLICENSEMAP', license_type) |
| 111 | copy_license(generic_directory, gen_lic_dest, spdx_generic) | 99 | copy_license(generic_directory, gen_lic_dest, spdx_generic) |
| 112 | link_license(gen_lic_dest, destdir, spdx_generic) | 100 | link_license(gen_lic_dest, destdir, spdx_generic) |
| 113 | else: | 101 | else: |
| 114 | # And here is where we warn people that their licenses are lousy | 102 | # And here is where we warn people that their licenses are lousy |
| 115 | bb.warn("%s: No generic license file exists for: %s at %s" % (pn, license_type, generic_directory)) | 103 | bb.warn("%s: No generic license file exists for: %s at %s" % (pn, license_type, generic_directory)) |
| @@ -117,7 +105,7 @@ python do_populate_lic() { | |||
| 117 | pass | 105 | pass |
| 118 | elif os.path.isfile(os.path.join(generic_directory, license_type)): | 106 | elif os.path.isfile(os.path.join(generic_directory, license_type)): |
| 119 | copy_license(generic_directory, gen_lic_dest, license_type) | 107 | copy_license(generic_directory, gen_lic_dest, license_type) |
| 120 | link_license(gen_lic_dest, destdir, license_type) | 108 | link_license(gen_lic_dest, destdir, license_type) |
| 121 | 109 | ||
| 122 | # All the license types for the package | 110 | # All the license types for the package |
| 123 | license_types = d.getVar('LICENSE', True) | 111 | license_types = d.getVar('LICENSE', True) |
| @@ -130,7 +118,7 @@ python do_populate_lic() { | |||
| 130 | srcdir = d.getVar('S', True) | 118 | srcdir = d.getVar('S', True) |
| 131 | # Directory we store the generic licenses as set in the distro configuration | 119 | # Directory we store the generic licenses as set in the distro configuration |
| 132 | generic_directory = d.getVar('COMMON_LICENSE_DIR', True) | 120 | generic_directory = d.getVar('COMMON_LICENSE_DIR', True) |
| 133 | 121 | ||
| 134 | try: | 122 | try: |
| 135 | bb.mkdirhier(destdir) | 123 | bb.mkdirhier(destdir) |
| 136 | except: | 124 | except: |
| @@ -153,21 +141,14 @@ python do_populate_lic() { | |||
| 153 | # If the copy didn't occur, something horrible went wrong and we fail out | 141 | # If the copy didn't occur, something horrible went wrong and we fail out |
| 154 | if ret is False or ret == 0: | 142 | if ret is False or ret == 0: |
| 155 | bb.warn("%s could not be copied for some reason. It may not exist. WARN for now." % srclicfile) | 143 | bb.warn("%s could not be copied for some reason. It may not exist. WARN for now." % srclicfile) |
| 156 | 144 | ||
| 157 | gen_lic_dest = os.path.join(d.getVar('LICENSE_DIRECTORY', True), "common-licenses") | 145 | gen_lic_dest = os.path.join(d.getVar('LICENSE_DIRECTORY', True), "common-licenses") |
| 158 | 146 | ||
| 159 | clean_licenses = "" | 147 | v = FindVisitor() |
| 160 | 148 | try: | |
| 161 | for x in license_types.replace("(", " ( ").replace(")", " ) ").split(): | 149 | v.visit_string(license_types) |
| 162 | if ((x != "(") and (x != ")") and (x != "&") and (x != "|")): | 150 | except oe.license.InvalidLicense as exc: |
| 163 | clean_licenses += "'" + x + "'" | 151 | bb.fatal("%s: %s" % (d.getVar('PF', True), exc)) |
| 164 | else: | ||
| 165 | clean_licenses += " " + x + " " | ||
| 166 | |||
| 167 | # lstrip any possible indents, since ast needs python syntax. | ||
| 168 | node = ast.parse(clean_licenses.lstrip()) | ||
| 169 | v = LicenseVisitor() | ||
| 170 | v.visit(node) | ||
| 171 | } | 152 | } |
| 172 | 153 | ||
| 173 | SSTATETASKS += "do_populate_lic" | 154 | SSTATETASKS += "do_populate_lic" |
diff --git a/meta/lib/oe/license.py b/meta/lib/oe/license.py new file mode 100644 index 0000000000..b230d3ef45 --- /dev/null +++ b/meta/lib/oe/license.py | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | # vi:sts=4:sw=4:et | ||
| 2 | """Code for parsing OpenEmbedded license strings""" | ||
| 3 | |||
| 4 | import ast | ||
| 5 | import re | ||
| 6 | |||
| 7 | class InvalidLicense(StandardError): | ||
| 8 | def __init__(self, license): | ||
| 9 | self.license = license | ||
| 10 | StandardError.__init__(self) | ||
| 11 | |||
| 12 | def __str__(self): | ||
| 13 | return "invalid license '%s'" % self.license | ||
| 14 | |||
| 15 | license_operator = re.compile('([&|() ])') | ||
| 16 | license_pattern = re.compile('[a-zA-Z0-9.+_\-]+$') | ||
| 17 | |||
| 18 | class LicenseVisitor(ast.NodeVisitor): | ||
| 19 | """Syntax tree visitor which can accept OpenEmbedded license strings""" | ||
| 20 | def visit_string(self, licensestr): | ||
| 21 | new_elements = [] | ||
| 22 | elements = filter(lambda x: x.strip(), license_operator.split(licensestr)) | ||
| 23 | for pos, element in enumerate(elements): | ||
| 24 | if license_pattern.match(element): | ||
| 25 | if pos > 0 and license_pattern.match(elements[pos-1]): | ||
| 26 | new_elements.append('&') | ||
| 27 | element = '"' + element + '"' | ||
| 28 | elif not license_operator.match(element): | ||
| 29 | raise InvalidLicense(element) | ||
| 30 | new_elements.append(element) | ||
| 31 | |||
| 32 | self.visit(ast.parse(' '.join(new_elements))) | ||
diff --git a/meta/lib/oe/tests/test_license.py b/meta/lib/oe/tests/test_license.py new file mode 100644 index 0000000000..cb949fc76f --- /dev/null +++ b/meta/lib/oe/tests/test_license.py | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | import unittest | ||
| 2 | import oe.license | ||
| 3 | |||
| 4 | class SeenVisitor(oe.license.LicenseVisitor): | ||
| 5 | def __init__(self): | ||
| 6 | self.seen = [] | ||
| 7 | oe.license.LicenseVisitor.__init__(self) | ||
| 8 | |||
| 9 | def visit_Str(self, node): | ||
| 10 | self.seen.append(node.s) | ||
| 11 | |||
| 12 | class TestSingleLicense(unittest.TestCase): | ||
| 13 | licenses = [ | ||
| 14 | "GPLv2", | ||
| 15 | "LGPL-2.0", | ||
| 16 | "Artistic", | ||
| 17 | "MIT", | ||
| 18 | "GPLv3+", | ||
| 19 | "FOO_BAR", | ||
| 20 | ] | ||
| 21 | invalid_licenses = ["GPL/BSD"] | ||
| 22 | |||
| 23 | @staticmethod | ||
| 24 | def parse(licensestr): | ||
| 25 | visitor = SeenVisitor() | ||
| 26 | visitor.visit_string(licensestr) | ||
| 27 | return visitor.seen | ||
| 28 | |||
| 29 | def test_single_licenses(self): | ||
| 30 | for license in self.licenses: | ||
| 31 | licenses = self.parse(license) | ||
| 32 | self.assertListEqual(licenses, [license]) | ||
| 33 | |||
| 34 | def test_invalid_licenses(self): | ||
| 35 | for license in self.invalid_licenses: | ||
| 36 | with self.assertRaises(oe.license.InvalidLicense) as cm: | ||
| 37 | self.parse(license) | ||
| 38 | self.assertEqual(cm.exception.license, license) | ||
