diff options
Diffstat (limited to 'meta/classes-recipe/license_image.bbclass')
-rw-r--r-- | meta/classes-recipe/license_image.bbclass | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/meta/classes-recipe/license_image.bbclass b/meta/classes-recipe/license_image.bbclass new file mode 100644 index 0000000000..b60d6e44f4 --- /dev/null +++ b/meta/classes-recipe/license_image.bbclass | |||
@@ -0,0 +1,295 @@ | |||
1 | # | ||
2 | # Copyright OpenEmbedded Contributors | ||
3 | # | ||
4 | # SPDX-License-Identifier: MIT | ||
5 | # | ||
6 | |||
7 | ROOTFS_LICENSE_DIR = "${IMAGE_ROOTFS}/usr/share/common-licenses" | ||
8 | |||
9 | # This requires LICENSE_CREATE_PACKAGE=1 to work too | ||
10 | COMPLEMENTARY_GLOB[lic-pkgs] = "*-lic" | ||
11 | |||
12 | python() { | ||
13 | if not oe.data.typed_value('LICENSE_CREATE_PACKAGE', d): | ||
14 | features = set(oe.data.typed_value('IMAGE_FEATURES', d)) | ||
15 | if 'lic-pkgs' in features: | ||
16 | bb.error("'lic-pkgs' in IMAGE_FEATURES but LICENSE_CREATE_PACKAGE not enabled to generate -lic packages") | ||
17 | } | ||
18 | |||
19 | python write_package_manifest() { | ||
20 | # Get list of installed packages | ||
21 | license_image_dir = d.expand('${LICENSE_DIRECTORY}/${IMAGE_NAME}') | ||
22 | bb.utils.mkdirhier(license_image_dir) | ||
23 | from oe.rootfs import image_list_installed_packages | ||
24 | from oe.utils import format_pkg_list | ||
25 | |||
26 | pkgs = image_list_installed_packages(d) | ||
27 | output = format_pkg_list(pkgs) | ||
28 | with open(os.path.join(license_image_dir, 'package.manifest'), "w+") as package_manifest: | ||
29 | package_manifest.write(output) | ||
30 | } | ||
31 | |||
32 | python license_create_manifest() { | ||
33 | import oe.packagedata | ||
34 | from oe.rootfs import image_list_installed_packages | ||
35 | |||
36 | build_images_from_feeds = d.getVar('BUILD_IMAGES_FROM_FEEDS') | ||
37 | if build_images_from_feeds == "1": | ||
38 | return 0 | ||
39 | |||
40 | pkg_dic = {} | ||
41 | for pkg in sorted(image_list_installed_packages(d)): | ||
42 | pkg_info = os.path.join(d.getVar('PKGDATA_DIR'), | ||
43 | 'runtime-reverse', pkg) | ||
44 | pkg_name = os.path.basename(os.readlink(pkg_info)) | ||
45 | |||
46 | pkg_dic[pkg_name] = oe.packagedata.read_pkgdatafile(pkg_info) | ||
47 | if not "LICENSE" in pkg_dic[pkg_name].keys(): | ||
48 | pkg_lic_name = "LICENSE:" + pkg_name | ||
49 | pkg_dic[pkg_name]["LICENSE"] = pkg_dic[pkg_name][pkg_lic_name] | ||
50 | |||
51 | rootfs_license_manifest = os.path.join(d.getVar('LICENSE_DIRECTORY'), | ||
52 | d.getVar('IMAGE_NAME'), 'license.manifest') | ||
53 | write_license_files(d, rootfs_license_manifest, pkg_dic, rootfs=True) | ||
54 | } | ||
55 | |||
56 | def write_license_files(d, license_manifest, pkg_dic, rootfs=True): | ||
57 | import re | ||
58 | import stat | ||
59 | |||
60 | bad_licenses = (d.getVar("INCOMPATIBLE_LICENSE") or "").split() | ||
61 | bad_licenses = expand_wildcard_licenses(d, bad_licenses) | ||
62 | |||
63 | exceptions = (d.getVar("INCOMPATIBLE_LICENSE_EXCEPTIONS") or "").split() | ||
64 | with open(license_manifest, "w") as license_file: | ||
65 | for pkg in sorted(pkg_dic): | ||
66 | remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions) | ||
67 | incompatible_licenses = incompatible_pkg_license(d, remaining_bad_licenses, pkg_dic[pkg]["LICENSE"]) | ||
68 | if incompatible_licenses: | ||
69 | bb.fatal("Package %s cannot be installed into the image because it has incompatible license(s): %s" %(pkg, ' '.join(incompatible_licenses))) | ||
70 | else: | ||
71 | incompatible_licenses = incompatible_pkg_license(d, bad_licenses, pkg_dic[pkg]["LICENSE"]) | ||
72 | if incompatible_licenses: | ||
73 | oe.qa.handle_error('license-incompatible', "Including %s with incompatible license(s) %s into the image, because it has been allowed by exception list." %(pkg, ' '.join(incompatible_licenses)), d) | ||
74 | try: | ||
75 | (pkg_dic[pkg]["LICENSE"], pkg_dic[pkg]["LICENSES"]) = \ | ||
76 | oe.license.manifest_licenses(pkg_dic[pkg]["LICENSE"], | ||
77 | remaining_bad_licenses, canonical_license, d) | ||
78 | except oe.license.LicenseError as exc: | ||
79 | bb.fatal('%s: %s' % (d.getVar('P'), exc)) | ||
80 | |||
81 | if not "IMAGE_MANIFEST" in pkg_dic[pkg]: | ||
82 | # Rootfs manifest | ||
83 | license_file.write("PACKAGE NAME: %s\n" % pkg) | ||
84 | license_file.write("PACKAGE VERSION: %s\n" % pkg_dic[pkg]["PV"]) | ||
85 | license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"]) | ||
86 | license_file.write("LICENSE: %s\n\n" % pkg_dic[pkg]["LICENSE"]) | ||
87 | |||
88 | # If the package doesn't contain any file, that is, its size is 0, the license | ||
89 | # isn't relevant as far as the final image is concerned. So doing license check | ||
90 | # doesn't make much sense, skip it. | ||
91 | if pkg_dic[pkg]["PKGSIZE:%s" % pkg] == "0": | ||
92 | continue | ||
93 | else: | ||
94 | # Image manifest | ||
95 | license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"]) | ||
96 | license_file.write("VERSION: %s\n" % pkg_dic[pkg]["PV"]) | ||
97 | license_file.write("LICENSE: %s\n" % pkg_dic[pkg]["LICENSE"]) | ||
98 | license_file.write("FILES: %s\n\n" % pkg_dic[pkg]["FILES"]) | ||
99 | |||
100 | for lic in pkg_dic[pkg]["LICENSES"]: | ||
101 | lic_file = os.path.join(d.getVar('LICENSE_DIRECTORY'), | ||
102 | pkg_dic[pkg]["PN"], "generic_%s" % | ||
103 | re.sub(r'\+', '', lic)) | ||
104 | # add explicity avoid of CLOSED license because isn't generic | ||
105 | if lic == "CLOSED": | ||
106 | continue | ||
107 | |||
108 | if not os.path.exists(lic_file): | ||
109 | oe.qa.handle_error('license-file-missing', | ||
110 | "The license listed %s was not in the "\ | ||
111 | "licenses collected for recipe %s" | ||
112 | % (lic, pkg_dic[pkg]["PN"]), d) | ||
113 | oe.qa.exit_if_errors(d) | ||
114 | |||
115 | # Two options here: | ||
116 | # - Just copy the manifest | ||
117 | # - Copy the manifest and the license directories | ||
118 | # With both options set we see a .5 M increase in core-image-minimal | ||
119 | copy_lic_manifest = d.getVar('COPY_LIC_MANIFEST') | ||
120 | copy_lic_dirs = d.getVar('COPY_LIC_DIRS') | ||
121 | if rootfs and copy_lic_manifest == "1": | ||
122 | rootfs_license_dir = d.getVar('ROOTFS_LICENSE_DIR') | ||
123 | bb.utils.mkdirhier(rootfs_license_dir) | ||
124 | rootfs_license_manifest = os.path.join(rootfs_license_dir, | ||
125 | os.path.split(license_manifest)[1]) | ||
126 | if not os.path.exists(rootfs_license_manifest): | ||
127 | oe.path.copyhardlink(license_manifest, rootfs_license_manifest) | ||
128 | |||
129 | if copy_lic_dirs == "1": | ||
130 | for pkg in sorted(pkg_dic): | ||
131 | pkg_rootfs_license_dir = os.path.join(rootfs_license_dir, pkg) | ||
132 | bb.utils.mkdirhier(pkg_rootfs_license_dir) | ||
133 | pkg_license_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), | ||
134 | pkg_dic[pkg]["PN"]) | ||
135 | |||
136 | pkg_manifest_licenses = [canonical_license(d, lic) \ | ||
137 | for lic in pkg_dic[pkg]["LICENSES"]] | ||
138 | |||
139 | licenses = os.listdir(pkg_license_dir) | ||
140 | for lic in licenses: | ||
141 | pkg_license = os.path.join(pkg_license_dir, lic) | ||
142 | pkg_rootfs_license = os.path.join(pkg_rootfs_license_dir, lic) | ||
143 | |||
144 | if re.match(r"^generic_.*$", lic): | ||
145 | generic_lic = canonical_license(d, | ||
146 | re.search(r"^generic_(.*)$", lic).group(1)) | ||
147 | |||
148 | # Do not copy generic license into package if isn't | ||
149 | # declared into LICENSES of the package. | ||
150 | if not re.sub(r'\+$', '', generic_lic) in \ | ||
151 | [re.sub(r'\+', '', lic) for lic in \ | ||
152 | pkg_manifest_licenses]: | ||
153 | continue | ||
154 | |||
155 | if oe.license.license_ok(generic_lic, | ||
156 | bad_licenses) == False: | ||
157 | continue | ||
158 | |||
159 | # Make sure we use only canonical name for the license file | ||
160 | generic_lic_file = "generic_%s" % generic_lic | ||
161 | rootfs_license = os.path.join(rootfs_license_dir, generic_lic_file) | ||
162 | if not os.path.exists(rootfs_license): | ||
163 | oe.path.copyhardlink(pkg_license, rootfs_license) | ||
164 | |||
165 | if not os.path.exists(pkg_rootfs_license): | ||
166 | os.symlink(os.path.join('..', generic_lic_file), pkg_rootfs_license) | ||
167 | else: | ||
168 | if (oe.license.license_ok(canonical_license(d, | ||
169 | lic), bad_licenses) == False or | ||
170 | os.path.exists(pkg_rootfs_license)): | ||
171 | continue | ||
172 | |||
173 | oe.path.copyhardlink(pkg_license, pkg_rootfs_license) | ||
174 | # Fixup file ownership and permissions | ||
175 | for walkroot, dirs, files in os.walk(rootfs_license_dir): | ||
176 | for f in files: | ||
177 | p = os.path.join(walkroot, f) | ||
178 | os.lchown(p, 0, 0) | ||
179 | if not os.path.islink(p): | ||
180 | os.chmod(p, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) | ||
181 | for dir in dirs: | ||
182 | p = os.path.join(walkroot, dir) | ||
183 | os.lchown(p, 0, 0) | ||
184 | os.chmod(p, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) | ||
185 | |||
186 | |||
187 | |||
188 | def license_deployed_manifest(d): | ||
189 | """ | ||
190 | Write the license manifest for the deployed recipes. | ||
191 | The deployed recipes usually includes the bootloader | ||
192 | and extra files to boot the target. | ||
193 | """ | ||
194 | |||
195 | dep_dic = {} | ||
196 | man_dic = {} | ||
197 | lic_dir = d.getVar("LICENSE_DIRECTORY") | ||
198 | |||
199 | dep_dic = get_deployed_dependencies(d) | ||
200 | for dep in dep_dic.keys(): | ||
201 | man_dic[dep] = {} | ||
202 | # It is necessary to mark this will be used for image manifest | ||
203 | man_dic[dep]["IMAGE_MANIFEST"] = True | ||
204 | man_dic[dep]["PN"] = dep | ||
205 | man_dic[dep]["FILES"] = \ | ||
206 | " ".join(get_deployed_files(dep_dic[dep])) | ||
207 | with open(os.path.join(lic_dir, dep, "recipeinfo"), "r") as f: | ||
208 | for line in f.readlines(): | ||
209 | key,val = line.split(": ", 1) | ||
210 | man_dic[dep][key] = val[:-1] | ||
211 | |||
212 | lic_manifest_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), | ||
213 | d.getVar('IMAGE_NAME')) | ||
214 | bb.utils.mkdirhier(lic_manifest_dir) | ||
215 | image_license_manifest = os.path.join(lic_manifest_dir, 'image_license.manifest') | ||
216 | write_license_files(d, image_license_manifest, man_dic, rootfs=False) | ||
217 | |||
218 | link_name = d.getVar('IMAGE_LINK_NAME') | ||
219 | if link_name: | ||
220 | lic_manifest_symlink_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), | ||
221 | link_name) | ||
222 | # remove old symlink | ||
223 | if os.path.islink(lic_manifest_symlink_dir): | ||
224 | os.unlink(lic_manifest_symlink_dir) | ||
225 | |||
226 | # create the image dir symlink | ||
227 | if lic_manifest_dir != lic_manifest_symlink_dir: | ||
228 | os.symlink(lic_manifest_dir, lic_manifest_symlink_dir) | ||
229 | |||
230 | def get_deployed_dependencies(d): | ||
231 | """ | ||
232 | Get all the deployed dependencies of an image | ||
233 | """ | ||
234 | |||
235 | deploy = {} | ||
236 | # Get all the dependencies for the current task (rootfs). | ||
237 | taskdata = d.getVar("BB_TASKDEPDATA", False) | ||
238 | pn = d.getVar("PN", True) | ||
239 | depends = list(set([dep[0] for dep | ||
240 | in list(taskdata.values()) | ||
241 | if not dep[0].endswith("-native") and not dep[0] == pn])) | ||
242 | |||
243 | # To verify what was deployed it checks the rootfs dependencies against | ||
244 | # the SSTATE_MANIFESTS for "deploy" task. | ||
245 | # The manifest file name contains the arch. Because we are not running | ||
246 | # in the recipe context it is necessary to check every arch used. | ||
247 | sstate_manifest_dir = d.getVar("SSTATE_MANIFESTS") | ||
248 | archs = list(set(d.getVar("SSTATE_ARCHS").split())) | ||
249 | for dep in depends: | ||
250 | for arch in archs: | ||
251 | sstate_manifest_file = os.path.join(sstate_manifest_dir, | ||
252 | "manifest-%s-%s.deploy" % (arch, dep)) | ||
253 | if os.path.exists(sstate_manifest_file): | ||
254 | deploy[dep] = sstate_manifest_file | ||
255 | break | ||
256 | |||
257 | return deploy | ||
258 | get_deployed_dependencies[vardepsexclude] = "BB_TASKDEPDATA" | ||
259 | |||
260 | def get_deployed_files(man_file): | ||
261 | """ | ||
262 | Get the files deployed from the sstate manifest | ||
263 | """ | ||
264 | |||
265 | dep_files = [] | ||
266 | excluded_files = [] | ||
267 | with open(man_file, "r") as manifest: | ||
268 | all_files = manifest.read() | ||
269 | for f in all_files.splitlines(): | ||
270 | if ((not (os.path.islink(f) or os.path.isdir(f))) and | ||
271 | not os.path.basename(f) in excluded_files): | ||
272 | dep_files.append(os.path.basename(f)) | ||
273 | return dep_files | ||
274 | |||
275 | ROOTFS_POSTPROCESS_COMMAND:prepend = "write_package_manifest; license_create_manifest; " | ||
276 | do_rootfs[recrdeptask] += "do_populate_lic" | ||
277 | |||
278 | python do_populate_lic_deploy() { | ||
279 | license_deployed_manifest(d) | ||
280 | oe.qa.exit_if_errors(d) | ||
281 | } | ||
282 | |||
283 | addtask populate_lic_deploy before do_build after do_image_complete | ||
284 | do_populate_lic_deploy[recrdeptask] += "do_populate_lic do_deploy" | ||
285 | |||
286 | python license_qa_dead_symlink() { | ||
287 | import os | ||
288 | |||
289 | for root, dirs, files in os.walk(d.getVar('ROOTFS_LICENSE_DIR')): | ||
290 | for file in files: | ||
291 | full_path = root + "/" + file | ||
292 | if os.path.islink(full_path) and not os.path.exists(full_path): | ||
293 | bb.error("broken symlink: " + full_path) | ||
294 | } | ||
295 | IMAGE_QA_COMMANDS += "license_qa_dead_symlink" | ||