blob: 8560c27e93487ca669e6d94ab977504552526873 [file] [log] [blame]
Patrick Williams92b42cb2022-09-03 06:53:57 -05001#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7ROOTFS_LICENSE_DIR = "${IMAGE_ROOTFS}/usr/share/common-licenses"
8
9# This requires LICENSE_CREATE_PACKAGE=1 to work too
10COMPLEMENTARY_GLOB[lic-pkgs] = "*-lic"
11
12python() {
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
19python 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
32python 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
56def 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
188def 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
230def 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)
Patrick Williams864cc432023-02-09 14:54:44 -0600238 pn = d.getVar("PN")
Patrick Williams92b42cb2022-09-03 06:53:57 -0500239 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
258get_deployed_dependencies[vardepsexclude] = "BB_TASKDEPDATA"
259
260def 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
275ROOTFS_POSTPROCESS_COMMAND:prepend = "write_package_manifest; license_create_manifest; "
276do_rootfs[recrdeptask] += "do_populate_lic"
277
278python do_populate_lic_deploy() {
279 license_deployed_manifest(d)
280 oe.qa.exit_if_errors(d)
281}
282
283addtask populate_lic_deploy before do_build after do_image_complete
284do_populate_lic_deploy[recrdeptask] += "do_populate_lic do_deploy"
285
286python 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}
295IMAGE_QA_COMMANDS += "license_qa_dead_symlink"