summaryrefslogtreecommitdiffstats
path: root/meta/classes/license.bbclass
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes/license.bbclass')
-rw-r--r--meta/classes/license.bbclass436
1 files changed, 0 insertions, 436 deletions
diff --git a/meta/classes/license.bbclass b/meta/classes/license.bbclass
deleted file mode 100644
index bcea0b3cb5..0000000000
--- a/meta/classes/license.bbclass
+++ /dev/null
@@ -1,436 +0,0 @@
1# Populates LICENSE_DIRECTORY as set in distro config with the license files as set by
2# LIC_FILES_CHKSUM.
3# TODO:
4# - There is a real issue revolving around license naming standards.
5
6LICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses"
7LICSSTATEDIR = "${WORKDIR}/license-destdir/"
8
9# Create extra package with license texts and add it to RRECOMMENDS_${PN}
10LICENSE_CREATE_PACKAGE[type] = "boolean"
11LICENSE_CREATE_PACKAGE ??= "0"
12LICENSE_PACKAGE_SUFFIX ??= "-lic"
13LICENSE_FILES_DIRECTORY ??= "${datadir}/licenses/"
14
15addtask populate_lic after do_patch before do_build
16do_populate_lic[dirs] = "${LICSSTATEDIR}/${PN}"
17do_populate_lic[cleandirs] = "${LICSSTATEDIR}"
18
19python do_populate_lic() {
20 """
21 Populate LICENSE_DIRECTORY with licenses.
22 """
23 lic_files_paths = find_license_files(d)
24
25 # The base directory we wrangle licenses to
26 destdir = os.path.join(d.getVar('LICSSTATEDIR'), d.getVar('PN'))
27 copy_license_files(lic_files_paths, destdir)
28 info = get_recipe_info(d)
29 with open(os.path.join(destdir, "recipeinfo"), "w") as f:
30 for key in sorted(info.keys()):
31 f.write("%s: %s\n" % (key, info[key]))
32}
33
34PSEUDO_IGNORE_PATHS .= ",${@','.join(((d.getVar('COMMON_LICENSE_DIR') or '') + ' ' + (d.getVar('LICENSE_PATH') or '')).split())}"
35# it would be better to copy them in do_install_append, but find_license_filesa is python
36python perform_packagecopy_prepend () {
37 enabled = oe.data.typed_value('LICENSE_CREATE_PACKAGE', d)
38 if d.getVar('CLASSOVERRIDE') == 'class-target' and enabled:
39 lic_files_paths = find_license_files(d)
40
41 # LICENSE_FILES_DIRECTORY starts with '/' so os.path.join cannot be used to join D and LICENSE_FILES_DIRECTORY
42 destdir = d.getVar('D') + os.path.join(d.getVar('LICENSE_FILES_DIRECTORY'), d.getVar('PN'))
43 copy_license_files(lic_files_paths, destdir)
44 add_package_and_files(d)
45}
46perform_packagecopy[vardeps] += "LICENSE_CREATE_PACKAGE"
47
48def get_recipe_info(d):
49 info = {}
50 info["PV"] = d.getVar("PV")
51 info["PR"] = d.getVar("PR")
52 info["LICENSE"] = d.getVar("LICENSE")
53 return info
54
55def add_package_and_files(d):
56 packages = d.getVar('PACKAGES')
57 files = d.getVar('LICENSE_FILES_DIRECTORY')
58 pn = d.getVar('PN')
59 pn_lic = "%s%s" % (pn, d.getVar('LICENSE_PACKAGE_SUFFIX', False))
60 if pn_lic in packages.split():
61 bb.warn("%s package already existed in %s." % (pn_lic, pn))
62 else:
63 # first in PACKAGES to be sure that nothing else gets LICENSE_FILES_DIRECTORY
64 d.setVar('PACKAGES', "%s %s" % (pn_lic, packages))
65 d.setVar('FILES_' + pn_lic, files)
66 for pn in packages.split():
67 if pn == pn_lic:
68 continue
69 rrecommends_pn = d.getVar('RRECOMMENDS_' + pn)
70 if rrecommends_pn:
71 d.setVar('RRECOMMENDS_' + pn, "%s %s" % (pn_lic, rrecommends_pn))
72 else:
73 d.setVar('RRECOMMENDS_' + pn, "%s" % (pn_lic))
74
75def copy_license_files(lic_files_paths, destdir):
76 import shutil
77 import errno
78
79 bb.utils.mkdirhier(destdir)
80 for (basename, path, beginline, endline) in lic_files_paths:
81 try:
82 src = path
83 dst = os.path.join(destdir, basename)
84 if os.path.exists(dst):
85 os.remove(dst)
86 if os.path.islink(src):
87 src = os.path.realpath(src)
88 canlink = os.access(src, os.W_OK) and (os.stat(src).st_dev == os.stat(destdir).st_dev) and beginline is None and endline is None
89 if canlink:
90 try:
91 os.link(src, dst)
92 except OSError as err:
93 if err.errno == errno.EXDEV:
94 # Copy license files if hard-link is not possible even if st_dev is the
95 # same on source and destination (docker container with device-mapper?)
96 canlink = False
97 else:
98 raise
99 # Only chown if we did hardling, and, we're running under pseudo
100 if canlink and os.environ.get('PSEUDO_DISABLED') == '0':
101 os.chown(dst,0,0)
102 if not canlink:
103 begin_idx = int(beginline)-1 if beginline is not None else None
104 end_idx = int(endline) if endline is not None else None
105 if begin_idx is None and end_idx is None:
106 shutil.copyfile(src, dst)
107 else:
108 with open(src, 'rb') as src_f:
109 with open(dst, 'wb') as dst_f:
110 dst_f.write(b''.join(src_f.readlines()[begin_idx:end_idx]))
111
112 except Exception as e:
113 bb.warn("Could not copy license file %s to %s: %s" % (src, dst, e))
114
115def find_license_files(d):
116 """
117 Creates list of files used in LIC_FILES_CHKSUM and generic LICENSE files.
118 """
119 import shutil
120 import oe.license
121 from collections import defaultdict, OrderedDict
122
123 # All the license files for the package
124 lic_files = d.getVar('LIC_FILES_CHKSUM') or ""
125 pn = d.getVar('PN')
126 # The license files are located in S/LIC_FILE_CHECKSUM.
127 srcdir = d.getVar('S')
128 # Directory we store the generic licenses as set in the distro configuration
129 generic_directory = d.getVar('COMMON_LICENSE_DIR')
130 # List of basename, path tuples
131 lic_files_paths = []
132 # hash for keep track generic lics mappings
133 non_generic_lics = {}
134 # Entries from LIC_FILES_CHKSUM
135 lic_chksums = {}
136 license_source_dirs = []
137 license_source_dirs.append(generic_directory)
138 try:
139 additional_lic_dirs = d.getVar('LICENSE_PATH').split()
140 for lic_dir in additional_lic_dirs:
141 license_source_dirs.append(lic_dir)
142 except:
143 pass
144
145 class FindVisitor(oe.license.LicenseVisitor):
146 def visit_Str(self, node):
147 #
148 # Until I figure out what to do with
149 # the two modifiers I support (or greater = +
150 # and "with exceptions" being *
151 # we'll just strip out the modifier and put
152 # the base license.
153 find_license(node.s.replace("+", "").replace("*", ""))
154 self.generic_visit(node)
155
156 def find_license(license_type):
157 try:
158 bb.utils.mkdirhier(gen_lic_dest)
159 except:
160 pass
161 spdx_generic = None
162 license_source = None
163 # If the generic does not exist we need to check to see if there is an SPDX mapping to it,
164 # unless NO_GENERIC_LICENSE is set.
165 for lic_dir in license_source_dirs:
166 if not os.path.isfile(os.path.join(lic_dir, license_type)):
167 if d.getVarFlag('SPDXLICENSEMAP', license_type) != None:
168 # Great, there is an SPDXLICENSEMAP. We can copy!
169 bb.debug(1, "We need to use a SPDXLICENSEMAP for %s" % (license_type))
170 spdx_generic = d.getVarFlag('SPDXLICENSEMAP', license_type)
171 license_source = lic_dir
172 break
173 elif os.path.isfile(os.path.join(lic_dir, license_type)):
174 spdx_generic = license_type
175 license_source = lic_dir
176 break
177
178 non_generic_lic = d.getVarFlag('NO_GENERIC_LICENSE', license_type)
179 if spdx_generic and license_source:
180 # we really should copy to generic_ + spdx_generic, however, that ends up messing the manifest
181 # audit up. This should be fixed in emit_pkgdata (or, we actually got and fix all the recipes)
182
183 lic_files_paths.append(("generic_" + license_type, os.path.join(license_source, spdx_generic),
184 None, None))
185
186 # The user may attempt to use NO_GENERIC_LICENSE for a generic license which doesn't make sense
187 # and should not be allowed, warn the user in this case.
188 if d.getVarFlag('NO_GENERIC_LICENSE', license_type):
189 bb.warn("%s: %s is a generic license, please don't use NO_GENERIC_LICENSE for it." % (pn, license_type))
190
191 elif non_generic_lic and non_generic_lic in lic_chksums:
192 # if NO_GENERIC_LICENSE is set, we copy the license files from the fetched source
193 # of the package rather than the license_source_dirs.
194 lic_files_paths.append(("generic_" + license_type,
195 os.path.join(srcdir, non_generic_lic), None, None))
196 non_generic_lics[non_generic_lic] = license_type
197 else:
198 # Add explicity avoid of CLOSED license because this isn't generic
199 if license_type != 'CLOSED':
200 # And here is where we warn people that their licenses are lousy
201 bb.warn("%s: No generic license file exists for: %s in any provider" % (pn, license_type))
202 pass
203
204 if not generic_directory:
205 bb.fatal("COMMON_LICENSE_DIR is unset. Please set this in your distro config")
206
207 for url in lic_files.split():
208 try:
209 (method, host, path, user, pswd, parm) = bb.fetch.decodeurl(url)
210 if method != "file" or not path:
211 raise bb.fetch.MalformedUrl()
212 except bb.fetch.MalformedUrl:
213 bb.fatal("%s: LIC_FILES_CHKSUM contains an invalid URL: %s" % (d.getVar('PF'), url))
214 # We want the license filename and path
215 chksum = parm.get('md5', None)
216 beginline = parm.get('beginline')
217 endline = parm.get('endline')
218 lic_chksums[path] = (chksum, beginline, endline)
219
220 v = FindVisitor()
221 try:
222 v.visit_string(d.getVar('LICENSE'))
223 except oe.license.InvalidLicense as exc:
224 bb.fatal('%s: %s' % (d.getVar('PF'), exc))
225 except SyntaxError:
226 bb.warn("%s: Failed to parse it's LICENSE field." % (d.getVar('PF')))
227 # Add files from LIC_FILES_CHKSUM to list of license files
228 lic_chksum_paths = defaultdict(OrderedDict)
229 for path, data in sorted(lic_chksums.items()):
230 lic_chksum_paths[os.path.basename(path)][data] = (os.path.join(srcdir, path), data[1], data[2])
231 for basename, files in lic_chksum_paths.items():
232 if len(files) == 1:
233 # Don't copy again a LICENSE already handled as non-generic
234 if basename in non_generic_lics:
235 continue
236 data = list(files.values())[0]
237 lic_files_paths.append(tuple([basename] + list(data)))
238 else:
239 # If there are multiple different license files with identical
240 # basenames we rename them to <file>.0, <file>.1, ...
241 for i, data in enumerate(files.values()):
242 lic_files_paths.append(tuple(["%s.%d" % (basename, i)] + list(data)))
243
244 return lic_files_paths
245
246def return_spdx(d, license):
247 """
248 This function returns the spdx mapping of a license if it exists.
249 """
250 return d.getVarFlag('SPDXLICENSEMAP', license)
251
252def canonical_license(d, license):
253 """
254 Return the canonical (SPDX) form of the license if available (so GPLv3
255 becomes GPL-3.0) or the passed license if there is no canonical form.
256 """
257 return d.getVarFlag('SPDXLICENSEMAP', license) or license
258
259def available_licenses(d):
260 """
261 Return the available licenses by searching the directories specified by
262 COMMON_LICENSE_DIR and LICENSE_PATH.
263 """
264 lic_dirs = ((d.getVar('COMMON_LICENSE_DIR') or '') + ' ' +
265 (d.getVar('LICENSE_PATH') or '')).split()
266
267 licenses = []
268 for lic_dir in lic_dirs:
269 licenses += os.listdir(lic_dir)
270
271 licenses = sorted(licenses)
272 return licenses
273
274# Only determine the list of all available licenses once. This assumes that any
275# additions to LICENSE_PATH have been done before this file is parsed.
276AVAILABLE_LICENSES := "${@' '.join(available_licenses(d))}"
277
278def expand_wildcard_licenses(d, wildcard_licenses):
279 """
280 Return actual spdx format license names if wildcards are used. We expand
281 wildcards from SPDXLICENSEMAP flags and AVAILABLE_LICENSES.
282 """
283 import fnmatch
284
285 # Assume if we're passed "GPLv3" or "*GPLv3" it means -or-later as well
286 for lic in wildcard_licenses[:]:
287 if not lic.endswith(("-or-later", "-only", "*")):
288 wildcard_licenses.append(lic + "+")
289
290 licenses = wildcard_licenses[:]
291 spdxmapkeys = d.getVarFlags('SPDXLICENSEMAP').keys()
292 for wld_lic in wildcard_licenses:
293 spdxflags = fnmatch.filter(spdxmapkeys, wld_lic)
294 licenses += [d.getVarFlag('SPDXLICENSEMAP', flag) for flag in spdxflags]
295
296 spdx_lics = d.getVar('AVAILABLE_LICENSES').split()
297 for wld_lic in wildcard_licenses:
298 licenses += fnmatch.filter(spdx_lics, wld_lic)
299
300 licenses = list(set(licenses))
301 return licenses
302
303def incompatible_license_contains(license, truevalue, falsevalue, d):
304 license = canonical_license(d, license)
305 bad_licenses = (d.getVar('INCOMPATIBLE_LICENSE') or "").split()
306 bad_licenses = expand_wildcard_licenses(d, bad_licenses)
307 return truevalue if license in bad_licenses else falsevalue
308
309def incompatible_pkg_license(d, dont_want_licenses, license):
310 # Handles an "or" or two license sets provided by
311 # flattened_licenses(), pick one that works if possible.
312 def choose_lic_set(a, b):
313 return a if all(oe.license.license_ok(canonical_license(d, lic),
314 dont_want_licenses) for lic in a) else b
315
316 try:
317 licenses = oe.license.flattened_licenses(license, choose_lic_set)
318 except oe.license.LicenseError as exc:
319 bb.fatal('%s: %s' % (d.getVar('P'), exc))
320
321 incompatible_lic = []
322 for l in licenses:
323 license = canonical_license(d, l)
324 if not oe.license.license_ok(license, dont_want_licenses):
325 incompatible_lic.append(license)
326
327 return sorted(incompatible_lic)
328
329def incompatible_license(d, dont_want_licenses, package=None):
330 """
331 This function checks if a recipe has only incompatible licenses. It also
332 take into consideration 'or' operand. dont_want_licenses should be passed
333 as canonical (SPDX) names.
334 """
335 import oe.license
336 license = d.getVar("LICENSE_%s" % package) if package else None
337 if not license:
338 license = d.getVar('LICENSE')
339
340 return incompatible_pkg_license(d, dont_want_licenses, license)
341
342def check_license_flags(d):
343 """
344 This function checks if a recipe has any LICENSE_FLAGS that
345 aren't whitelisted.
346
347 If it does, it returns the all LICENSE_FLAGS missing from the whitelist, or
348 all of the LICENSE_FLAGS if there is no whitelist.
349
350 If everything is is properly whitelisted, it returns None.
351 """
352
353 def license_flag_matches(flag, whitelist, pn):
354 """
355 Return True if flag matches something in whitelist, None if not.
356
357 Before we test a flag against the whitelist, we append _${PN}
358 to it. We then try to match that string against the
359 whitelist. This covers the normal case, where we expect
360 LICENSE_FLAGS to be a simple string like 'commercial', which
361 the user typically matches exactly in the whitelist by
362 explicitly appending the package name e.g 'commercial_foo'.
363 If we fail the match however, we then split the flag across
364 '_' and append each fragment and test until we either match or
365 run out of fragments.
366 """
367 flag_pn = ("%s_%s" % (flag, pn))
368 for candidate in whitelist:
369 if flag_pn == candidate:
370 return True
371
372 flag_cur = ""
373 flagments = flag_pn.split("_")
374 flagments.pop() # we've already tested the full string
375 for flagment in flagments:
376 if flag_cur:
377 flag_cur += "_"
378 flag_cur += flagment
379 for candidate in whitelist:
380 if flag_cur == candidate:
381 return True
382 return False
383
384 def all_license_flags_match(license_flags, whitelist):
385 """ Return all unmatched flags, None if all flags match """
386 pn = d.getVar('PN')
387 split_whitelist = whitelist.split()
388 flags = []
389 for flag in license_flags.split():
390 if not license_flag_matches(flag, split_whitelist, pn):
391 flags.append(flag)
392 return flags if flags else None
393
394 license_flags = d.getVar('LICENSE_FLAGS')
395 if license_flags:
396 whitelist = d.getVar('LICENSE_FLAGS_WHITELIST')
397 if not whitelist:
398 return license_flags.split()
399 unmatched_flags = all_license_flags_match(license_flags, whitelist)
400 if unmatched_flags:
401 return unmatched_flags
402 return None
403
404def check_license_format(d):
405 """
406 This function checks if LICENSE is well defined,
407 Validate operators in LICENSES.
408 No spaces are allowed between LICENSES.
409 """
410 pn = d.getVar('PN')
411 licenses = d.getVar('LICENSE')
412 from oe.license import license_operator, license_operator_chars, license_pattern
413
414 elements = list(filter(lambda x: x.strip(), license_operator.split(licenses)))
415 for pos, element in enumerate(elements):
416 if license_pattern.match(element):
417 if pos > 0 and license_pattern.match(elements[pos - 1]):
418 bb.warn('%s: LICENSE value "%s" has an invalid format - license names ' \
419 'must be separated by the following characters to indicate ' \
420 'the license selection: %s' %
421 (pn, licenses, license_operator_chars))
422 elif not license_operator.match(element):
423 bb.warn('%s: LICENSE value "%s" has an invalid separator "%s" that is not ' \
424 'in the valid list of separators (%s)' %
425 (pn, licenses, element, license_operator_chars))
426
427SSTATETASKS += "do_populate_lic"
428do_populate_lic[sstate-inputdirs] = "${LICSSTATEDIR}"
429do_populate_lic[sstate-outputdirs] = "${LICENSE_DIRECTORY}/"
430
431IMAGE_CLASSES_append = " license_image"
432
433python do_populate_lic_setscene () {
434 sstate_setscene(d)
435}
436addtask do_populate_lic_setscene