summaryrefslogtreecommitdiffstats
path: root/meta/lib/oe
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2024-07-12 09:58:19 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2024-07-16 14:55:53 +0100
commit9850df1b6051cefdef4f6f9acd93cc93ab2b8b75 (patch)
treecd3153d83952cad38f351d602e84641cd8425e08 /meta/lib/oe
parent454008311b958a080cffb2cf535958fa0be38c05 (diff)
downloadpoky-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.py21
-rw-r--r--meta/lib/oe/spdx_common.py228
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
12import hashlib 12import hashlib
13import uuid 13import uuid
14import os 14import os
15import oe.spdx_common
15from datetime import datetime, timezone 16from datetime import datetime, timezone
16 17
17OE_SPDX_BASE = "https://rdf.openembedded.org/spdx/3.0/" 18OE_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
208def 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
226def to_list(l): 209def 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
7import bb
8import collections
9import json
10import oe.packagedata
11import re
12import shutil
13
14from pathlib import Path
15
16
17LIC_REGEX = re.compile(
18 rb"^\W*SPDX-License-Identifier:\s*([ \w\d.()+-]+?)(?:\s+\W*)?$",
19 re.MULTILINE,
20)
21
22
23def 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
40def is_work_shared_spdx(d):
41 return bb.data.inherits_class("kernel", d) or ("work-shared" in d.getVar("WORKDIR"))
42
43
44def 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
55def 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
87Dep = collections.namedtuple("Dep", ["pn", "hashfn", "in_taskhash"])
88
89
90def 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
118def 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
131def 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
171def 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)