blob: 19b3dc55ba2ac00c3e7904ac8475d4addc9dec22 [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
Andrew Geissler220dafd2023-10-04 10:18:08 -050021 license_image_dir = d.expand('${LICENSE_DIRECTORY}/${SSTATE_PKGARCH}/${IMAGE_NAME}')
Patrick Williams92b42cb2022-09-03 06:53:57 -050022 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'),
Andrew Geissler220dafd2023-10-04 10:18:08 -050052 d.getVar('SSTATE_PKGARCH'), d.getVar('IMAGE_NAME'), 'license.manifest')
Patrick Williams92b42cb2022-09-03 06:53:57 -050053 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)
Andrew Geissler220dafd2023-10-04 10:18:08 -050062 pkgarchs = d.getVar("SSTATE_ARCHS").split()
63 pkgarchs.reverse()
Patrick Williams92b42cb2022-09-03 06:53:57 -050064
65 exceptions = (d.getVar("INCOMPATIBLE_LICENSE_EXCEPTIONS") or "").split()
66 with open(license_manifest, "w") as license_file:
67 for pkg in sorted(pkg_dic):
68 remaining_bad_licenses = oe.license.apply_pkg_license_exception(pkg, bad_licenses, exceptions)
69 incompatible_licenses = incompatible_pkg_license(d, remaining_bad_licenses, pkg_dic[pkg]["LICENSE"])
70 if incompatible_licenses:
71 bb.fatal("Package %s cannot be installed into the image because it has incompatible license(s): %s" %(pkg, ' '.join(incompatible_licenses)))
72 else:
73 incompatible_licenses = incompatible_pkg_license(d, bad_licenses, pkg_dic[pkg]["LICENSE"])
74 if incompatible_licenses:
75 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)
76 try:
77 (pkg_dic[pkg]["LICENSE"], pkg_dic[pkg]["LICENSES"]) = \
78 oe.license.manifest_licenses(pkg_dic[pkg]["LICENSE"],
79 remaining_bad_licenses, canonical_license, d)
80 except oe.license.LicenseError as exc:
81 bb.fatal('%s: %s' % (d.getVar('P'), exc))
82
83 if not "IMAGE_MANIFEST" in pkg_dic[pkg]:
84 # Rootfs manifest
85 license_file.write("PACKAGE NAME: %s\n" % pkg)
86 license_file.write("PACKAGE VERSION: %s\n" % pkg_dic[pkg]["PV"])
87 license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"])
88 license_file.write("LICENSE: %s\n\n" % pkg_dic[pkg]["LICENSE"])
89
90 # If the package doesn't contain any file, that is, its size is 0, the license
91 # isn't relevant as far as the final image is concerned. So doing license check
92 # doesn't make much sense, skip it.
93 if pkg_dic[pkg]["PKGSIZE:%s" % pkg] == "0":
94 continue
95 else:
96 # Image manifest
97 license_file.write("RECIPE NAME: %s\n" % pkg_dic[pkg]["PN"])
98 license_file.write("VERSION: %s\n" % pkg_dic[pkg]["PV"])
99 license_file.write("LICENSE: %s\n" % pkg_dic[pkg]["LICENSE"])
100 license_file.write("FILES: %s\n\n" % pkg_dic[pkg]["FILES"])
101
102 for lic in pkg_dic[pkg]["LICENSES"]:
Andrew Geissler220dafd2023-10-04 10:18:08 -0500103 for pkgarch in pkgarchs:
104 lic_file = os.path.join(d.getVar('LICENSE_DIRECTORY'),
105 pkgarch,
106 pkg_dic[pkg]["PN"], "generic_%s" %
107 re.sub(r'\+', '', lic))
108 if os.path.exists(lic_file):
109 break
Patrick Williams92b42cb2022-09-03 06:53:57 -0500110 # add explicity avoid of CLOSED license because isn't generic
111 if lic == "CLOSED":
112 continue
113
114 if not os.path.exists(lic_file):
115 oe.qa.handle_error('license-file-missing',
116 "The license listed %s was not in the "\
117 "licenses collected for recipe %s"
118 % (lic, pkg_dic[pkg]["PN"]), d)
119 oe.qa.exit_if_errors(d)
120
121 # Two options here:
122 # - Just copy the manifest
123 # - Copy the manifest and the license directories
124 # With both options set we see a .5 M increase in core-image-minimal
125 copy_lic_manifest = d.getVar('COPY_LIC_MANIFEST')
126 copy_lic_dirs = d.getVar('COPY_LIC_DIRS')
127 if rootfs and copy_lic_manifest == "1":
128 rootfs_license_dir = d.getVar('ROOTFS_LICENSE_DIR')
129 bb.utils.mkdirhier(rootfs_license_dir)
130 rootfs_license_manifest = os.path.join(rootfs_license_dir,
131 os.path.split(license_manifest)[1])
132 if not os.path.exists(rootfs_license_manifest):
133 oe.path.copyhardlink(license_manifest, rootfs_license_manifest)
134
135 if copy_lic_dirs == "1":
136 for pkg in sorted(pkg_dic):
137 pkg_rootfs_license_dir = os.path.join(rootfs_license_dir, pkg)
138 bb.utils.mkdirhier(pkg_rootfs_license_dir)
Andrew Geissler220dafd2023-10-04 10:18:08 -0500139 for pkgarch in pkgarchs:
140 pkg_license_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'),
141 pkgarch, pkg_dic[pkg]["PN"])
142 if os.path.exists(pkg_license_dir):
143 break
144 if not os.path.exists(pkg_license_dir ):
145 bb.fatal("Couldn't find license information for dependency %s" % pkg)
Patrick Williams92b42cb2022-09-03 06:53:57 -0500146
147 pkg_manifest_licenses = [canonical_license(d, lic) \
148 for lic in pkg_dic[pkg]["LICENSES"]]
149
150 licenses = os.listdir(pkg_license_dir)
151 for lic in licenses:
152 pkg_license = os.path.join(pkg_license_dir, lic)
153 pkg_rootfs_license = os.path.join(pkg_rootfs_license_dir, lic)
154
155 if re.match(r"^generic_.*$", lic):
156 generic_lic = canonical_license(d,
157 re.search(r"^generic_(.*)$", lic).group(1))
158
159 # Do not copy generic license into package if isn't
160 # declared into LICENSES of the package.
161 if not re.sub(r'\+$', '', generic_lic) in \
162 [re.sub(r'\+', '', lic) for lic in \
163 pkg_manifest_licenses]:
164 continue
165
166 if oe.license.license_ok(generic_lic,
167 bad_licenses) == False:
168 continue
169
170 # Make sure we use only canonical name for the license file
171 generic_lic_file = "generic_%s" % generic_lic
172 rootfs_license = os.path.join(rootfs_license_dir, generic_lic_file)
173 if not os.path.exists(rootfs_license):
174 oe.path.copyhardlink(pkg_license, rootfs_license)
175
176 if not os.path.exists(pkg_rootfs_license):
177 os.symlink(os.path.join('..', generic_lic_file), pkg_rootfs_license)
178 else:
179 if (oe.license.license_ok(canonical_license(d,
180 lic), bad_licenses) == False or
181 os.path.exists(pkg_rootfs_license)):
182 continue
183
184 oe.path.copyhardlink(pkg_license, pkg_rootfs_license)
185 # Fixup file ownership and permissions
186 for walkroot, dirs, files in os.walk(rootfs_license_dir):
187 for f in files:
188 p = os.path.join(walkroot, f)
189 os.lchown(p, 0, 0)
190 if not os.path.islink(p):
191 os.chmod(p, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
192 for dir in dirs:
193 p = os.path.join(walkroot, dir)
194 os.lchown(p, 0, 0)
195 os.chmod(p, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
196
Andrew Geissler220dafd2023-10-04 10:18:08 -0500197write_license_files[vardepsexclude] = "SSTATE_ARCHS"
Patrick Williams92b42cb2022-09-03 06:53:57 -0500198
199def license_deployed_manifest(d):
200 """
201 Write the license manifest for the deployed recipes.
202 The deployed recipes usually includes the bootloader
203 and extra files to boot the target.
204 """
205
206 dep_dic = {}
207 man_dic = {}
208 lic_dir = d.getVar("LICENSE_DIRECTORY")
Andrew Geissler220dafd2023-10-04 10:18:08 -0500209 pkgarchs = d.getVar("SSTATE_ARCHS").split()
210 pkgarchs.reverse()
Patrick Williams92b42cb2022-09-03 06:53:57 -0500211
212 dep_dic = get_deployed_dependencies(d)
213 for dep in dep_dic.keys():
214 man_dic[dep] = {}
215 # It is necessary to mark this will be used for image manifest
216 man_dic[dep]["IMAGE_MANIFEST"] = True
217 man_dic[dep]["PN"] = dep
218 man_dic[dep]["FILES"] = \
219 " ".join(get_deployed_files(dep_dic[dep]))
Andrew Geissler220dafd2023-10-04 10:18:08 -0500220
221 for pkgarch in pkgarchs:
222 licfile = os.path.join(lic_dir, pkgarch, dep, "recipeinfo")
223 if os.path.exists(licfile):
224 break
225 if not os.path.exists(licfile):
226 bb.fatal("Couldn't find license information for dependency %s" % dep)
227 with open(licfile, "r") as f:
Patrick Williams92b42cb2022-09-03 06:53:57 -0500228 for line in f.readlines():
229 key,val = line.split(": ", 1)
230 man_dic[dep][key] = val[:-1]
231
Andrew Geissler220dafd2023-10-04 10:18:08 -0500232 lic_manifest_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), d.getVar('SSTATE_PKGARCH'),
Patrick Williams92b42cb2022-09-03 06:53:57 -0500233 d.getVar('IMAGE_NAME'))
234 bb.utils.mkdirhier(lic_manifest_dir)
235 image_license_manifest = os.path.join(lic_manifest_dir, 'image_license.manifest')
236 write_license_files(d, image_license_manifest, man_dic, rootfs=False)
237
238 link_name = d.getVar('IMAGE_LINK_NAME')
239 if link_name:
Andrew Geissler220dafd2023-10-04 10:18:08 -0500240 lic_manifest_symlink_dir = os.path.join(d.getVar('LICENSE_DIRECTORY'), d.getVar('SSTATE_PKGARCH'),
Patrick Williams92b42cb2022-09-03 06:53:57 -0500241 link_name)
242 # remove old symlink
243 if os.path.islink(lic_manifest_symlink_dir):
244 os.unlink(lic_manifest_symlink_dir)
245
246 # create the image dir symlink
247 if lic_manifest_dir != lic_manifest_symlink_dir:
248 os.symlink(lic_manifest_dir, lic_manifest_symlink_dir)
249
Andrew Geissler220dafd2023-10-04 10:18:08 -0500250license_deployed_manifest[vardepsexclude] = "SSTATE_ARCHS"
251
Patrick Williams92b42cb2022-09-03 06:53:57 -0500252def get_deployed_dependencies(d):
253 """
254 Get all the deployed dependencies of an image
255 """
256
257 deploy = {}
258 # Get all the dependencies for the current task (rootfs).
259 taskdata = d.getVar("BB_TASKDEPDATA", False)
Patrick Williams864cc432023-02-09 14:54:44 -0600260 pn = d.getVar("PN")
Patrick Williams92b42cb2022-09-03 06:53:57 -0500261 depends = list(set([dep[0] for dep
262 in list(taskdata.values())
263 if not dep[0].endswith("-native") and not dep[0] == pn]))
264
265 # To verify what was deployed it checks the rootfs dependencies against
266 # the SSTATE_MANIFESTS for "deploy" task.
267 # The manifest file name contains the arch. Because we are not running
268 # in the recipe context it is necessary to check every arch used.
269 sstate_manifest_dir = d.getVar("SSTATE_MANIFESTS")
270 archs = list(set(d.getVar("SSTATE_ARCHS").split()))
271 for dep in depends:
272 for arch in archs:
273 sstate_manifest_file = os.path.join(sstate_manifest_dir,
274 "manifest-%s-%s.deploy" % (arch, dep))
275 if os.path.exists(sstate_manifest_file):
276 deploy[dep] = sstate_manifest_file
277 break
278
279 return deploy
Andrew Geissler220dafd2023-10-04 10:18:08 -0500280get_deployed_dependencies[vardepsexclude] = "BB_TASKDEPDATA SSTATE_ARCHS"
Patrick Williams92b42cb2022-09-03 06:53:57 -0500281
282def get_deployed_files(man_file):
283 """
284 Get the files deployed from the sstate manifest
285 """
286
287 dep_files = []
288 excluded_files = []
289 with open(man_file, "r") as manifest:
290 all_files = manifest.read()
291 for f in all_files.splitlines():
292 if ((not (os.path.islink(f) or os.path.isdir(f))) and
293 not os.path.basename(f) in excluded_files):
294 dep_files.append(os.path.basename(f))
295 return dep_files
296
Andrew Geissler5082cc72023-09-11 08:41:39 -0400297ROOTFS_POSTPROCESS_COMMAND:prepend = "write_package_manifest license_create_manifest "
Patrick Williams92b42cb2022-09-03 06:53:57 -0500298do_rootfs[recrdeptask] += "do_populate_lic"
299
300python do_populate_lic_deploy() {
301 license_deployed_manifest(d)
302 oe.qa.exit_if_errors(d)
303}
304
305addtask populate_lic_deploy before do_build after do_image_complete
306do_populate_lic_deploy[recrdeptask] += "do_populate_lic do_deploy"
307
308python license_qa_dead_symlink() {
309 import os
310
311 for root, dirs, files in os.walk(d.getVar('ROOTFS_LICENSE_DIR')):
312 for file in files:
313 full_path = root + "/" + file
314 if os.path.islink(full_path) and not os.path.exists(full_path):
315 bb.error("broken symlink: " + full_path)
316}
317IMAGE_QA_COMMANDS += "license_qa_dead_symlink"