1#
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)
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
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"
296