diff options
| author | Joshua Watt <JPEWhacker@gmail.com> | 2024-07-12 09:58:19 -0600 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2024-07-16 14:55:53 +0100 |
| commit | 9850df1b6051cefdef4f6f9acd93cc93ab2b8b75 (patch) | |
| tree | cd3153d83952cad38f351d602e84641cd8425e08 /meta/lib | |
| parent | 454008311b958a080cffb2cf535958fa0be38c05 (diff) | |
| download | poky-9850df1b6051cefdef4f6f9acd93cc93ab2b8b75.tar.gz | |
classes/spdx-common: Move to library
Moves the bulk of the code in the spdx-common bbclass into library code
(From OE-Core rev: 3f9b7c7f6b15493b6890031190ca8d1a10f2f384)
Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib')
| -rw-r--r-- | meta/lib/oe/sbom30.py | 21 | ||||
| -rw-r--r-- | meta/lib/oe/spdx_common.py | 228 |
2 files changed, 230 insertions, 19 deletions
diff --git a/meta/lib/oe/sbom30.py b/meta/lib/oe/sbom30.py index 771e87be79..2532d19dad 100644 --- a/meta/lib/oe/sbom30.py +++ b/meta/lib/oe/sbom30.py | |||
| @@ -12,6 +12,7 @@ import re | |||
| 12 | import hashlib | 12 | import hashlib |
| 13 | import uuid | 13 | import uuid |
| 14 | import os | 14 | import os |
| 15 | import oe.spdx_common | ||
| 15 | from datetime import datetime, timezone | 16 | from datetime import datetime, timezone |
| 16 | 17 | ||
| 17 | OE_SPDX_BASE = "https://rdf.openembedded.org/spdx/3.0/" | 18 | OE_SPDX_BASE = "https://rdf.openembedded.org/spdx/3.0/" |
| @@ -205,24 +206,6 @@ def get_alias(obj): | |||
| 205 | return None | 206 | return None |
| 206 | 207 | ||
| 207 | 208 | ||
| 208 | def extract_licenses(filename): | ||
| 209 | lic_regex = re.compile( | ||
| 210 | rb"^\W*SPDX-License-Identifier:\s*([ \w\d.()+-]+?)(?:\s+\W*)?$", re.MULTILINE | ||
| 211 | ) | ||
| 212 | |||
| 213 | try: | ||
| 214 | with open(filename, "rb") as f: | ||
| 215 | size = min(15000, os.stat(filename).st_size) | ||
| 216 | txt = f.read(size) | ||
| 217 | licenses = re.findall(lic_regex, txt) | ||
| 218 | if licenses: | ||
| 219 | ascii_licenses = [lic.decode("ascii") for lic in licenses] | ||
| 220 | return ascii_licenses | ||
| 221 | except Exception as e: | ||
| 222 | bb.warn(f"Exception reading {filename}: {e}") | ||
| 223 | return [] | ||
| 224 | |||
| 225 | |||
| 226 | def to_list(l): | 209 | def to_list(l): |
| 227 | if isinstance(l, set): | 210 | if isinstance(l, set): |
| 228 | l = sorted(list(l)) | 211 | l = sorted(list(l)) |
| @@ -630,7 +613,7 @@ class ObjectSet(oe.spdx30.SHACLObjectSet): | |||
| 630 | return | 613 | return |
| 631 | 614 | ||
| 632 | file_licenses = set() | 615 | file_licenses = set() |
| 633 | for extracted_lic in extract_licenses(filepath): | 616 | for extracted_lic in oe.spdx_common.extract_licenses(filepath): |
| 634 | file_licenses.add(self.new_license_expression(extracted_lic)) | 617 | file_licenses.add(self.new_license_expression(extracted_lic)) |
| 635 | 618 | ||
| 636 | self.new_relationship( | 619 | self.new_relationship( |
diff --git a/meta/lib/oe/spdx_common.py b/meta/lib/oe/spdx_common.py new file mode 100644 index 0000000000..f23100fe03 --- /dev/null +++ b/meta/lib/oe/spdx_common.py | |||
| @@ -0,0 +1,228 @@ | |||
| 1 | # | ||
| 2 | # Copyright OpenEmbedded Contributors | ||
| 3 | # | ||
| 4 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 5 | # | ||
| 6 | |||
| 7 | import bb | ||
| 8 | import collections | ||
| 9 | import json | ||
| 10 | import oe.packagedata | ||
| 11 | import re | ||
| 12 | import shutil | ||
| 13 | |||
| 14 | from pathlib import Path | ||
| 15 | |||
| 16 | |||
| 17 | LIC_REGEX = re.compile( | ||
| 18 | rb"^\W*SPDX-License-Identifier:\s*([ \w\d.()+-]+?)(?:\s+\W*)?$", | ||
| 19 | re.MULTILINE, | ||
| 20 | ) | ||
| 21 | |||
| 22 | |||
| 23 | def extract_licenses(filename): | ||
| 24 | """ | ||
| 25 | Extract SPDX License identifiers from a file | ||
| 26 | """ | ||
| 27 | try: | ||
| 28 | with open(filename, "rb") as f: | ||
| 29 | size = min(15000, os.stat(filename).st_size) | ||
| 30 | txt = f.read(size) | ||
| 31 | licenses = re.findall(LIC_REGEX, txt) | ||
| 32 | if licenses: | ||
| 33 | ascii_licenses = [lic.decode("ascii") for lic in licenses] | ||
| 34 | return ascii_licenses | ||
| 35 | except Exception as e: | ||
| 36 | bb.warn(f"Exception reading {filename}: {e}") | ||
| 37 | return [] | ||
| 38 | |||
| 39 | |||
| 40 | def is_work_shared_spdx(d): | ||
| 41 | return bb.data.inherits_class("kernel", d) or ("work-shared" in d.getVar("WORKDIR")) | ||
| 42 | |||
| 43 | |||
| 44 | def load_spdx_license_data(d): | ||
| 45 | if d.getVar("SPDX_LICENSE_DATA"): | ||
| 46 | return | ||
| 47 | |||
| 48 | with open(d.getVar("SPDX_LICENSES"), "r") as f: | ||
| 49 | data = json.load(f) | ||
| 50 | # Transform the license array to a dictionary | ||
| 51 | data["licenses"] = {l["licenseId"]: l for l in data["licenses"]} | ||
| 52 | d.setVar("SPDX_LICENSE_DATA", data) | ||
| 53 | |||
| 54 | |||
| 55 | def process_sources(d): | ||
| 56 | """ | ||
| 57 | Returns True if the sources for this recipe should be included in the SPDX | ||
| 58 | or False if not | ||
| 59 | """ | ||
| 60 | pn = d.getVar("PN") | ||
| 61 | assume_provided = (d.getVar("ASSUME_PROVIDED") or "").split() | ||
| 62 | if pn in assume_provided: | ||
| 63 | for p in d.getVar("PROVIDES").split(): | ||
| 64 | if p != pn: | ||
| 65 | pn = p | ||
| 66 | break | ||
| 67 | |||
| 68 | # glibc-locale: do_fetch, do_unpack and do_patch tasks have been deleted, | ||
| 69 | # so avoid archiving source here. | ||
| 70 | if pn.startswith("glibc-locale"): | ||
| 71 | return False | ||
| 72 | if d.getVar("PN") == "libtool-cross": | ||
| 73 | return False | ||
| 74 | if d.getVar("PN") == "libgcc-initial": | ||
| 75 | return False | ||
| 76 | if d.getVar("PN") == "shadow-sysroot": | ||
| 77 | return False | ||
| 78 | |||
| 79 | # We just archive gcc-source for all the gcc related recipes | ||
| 80 | if d.getVar("BPN") in ["gcc", "libgcc"]: | ||
| 81 | bb.debug(1, "spdx: There is bug in scan of %s is, do nothing" % pn) | ||
| 82 | return False | ||
| 83 | |||
| 84 | return True | ||
| 85 | |||
| 86 | |||
| 87 | Dep = collections.namedtuple("Dep", ["pn", "hashfn", "in_taskhash"]) | ||
| 88 | |||
| 89 | |||
| 90 | def collect_direct_deps(d, dep_task): | ||
| 91 | """ | ||
| 92 | Find direct dependencies of current task | ||
| 93 | |||
| 94 | Returns the list of recipes that have a dep_task that the current task | ||
| 95 | depends on | ||
| 96 | """ | ||
| 97 | current_task = "do_" + d.getVar("BB_CURRENTTASK") | ||
| 98 | pn = d.getVar("PN") | ||
| 99 | |||
| 100 | taskdepdata = d.getVar("BB_TASKDEPDATA", False) | ||
| 101 | |||
| 102 | for this_dep in taskdepdata.values(): | ||
| 103 | if this_dep[0] == pn and this_dep[1] == current_task: | ||
| 104 | break | ||
| 105 | else: | ||
| 106 | bb.fatal(f"Unable to find this {pn}:{current_task} in taskdepdata") | ||
| 107 | |||
| 108 | deps = set() | ||
| 109 | |||
| 110 | for dep_name in this_dep.deps: | ||
| 111 | dep_data = taskdepdata[dep_name] | ||
| 112 | if dep_data.taskname == dep_task and dep_data.pn != pn: | ||
| 113 | deps.add((dep_data.pn, dep_data.hashfn, dep_name in this_dep.taskhash_deps)) | ||
| 114 | |||
| 115 | return sorted(deps) | ||
| 116 | |||
| 117 | |||
| 118 | def get_spdx_deps(d): | ||
| 119 | """ | ||
| 120 | Reads the SPDX dependencies JSON file and returns the data | ||
| 121 | """ | ||
| 122 | spdx_deps_file = Path(d.getVar("SPDXDEPS")) | ||
| 123 | |||
| 124 | deps = [] | ||
| 125 | with spdx_deps_file.open("r") as f: | ||
| 126 | for d in json.load(f): | ||
| 127 | deps.append(Dep(*d)) | ||
| 128 | return deps | ||
| 129 | |||
| 130 | |||
| 131 | def collect_package_providers(d): | ||
| 132 | """ | ||
| 133 | Returns a dictionary where each RPROVIDES is mapped to the package that | ||
| 134 | provides it | ||
| 135 | """ | ||
| 136 | deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX")) | ||
| 137 | |||
| 138 | providers = {} | ||
| 139 | |||
| 140 | deps = collect_direct_deps(d, "do_create_spdx") | ||
| 141 | deps.append((d.getVar("PN"), d.getVar("BB_HASHFILENAME"), True)) | ||
| 142 | |||
| 143 | for dep_pn, dep_hashfn, _ in deps: | ||
| 144 | localdata = d | ||
| 145 | recipe_data = oe.packagedata.read_pkgdata(dep_pn, localdata) | ||
| 146 | if not recipe_data: | ||
| 147 | localdata = bb.data.createCopy(d) | ||
| 148 | localdata.setVar("PKGDATA_DIR", "${PKGDATA_DIR_SDK}") | ||
| 149 | recipe_data = oe.packagedata.read_pkgdata(dep_pn, localdata) | ||
| 150 | |||
| 151 | for pkg in recipe_data.get("PACKAGES", "").split(): | ||
| 152 | pkg_data = oe.packagedata.read_subpkgdata_dict(pkg, localdata) | ||
| 153 | rprovides = set( | ||
| 154 | n | ||
| 155 | for n, _ in bb.utils.explode_dep_versions2( | ||
| 156 | pkg_data.get("RPROVIDES", "") | ||
| 157 | ).items() | ||
| 158 | ) | ||
| 159 | rprovides.add(pkg) | ||
| 160 | |||
| 161 | if "PKG" in pkg_data: | ||
| 162 | pkg = pkg_data["PKG"] | ||
| 163 | rprovides.add(pkg) | ||
| 164 | |||
| 165 | for r in rprovides: | ||
| 166 | providers[r] = (pkg, dep_hashfn) | ||
| 167 | |||
| 168 | return providers | ||
| 169 | |||
| 170 | |||
| 171 | def get_patched_src(d): | ||
| 172 | """ | ||
| 173 | Save patched source of the recipe in SPDX_WORKDIR. | ||
| 174 | """ | ||
| 175 | spdx_workdir = d.getVar("SPDXWORK") | ||
| 176 | spdx_sysroot_native = d.getVar("STAGING_DIR_NATIVE") | ||
| 177 | pn = d.getVar("PN") | ||
| 178 | |||
| 179 | workdir = d.getVar("WORKDIR") | ||
| 180 | |||
| 181 | try: | ||
| 182 | # The kernel class functions require it to be on work-shared, so we dont change WORKDIR | ||
| 183 | if not is_work_shared_spdx(d): | ||
| 184 | # Change the WORKDIR to make do_unpack do_patch run in another dir. | ||
| 185 | d.setVar("WORKDIR", spdx_workdir) | ||
| 186 | # Restore the original path to recipe's native sysroot (it's relative to WORKDIR). | ||
| 187 | d.setVar("STAGING_DIR_NATIVE", spdx_sysroot_native) | ||
| 188 | |||
| 189 | # The changed 'WORKDIR' also caused 'B' changed, create dir 'B' for the | ||
| 190 | # possibly requiring of the following tasks (such as some recipes's | ||
| 191 | # do_patch required 'B' existed). | ||
| 192 | bb.utils.mkdirhier(d.getVar("B")) | ||
| 193 | |||
| 194 | bb.build.exec_func("do_unpack", d) | ||
| 195 | # Copy source of kernel to spdx_workdir | ||
| 196 | if is_work_shared_spdx(d): | ||
| 197 | share_src = d.getVar("WORKDIR") | ||
| 198 | d.setVar("WORKDIR", spdx_workdir) | ||
| 199 | d.setVar("STAGING_DIR_NATIVE", spdx_sysroot_native) | ||
| 200 | src_dir = ( | ||
| 201 | spdx_workdir | ||
| 202 | + "/" | ||
| 203 | + d.getVar("PN") | ||
| 204 | + "-" | ||
| 205 | + d.getVar("PV") | ||
| 206 | + "-" | ||
| 207 | + d.getVar("PR") | ||
| 208 | ) | ||
| 209 | bb.utils.mkdirhier(src_dir) | ||
| 210 | if bb.data.inherits_class("kernel", d): | ||
| 211 | share_src = d.getVar("STAGING_KERNEL_DIR") | ||
| 212 | cmd_copy_share = "cp -rf " + share_src + "/* " + src_dir + "/" | ||
| 213 | cmd_copy_shared_res = os.popen(cmd_copy_share).read() | ||
| 214 | bb.note("cmd_copy_shared_result = " + cmd_copy_shared_res) | ||
| 215 | |||
| 216 | git_path = src_dir + "/.git" | ||
| 217 | if os.path.exists(git_path): | ||
| 218 | shutils.rmtree(git_path) | ||
| 219 | |||
| 220 | # Make sure gcc and kernel sources are patched only once | ||
| 221 | if not (d.getVar("SRC_URI") == "" or is_work_shared_spdx(d)): | ||
| 222 | bb.build.exec_func("do_patch", d) | ||
| 223 | |||
| 224 | # Some userland has no source. | ||
| 225 | if not os.path.exists(spdx_workdir): | ||
| 226 | bb.utils.mkdirhier(spdx_workdir) | ||
| 227 | finally: | ||
| 228 | d.setVar("WORKDIR", workdir) | ||
