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/oe | |
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/oe')
-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) | ||