diff options
Diffstat (limited to 'meta/classes/license.bbclass')
-rw-r--r-- | meta/classes/license.bbclass | 436 |
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 | |||
6 | LICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses" | ||
7 | LICSSTATEDIR = "${WORKDIR}/license-destdir/" | ||
8 | |||
9 | # Create extra package with license texts and add it to RRECOMMENDS_${PN} | ||
10 | LICENSE_CREATE_PACKAGE[type] = "boolean" | ||
11 | LICENSE_CREATE_PACKAGE ??= "0" | ||
12 | LICENSE_PACKAGE_SUFFIX ??= "-lic" | ||
13 | LICENSE_FILES_DIRECTORY ??= "${datadir}/licenses/" | ||
14 | |||
15 | addtask populate_lic after do_patch before do_build | ||
16 | do_populate_lic[dirs] = "${LICSSTATEDIR}/${PN}" | ||
17 | do_populate_lic[cleandirs] = "${LICSSTATEDIR}" | ||
18 | |||
19 | python 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 | |||
34 | PSEUDO_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 | ||
36 | python 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 | } | ||
46 | perform_packagecopy[vardeps] += "LICENSE_CREATE_PACKAGE" | ||
47 | |||
48 | def 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 | |||
55 | def 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 | |||
75 | def 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 | |||
115 | def 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 | |||
246 | def 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 | |||
252 | def 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 | |||
259 | def 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. | ||
276 | AVAILABLE_LICENSES := "${@' '.join(available_licenses(d))}" | ||
277 | |||
278 | def 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 | |||
303 | def 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 | |||
309 | def 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 | |||
329 | def 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 | |||
342 | def 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 | |||
404 | def 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 | |||
427 | SSTATETASKS += "do_populate_lic" | ||
428 | do_populate_lic[sstate-inputdirs] = "${LICSSTATEDIR}" | ||
429 | do_populate_lic[sstate-outputdirs] = "${LICENSE_DIRECTORY}/" | ||
430 | |||
431 | IMAGE_CLASSES_append = " license_image" | ||
432 | |||
433 | python do_populate_lic_setscene () { | ||
434 | sstate_setscene(d) | ||
435 | } | ||
436 | addtask do_populate_lic_setscene | ||