summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/classes/license.bbclass59
-rw-r--r--meta/lib/oe/license.py32
-rw-r--r--meta/lib/oe/tests/test_license.py38
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
11LICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses" 11LICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses"
12LICSSTATEDIR = "${WORKDIR}/license-destdir/" 12LICSSTATEDIR = "${WORKDIR}/license-destdir/"
13 13
14addtask populate_lic after do_patch before do_package 14addtask populate_lic after do_patch before do_package
15do_populate_lic[dirs] = "${LICSSTATEDIR}/${PN}" 15do_populate_lic[dirs] = "${LICSSTATEDIR}/${PN}"
16do_populate_lic[cleandirs] = "${LICSSTATEDIR}" 16do_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
173SSTATETASKS += "do_populate_lic" 154SSTATETASKS += "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
4import ast
5import re
6
7class 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
15license_operator = re.compile('([&|() ])')
16license_pattern = re.compile('[a-zA-Z0-9.+_\-]+$')
17
18class 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 @@
1import unittest
2import oe.license
3
4class 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
12class 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)