summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/classes/copyleft_compliance.bbclass94
1 files changed, 94 insertions, 0 deletions
diff --git a/meta/classes/copyleft_compliance.bbclass b/meta/classes/copyleft_compliance.bbclass
new file mode 100644
index 0000000000..5d9ab11926
--- /dev/null
+++ b/meta/classes/copyleft_compliance.bbclass
@@ -0,0 +1,94 @@
1# Deploy sources for recipes for compliance with copyleft-style licenses
2# Defaults to using symlinks, as it's a quick operation, and one can easily
3# follow the links when making use of the files (e.g. tar with the -h arg).
4#
5# By default, includes all GPL and LGPL, and excludes CLOSED and Proprietary.
6#
7# vi:sts=4:sw=4:et
8
9COPYLEFT_SOURCES_DIR ?= '${DEPLOY_DIR}/copyleft_sources'
10
11COPYLEFT_LICENSE_INCLUDE ?= 'GPL* LGPL*'
12COPYLEFT_LICENSE_INCLUDE[type] = 'list'
13COPYLEFT_LICENSE_INCLUDE[doc] = 'Space separated list of globs which include licenses'
14
15COPYLEFT_LICENSE_EXCLUDE ?= 'CLOSED Proprietary'
16COPYLEFT_LICENSE_EXCLUDE[type] = 'list'
17COPYLEFT_LICENSE_INCLUDE[doc] = 'Space separated list of globs which exclude licenses'
18
19
20def copyleft_should_include(d):
21 """Determine if this recipe's sources should be deployed for compliance"""
22 import ast
23 import oe.license
24 from fnmatch import fnmatchcase as fnmatch
25
26 if oe.utils.inherits(d, 'native', 'nativesdk', 'cross', 'crossdk'):
27 # not a target recipe
28 return
29
30 include = oe.data.typed_value('COPYLEFT_LICENSE_INCLUDE', d)
31 exclude = oe.data.typed_value('COPYLEFT_LICENSE_EXCLUDE', d)
32
33 def include_license(license):
34 if any(fnmatch(license, pattern) for pattern in exclude):
35 return False
36 if any(fnmatch(license, pattern) for pattern in include):
37 return True
38 return False
39
40 def choose_licenses(a, b):
41 """Select the left option in an OR if all its licenses are to be included"""
42 if all(include_license(lic) for lic in a):
43 return a
44 else:
45 return b
46
47 try:
48 licenses = oe.license.flattened_licenses(d.getVar('LICENSE', True), choose_licenses)
49 except oe.license.InvalidLicense as exc:
50 bb.fatal('%s: %s' % (d.getVar('PF', True), exc))
51
52 return all(include_license(lic) for lic in licenses)
53
54python do_prepare_copyleft_sources () {
55 """Populate a tree of the recipe sources and emit patch series files"""
56 import os.path
57 import shutil
58
59 if not copyleft_should_include(d):
60 return
61
62 sources_dir = d.getVar('COPYLEFT_SOURCES_DIR', 1)
63 src_uri = d.getVar('SRC_URI', 1).split()
64 fetch = bb.fetch2.Fetch(src_uri, d)
65 ud = fetch.ud
66
67 locals = (fetch.localpath(url) for url in fetch.urls)
68 localpaths = [local for local in locals if not local.endswith('.bb')]
69 if not localpaths:
70 return
71
72 pf = d.getVar('PF', True)
73 dest = os.path.join(sources_dir, pf)
74 shutil.rmtree(dest, ignore_errors=True)
75 bb.mkdirhier(dest)
76
77 for path in localpaths:
78 os.symlink(path, os.path.join(dest, os.path.basename(path)))
79
80 patches = src_patches(d)
81 for patch in patches:
82 _, _, local, _, _, parm = bb.decodeurl(patch)
83 patchdir = parm.get('patchdir')
84 if patchdir:
85 series = os.path.join(dest, 'series.subdir.%s' % patchdir.replace('/', '_'))
86 else:
87 series = os.path.join(dest, 'series')
88
89 with open(series, 'a') as s:
90 s.write('%s -p%s\n' % (os.path.basename(local), parm['striplevel']))
91}
92
93addtask prepare_copyleft_sources after do_fetch before do_build
94do_build[recrdeptask] += 'do_prepare_copyleft_sources'