mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
scripts/sbom: add additional dependency sources for cmd graph
Add hardcoded dependencies and .incbin directive parsing to discover dependencies not tracked by .cmd files. Assisted-by: Cursor:claude-sonnet-4-5 Assisted-by: OpenCode:GLM-4-7 Co-developed-by: Maximilian Huber <maximilian.huber@tngtech.com> Signed-off-by: Maximilian Huber <maximilian.huber@tngtech.com> Signed-off-by: Luis Augenstein <luis.augenstein@tngtech.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
9c16c1ea46
commit
d764b54e28
|
|
@ -2,15 +2,24 @@
|
|||
# Copyright (C) 2025 TNG Technology Consulting GmbH
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from itertools import chain
|
||||
import logging
|
||||
import os
|
||||
from typing import Iterator, Protocol
|
||||
|
||||
from sbom import sbom_logging
|
||||
from sbom.cmd_graph.cmd_file import CmdFile
|
||||
from sbom.cmd_graph.hardcoded_dependencies import get_hardcoded_dependencies
|
||||
from sbom.cmd_graph.incbin_parser import parse_incbin_statements
|
||||
from sbom.path_utils import PathStr, has_link, is_relative_to
|
||||
|
||||
|
||||
@dataclass
|
||||
class IncbinDependency:
|
||||
node: "CmdGraphNode"
|
||||
full_statement: str
|
||||
|
||||
|
||||
class CmdGraphNodeConfig(Protocol):
|
||||
obj_tree: PathStr
|
||||
src_tree: PathStr
|
||||
|
|
@ -28,11 +37,17 @@ class CmdGraphNode:
|
|||
"""Parsed .cmd file describing how the file at absolute_path was built, or None if not available."""
|
||||
|
||||
cmd_file_dependencies: list["CmdGraphNode"] = field(default_factory=list)
|
||||
incbin_dependencies: list[IncbinDependency] = field(default_factory=list)
|
||||
hardcoded_dependencies: list["CmdGraphNode"] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def children(self) -> Iterator["CmdGraphNode"]:
|
||||
seen: set[PathStr] = set()
|
||||
for node in self.cmd_file_dependencies:
|
||||
for node in chain(
|
||||
self.cmd_file_dependencies,
|
||||
(dep.node for dep in self.incbin_dependencies),
|
||||
self.hardcoded_dependencies,
|
||||
):
|
||||
if node.absolute_path not in seen:
|
||||
seen.add(node.absolute_path)
|
||||
yield node
|
||||
|
|
@ -95,6 +110,13 @@ class CmdGraphNode:
|
|||
def _build_child_node(child_path: PathStr) -> "CmdGraphNode":
|
||||
return CmdGraphNode.create(child_path, config, cache, depth + 1)
|
||||
|
||||
node.hardcoded_dependencies = [
|
||||
_build_child_node(hardcoded_dependency_path)
|
||||
for hardcoded_dependency_path in get_hardcoded_dependencies(
|
||||
target_path_absolute, config.obj_tree, config.src_tree
|
||||
)
|
||||
]
|
||||
|
||||
if cmd_file is not None:
|
||||
node.cmd_file_dependencies = [
|
||||
_build_child_node(cmd_file_dependency_path)
|
||||
|
|
@ -103,6 +125,15 @@ class CmdGraphNode:
|
|||
)
|
||||
]
|
||||
|
||||
if node.absolute_path.endswith(".S"):
|
||||
node.incbin_dependencies = [
|
||||
IncbinDependency(
|
||||
node=_build_child_node(incbin_statement.path),
|
||||
full_statement=incbin_statement.full_statement,
|
||||
)
|
||||
for incbin_statement in parse_incbin_statements(node.absolute_path)
|
||||
]
|
||||
|
||||
return node
|
||||
|
||||
|
||||
|
|
|
|||
87
scripts/sbom/sbom/cmd_graph/hardcoded_dependencies.py
Normal file
87
scripts/sbom/sbom/cmd_graph/hardcoded_dependencies.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR MIT
|
||||
# Copyright (C) 2025 TNG Technology Consulting GmbH
|
||||
|
||||
import os
|
||||
from typing import Callable
|
||||
import sbom.sbom_logging as sbom_logging
|
||||
from sbom.path_utils import PathStr, is_relative_to
|
||||
from sbom.environment import Environment
|
||||
|
||||
HARDCODED_DEPENDENCIES: dict[str, list[str]] = {
|
||||
# defined in linux/Kbuild
|
||||
"include/generated/rq-offsets.h": ["kernel/sched/rq-offsets.s"],
|
||||
"kernel/sched/rq-offsets.s": ["include/generated/asm-offsets.h"],
|
||||
"include/generated/bounds.h": ["kernel/bounds.s"],
|
||||
"include/generated/asm-offsets.h": ["arch/{arch}/kernel/asm-offsets.s"],
|
||||
}
|
||||
"""
|
||||
Maps file paths to the list of dependencies required to build them
|
||||
which are not tracked by the .cmd dependency mechanism.
|
||||
Paths are relative to either the source tree or the object tree.
|
||||
"""
|
||||
|
||||
def get_hardcoded_dependencies(path: PathStr, obj_tree: PathStr, src_tree: PathStr) -> list[PathStr]:
|
||||
"""
|
||||
Some files in the kernel build process are not tracked by the .cmd dependency mechanism.
|
||||
Parsing these dependencies programmatically is too complex for the scope of this project.
|
||||
Therefore, this function provides manually defined dependencies to be added to the build graph.
|
||||
|
||||
Args:
|
||||
path: absolute path to a file within the src tree or object tree.
|
||||
obj_tree: absolute Path to the base directory of the object tree.
|
||||
src_tree: absolute Path to the `linux` source directory.
|
||||
|
||||
Returns:
|
||||
list[PathStr]: A list of dependency file paths (relative to the object tree) required to build the file at the given path.
|
||||
"""
|
||||
if is_relative_to(path, obj_tree):
|
||||
path = os.path.relpath(path, obj_tree)
|
||||
elif is_relative_to(path, src_tree):
|
||||
path = os.path.relpath(path, src_tree)
|
||||
|
||||
if path not in HARDCODED_DEPENDENCIES:
|
||||
return []
|
||||
|
||||
template_variables: dict[str, Callable[[], str | None]] = {
|
||||
"arch": lambda: _get_arch(path),
|
||||
}
|
||||
|
||||
dependencies: list[PathStr] = []
|
||||
for dependency_template in HARDCODED_DEPENDENCIES[path]:
|
||||
dependency = _evaluate_template(dependency_template, template_variables)
|
||||
if dependency is None:
|
||||
continue
|
||||
if os.path.exists(os.path.join(obj_tree, dependency)):
|
||||
dependencies.append(dependency)
|
||||
elif os.path.exists(dependency_absolute := os.path.join(src_tree, dependency)):
|
||||
dependencies.append(os.path.relpath(dependency_absolute, obj_tree))
|
||||
else:
|
||||
sbom_logging.error(
|
||||
"Skip hardcoded dependency '{dependency}' for '{path}' because the dependency lies neither in the src tree nor the object tree.",
|
||||
dependency=dependency,
|
||||
path=path,
|
||||
)
|
||||
|
||||
return dependencies
|
||||
|
||||
|
||||
def _evaluate_template(template: str, variables: dict[str, Callable[[], str | None]]) -> str | None:
|
||||
for key, value_function in variables.items():
|
||||
template_key = "{" + key + "}"
|
||||
if template_key in template:
|
||||
value = value_function()
|
||||
if value is None:
|
||||
return None
|
||||
template = template.replace(template_key, value)
|
||||
return template
|
||||
|
||||
|
||||
def _get_arch(path: PathStr):
|
||||
srcarch = Environment.SRCARCH()
|
||||
if srcarch is None:
|
||||
sbom_logging.error(
|
||||
"Skipped architecture specific hardcoded dependency for '{path}' because the SRCARCH environment variable was not set.",
|
||||
path=path,
|
||||
)
|
||||
return None
|
||||
return srcarch
|
||||
42
scripts/sbom/sbom/cmd_graph/incbin_parser.py
Normal file
42
scripts/sbom/sbom/cmd_graph/incbin_parser.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR MIT
|
||||
# Copyright (C) 2025 TNG Technology Consulting GmbH
|
||||
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
|
||||
from sbom.path_utils import PathStr
|
||||
|
||||
INCBIN_PATTERN = re.compile(r'\s*\.incbin\s+"(?P<path>[^"]+)"')
|
||||
"""Regex pattern for matching `.incbin "<path>"` statements."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class IncbinStatement:
|
||||
"""A parsed `.incbin "<path>"` directive."""
|
||||
|
||||
path: PathStr
|
||||
"""path to the file referenced by the `.incbin` directive."""
|
||||
|
||||
full_statement: str
|
||||
"""Full `.incbin "<path>"` statement as it originally appeared in the file."""
|
||||
|
||||
|
||||
def parse_incbin_statements(absolute_path: PathStr) -> list[IncbinStatement]:
|
||||
"""
|
||||
Parses `.incbin` directives from an `.S` assembly file.
|
||||
|
||||
Args:
|
||||
absolute_path: Absolute path to the `.S` assembly file.
|
||||
|
||||
Returns:
|
||||
list[IncbinStatement]: Parsed `.incbin` statements.
|
||||
"""
|
||||
with open(absolute_path, "rt", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
return [
|
||||
IncbinStatement(
|
||||
path=match.group("path"),
|
||||
full_statement=match.group(0).strip(),
|
||||
)
|
||||
for match in INCBIN_PATTERN.finditer(content)
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user