[PATCH v3 6/7] scripts/sbom: use make_prereqs to determine build inputs

From: Luis Augenstein

Date: Mon Oct 12 2026 - 17:37:51 EST


Build the list of file dependencies of a target from the
make_prereqs_<target> field and the deps_<target> entries of the .cmd
file instead of parsing the shell command stored in savedcmd_.

The prerequisites include more than KernelSbom wants to track: files
should be tracked if they end up in the kernel image or loadable
modules, but not each and every host tool or helper script used to
build them. Add a manually maintained exclude list for such
dependencies. It is still manual, but much easier to maintain than
the command-specific parsers that were needed to extract the relevant
inputs from each individual build command.

Assisted-by: LLM
Co-developed-by: Maximilian Huber <maximilian.huber@xxxxxxxxxxx>
Signed-off-by: Maximilian Huber <maximilian.huber@xxxxxxxxxxx>
Signed-off-by: Luis Augenstein <luis.augenstein@xxxxxxxxxxx>
---
scripts/sbom/sbom/cmd_graph/cmd_file.py | 46 ++-----------
.../sbom/sbom/cmd_graph/dependency_filter.py | 59 +++++++++++++++++
scripts/sbom/sbom/spdx_graph/kernel_file.py | 64 ++++++++++++-------
3 files changed, 105 insertions(+), 64 deletions(-)
create mode 100644 scripts/sbom/sbom/cmd_graph/dependency_filter.py

diff --git a/scripts/sbom/sbom/cmd_graph/cmd_file.py b/scripts/sbom/sbom/cmd_graph/cmd_file.py
index cc576c7c6be..ab4b00b252b 100644
--- a/scripts/sbom/sbom/cmd_graph/cmd_file.py
+++ b/scripts/sbom/sbom/cmd_graph/cmd_file.py
@@ -5,7 +5,7 @@ import os
import re
from dataclasses import dataclass, field
from sbom.cmd_graph.deps_parser import parse_cmd_file_deps
-from sbom.cmd_graph.savedcmd_parser import parse_inputs_from_commands
+from sbom.cmd_graph.dependency_filter import should_include_dependency
import sbom.sbom_logging as sbom_logging
from sbom.path_utils import PathStr

@@ -85,51 +85,17 @@ class CmdFile:
Returns:
list[PathStr]: dependency file paths relative to `obj_tree`.
"""
- input_files: list[PathStr] = [
- str(p) for p in parse_inputs_from_commands(self.savedcmd, fail_on_unknown_build_command)
- ]
+ input_files: list[PathStr] = list(self.make_prereqs)
+
if self.deps:
input_files += [str(p) for p in parse_cmd_file_deps(self.deps)]
- input_files = _expand_resolve_files(input_files, obj_tree)

cmd_file_dependencies: list[PathStr] = []
for input_file in input_files:
- # input files are either absolute or relative to the object tree
+ # input files are either absolute or already relative to the object tree
if os.path.isabs(input_file):
input_file = os.path.relpath(input_file, obj_tree)
- if input_file == target_path:
- # Skip target file to prevent cycles. This is necessary because some multi stage commands first create an output and then pass it as input to the next command, e.g., objcopy.
- continue
- cmd_file_dependencies.append(input_file)
+ if should_include_dependency(input_file):
+ cmd_file_dependencies.append(input_file)
unique_cmd_file_dependencies = list(dict.fromkeys(cmd_file_dependencies))
return unique_cmd_file_dependencies
-
-
-def _expand_resolve_files(input_files: list[PathStr], obj_tree: PathStr) -> list[PathStr]:
- """
- Expands resolve files which may reference additional files via '@' notation.
-
- Args:
- input_files (list[PathStr]): List of file paths relative to the object tree, where paths starting with '@' refer to files
- containing further file paths, each on a separate line.
- obj_tree: Absolute path to the root of the object tree.
-
- Returns:
- list[PathStr]: Flattened list of all input file paths, with any nested '@' file references resolved recursively.
- """
- expanded_input_files: list[PathStr] = []
- for input_file in input_files:
- if not input_file.startswith("@"):
- expanded_input_files.append(input_file)
- continue
- resolve_file_path = os.path.join(obj_tree, input_file.removeprefix("@"))
- if not os.path.exists(resolve_file_path):
- sbom_logging.error(
- "Skip resolving '{resolve_file_path}' because the response file does not exist.",
- resolve_file_path=resolve_file_path,
- )
- continue
- with open(resolve_file_path, "rt", encoding="utf-8") as f:
- resolve_file_content = [line_stripped for line in f.readlines() if (line_stripped := line.strip())]
- expanded_input_files += _expand_resolve_files(resolve_file_content, obj_tree)
- return expanded_input_files
diff --git a/scripts/sbom/sbom/cmd_graph/dependency_filter.py b/scripts/sbom/sbom/cmd_graph/dependency_filter.py
new file mode 100644
index 00000000000..f1fdc2cb965
--- /dev/null
+++ b/scripts/sbom/sbom/cmd_graph/dependency_filter.py
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+# Copyright (C) 2025 TNG Technology Consulting GmbH
+
+import re
+
+from sbom.path_utils import PathStr
+
+# Regex patterns for dependencies that should not be tracked in the dependency graph.
+_COMPILED_EXCLUDE_PATTERNS: tuple[re.Pattern[str], ...] = (
+ # scripts in source tree (except module-common.c, which is compiled into every module)
+ re.compile(r"^\.\./scripts/(?!module-common\.c$)"),
+ # scripts and tools in obj tree
+ re.compile(r"^scripts/"),
+ re.compile(r"^tools/"),
+ re.compile(r"^arch/[^/]+/tools/"),
+ # Host helper scripts
+ re.compile(r".*\.sh$"),
+ re.compile(r".*\.py$"),
+ re.compile(r".*\.pl$"),
+ # Host-tool executables
+ re.compile(r"^certs/extract-cert$"),
+ re.compile(r"^security/selinux/genheaders$"),
+ re.compile(r"^usr/gen_init_cpio$"),
+ re.compile(r"^drivers/gpu/drm/radeon/mkregtable$"),
+ re.compile(r"^drivers/video/logo/pnmtologo$"),
+ re.compile(r"^lib/raid/raid6/mktables$"),
+ re.compile(r"^\.\./fs/smb/client/gen_smb1_mapping$"),
+ re.compile(r"^\.\./fs/smb/client/gen_smb2_mapping$"),
+ # Other
+ re.compile(r".*\.xsd$"),
+ re.compile(r".*purgatory\.chk$"),
+ re.compile(r".*\.genkey$"),
+ re.compile(r".*\.md$"),
+ re.compile(r"^include/config/"),
+ # x86
+ re.compile(r"^arch/x86/boot/compressed/mkpiggy$"),
+ re.compile(r"^arch/x86/boot/mkcpustr$"),
+ # arm
+ re.compile(r"^arch/arm/vdso/vdsomunge$"),
+ # arm64
+ re.compile(r"^arch/arm64/kvm/hyp/nvhe/gen-hyprel$"),
+ re.compile(r"^arch/arm64/kernel/pi/relacheck$"),
+)
+
+def should_include_dependency(path: PathStr) -> bool:
+ """
+ Return whether a `.cmd` dependency should appear in the tracked dependency graph.
+
+ Track files that are inputs to compiling or linking artifacts that end up in the
+ kernel image or loadable modules (e.g. source → object → archive → image/module).
+ Exclude host tools and helper scripts whose code is not linked into the kernel.
+
+ Args:
+ path: Dependency path relative to the object tree.
+
+ Returns:
+ True if the dependency should be included in the dependency graph.
+ """
+ return not any(pattern.match(path) for pattern in _COMPILED_EXCLUDE_PATTERNS)
diff --git a/scripts/sbom/sbom/spdx_graph/kernel_file.py b/scripts/sbom/sbom/spdx_graph/kernel_file.py
index 505f25f66eb..cea14cb3538 100644
--- a/scripts/sbom/sbom/spdx_graph/kernel_file.py
+++ b/scripts/sbom/sbom/spdx_graph/kernel_file.py
@@ -245,7 +245,7 @@ def _get_primary_purpose(absolute_path: PathStr) -> SoftwarePurpose | None:
return any(segment in absolute_path for segment in path_segments)

# Source code
- if ends_with([".c", ".h", ".S", ".s", ".rs", ".pl", "gen_smb1_mapping", "gen_smb2_mapping"]):
+ if ends_with([".c", ".h", ".S", ".s", ".rs"]):
return "source"

# Libraries
@@ -271,45 +271,61 @@ def _get_primary_purpose(absolute_path: PathStr) -> SoftwarePurpose | None:
# Data files
if ends_with(
[
- ".tbl",
- ".relocs",
- ".rmeta",
- ".in",
+ ".awk",
".dbg",
- ".x509",
- ".pbm",
- ".ppm",
".dtb",
- ".uc",
- ".inc",
+ ".dtbo",
".dts",
".dtsi",
- ".dtbo",
- ".xml",
+ ".dtso",
+ ".in",
+ ".inc",
+ ".json",
+ ".ld",
+ ".lds",
+ ".mod",
+ ".order",
+ ".pbm",
+ ".ppm",
+ ".relocs",
+ ".rmeta",
".ro",
- "initramfs_inc_data",
- "default_cpio_list",
- "x509_certificate_list",
- "utf8data.c_shipped",
+ ".tbl",
+ ".uc",
+ ".x509",
+ ".xml",
"blacklist_hash_list",
- "x509_revocation_list",
"cpucaps",
- "sysreg",
+ "default_cpio_list",
+ "initramfs_inc_data",
+ "kheaders.md5",
+ "kheaders-objlist",
+ "kheaders-srclist",
"mach-types",
+ "sysreg",
+ "utf8data.c_shipped",
+ "x509_certificate_list",
+ "x509_revocation_list",
]
) or includes_path_segments(["drivers/gpu/drm/radeon/reg_srcs/"]):
return "data"

# Configuration files
- if ends_with([".pem", ".key", ".conf", ".config", ".cfg", ".bconf"]):
+ if ends_with([
+ ".pem",
+ ".key",
+ ".conf",
+ ".config",
+ ".cfg",
+ ".bconf",
+ "config_data",
+ ".genkey",
+ "bindgen_parameters",
+ ]):
return "configuration"

- # Documentation
- if ends_with([".md"]):
- return "documentation"
-
# Other / miscellaneous
- if ends_with([".o", ".tmp"]):
+ if ends_with([".o"]):
return "other"

sbom_logging.warning("Could not infer primary purpose for {absolute_path}", absolute_path=absolute_path)
--
2.43.0