xref: /dpdk/dts/framework/utils.py (revision a23f22457dbd51636fef2a8bfa41872e669267a0)
1812c4071SJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause
2812c4071SJuraj Linkeš# Copyright(c) 2010-2014 Intel Corporation
3680d8a24SJuraj Linkeš# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
4680d8a24SJuraj Linkeš# Copyright(c) 2022-2023 University of New Hampshire
5*a23f2245SLuca Vizzarro# Copyright(c) 2024 Arm Limited
6812c4071SJuraj Linkeš
76ef07151SJuraj Linkeš"""Various utility classes and functions.
86ef07151SJuraj Linkeš
96ef07151SJuraj LinkešThese are used in multiple modules across the framework. They're here because
106ef07151SJuraj Linkešthey provide some non-specific functionality, greatly simplify imports or just don't
116ef07151SJuraj Linkešfit elsewhere.
126ef07151SJuraj Linkeš
136ef07151SJuraj LinkešAttributes:
146ef07151SJuraj Linkeš    REGEX_FOR_PCI_ADDRESS: The regex representing a PCI address, e.g. ``0000:00:08.0``.
156ef07151SJuraj Linkeš"""
166ef07151SJuraj Linkeš
17c9d31a7eSJuraj Linkešimport atexit
18cecfe0aaSJuraj Linkešimport json
19c9d31a7eSJuraj Linkešimport os
20c9d31a7eSJuraj Linkešimport subprocess
21c9d31a7eSJuraj Linkešfrom enum import Enum
22c9d31a7eSJuraj Linkešfrom pathlib import Path
23c9d31a7eSJuraj Linkešfrom subprocess import SubprocessError
24c9d31a7eSJuraj Linkeš
25282688eaSLuca Vizzarrofrom scapy.packet import Packet  # type: ignore[import-untyped]
26cecfe0aaSJuraj Linkeš
27c9d31a7eSJuraj Linkešfrom .exception import ConfigurationError
28c9d31a7eSJuraj Linkeš
29840b1e01SJuraj LinkešREGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
3057c58bf8SJuraj Linkeš
31812c4071SJuraj Linkeš
32c020b7ceSJuraj Linkešdef expand_range(range_str: str) -> list[int]:
336ef07151SJuraj Linkeš    """Process `range_str` into a list of integers.
34c020b7ceSJuraj Linkeš
356ef07151SJuraj Linkeš    There are two possible formats of `range_str`:
366ef07151SJuraj Linkeš
376ef07151SJuraj Linkeš        * ``n`` - a single integer,
386ef07151SJuraj Linkeš        * ``n-m`` - a range of integers.
396ef07151SJuraj Linkeš
406ef07151SJuraj Linkeš    The returned range includes both ``n`` and ``m``. Empty string returns an empty list.
416ef07151SJuraj Linkeš
426ef07151SJuraj Linkeš    Args:
436ef07151SJuraj Linkeš        range_str: The range to expand.
446ef07151SJuraj Linkeš
456ef07151SJuraj Linkeš    Returns:
466ef07151SJuraj Linkeš        All the numbers from the range.
47c020b7ceSJuraj Linkeš    """
48c020b7ceSJuraj Linkeš    expanded_range: list[int] = []
49c020b7ceSJuraj Linkeš    if range_str:
50c020b7ceSJuraj Linkeš        range_boundaries = range_str.split("-")
51c020b7ceSJuraj Linkeš        # will throw an exception when items in range_boundaries can't be converted,
52c020b7ceSJuraj Linkeš        # serving as type check
53517b4b26SJuraj Linkeš        expanded_range.extend(range(int(range_boundaries[0]), int(range_boundaries[-1]) + 1))
54c020b7ceSJuraj Linkeš
55c020b7ceSJuraj Linkeš    return expanded_range
56c020b7ceSJuraj Linkeš
57c020b7ceSJuraj Linkeš
58840b1e01SJuraj Linkešdef get_packet_summaries(packets: list[Packet]) -> str:
596ef07151SJuraj Linkeš    """Format a string summary from `packets`.
606ef07151SJuraj Linkeš
616ef07151SJuraj Linkeš    Args:
626ef07151SJuraj Linkeš        packets: The packets to format.
636ef07151SJuraj Linkeš
646ef07151SJuraj Linkeš    Returns:
656ef07151SJuraj Linkeš        The summary of `packets`.
666ef07151SJuraj Linkeš    """
67cecfe0aaSJuraj Linkeš    if len(packets) == 1:
68cecfe0aaSJuraj Linkeš        packet_summaries = packets[0].summary()
69cecfe0aaSJuraj Linkeš    else:
70517b4b26SJuraj Linkeš        packet_summaries = json.dumps(list(map(lambda pkt: pkt.summary(), packets)), indent=4)
71cecfe0aaSJuraj Linkeš    return f"Packet contents: \n{packet_summaries}"
72cecfe0aaSJuraj Linkeš
73cecfe0aaSJuraj Linkeš
74*a23f2245SLuca Vizzarrodef get_commit_id(rev_id: str) -> str:
75*a23f2245SLuca Vizzarro    """Given a Git revision ID, return the corresponding commit ID.
76*a23f2245SLuca Vizzarro
77*a23f2245SLuca Vizzarro    Args:
78*a23f2245SLuca Vizzarro        rev_id: The Git revision ID.
79*a23f2245SLuca Vizzarro
80*a23f2245SLuca Vizzarro    Raises:
81*a23f2245SLuca Vizzarro        ConfigurationError: The ``git rev-parse`` command failed, suggesting
82*a23f2245SLuca Vizzarro            an invalid or ambiguous revision ID was supplied.
83*a23f2245SLuca Vizzarro    """
84*a23f2245SLuca Vizzarro    result = subprocess.run(
85*a23f2245SLuca Vizzarro        ["git", "rev-parse", "--verify", rev_id],
86*a23f2245SLuca Vizzarro        text=True,
87*a23f2245SLuca Vizzarro        capture_output=True,
88*a23f2245SLuca Vizzarro    )
89*a23f2245SLuca Vizzarro    if result.returncode != 0:
90*a23f2245SLuca Vizzarro        raise ConfigurationError(
91*a23f2245SLuca Vizzarro            f"{rev_id} is not a valid git reference.\n"
92*a23f2245SLuca Vizzarro            f"Command: {result.args}\n"
93*a23f2245SLuca Vizzarro            f"Stdout: {result.stdout}\n"
94*a23f2245SLuca Vizzarro            f"Stderr: {result.stderr}"
95*a23f2245SLuca Vizzarro        )
96*a23f2245SLuca Vizzarro    return result.stdout.strip()
97*a23f2245SLuca Vizzarro
98*a23f2245SLuca Vizzarro
99840b1e01SJuraj Linkešclass StrEnum(Enum):
1006ef07151SJuraj Linkeš    """Enum with members stored as strings."""
1016ef07151SJuraj Linkeš
102840b1e01SJuraj Linkeš    @staticmethod
103840b1e01SJuraj Linkeš    def _generate_next_value_(name: str, start: int, count: int, last_values: object) -> str:
104840b1e01SJuraj Linkeš        return name
105840b1e01SJuraj Linkeš
106840b1e01SJuraj Linkeš    def __str__(self) -> str:
1076ef07151SJuraj Linkeš        """The string representation is the name of the member."""
108840b1e01SJuraj Linkeš        return self.name
109680d8a24SJuraj Linkeš
110680d8a24SJuraj Linkeš
111680d8a24SJuraj Linkešclass MesonArgs(object):
1126ef07151SJuraj Linkeš    """Aggregate the arguments needed to build DPDK."""
113680d8a24SJuraj Linkeš
114680d8a24SJuraj Linkeš    _default_library: str
115680d8a24SJuraj Linkeš
116680d8a24SJuraj Linkeš    def __init__(self, default_library: str | None = None, **dpdk_args: str | bool):
1176ef07151SJuraj Linkeš        """Initialize the meson arguments.
1186ef07151SJuraj Linkeš
1196ef07151SJuraj Linkeš        Args:
1206ef07151SJuraj Linkeš            default_library: The default library type, Meson supports ``shared``, ``static`` and
1216ef07151SJuraj Linkeš                ``both``. Defaults to :data:`None`, in which case the argument won't be used.
1226ef07151SJuraj Linkeš            dpdk_args: The arguments found in ``meson_options.txt`` in root DPDK directory.
1236ef07151SJuraj Linkeš                Do not use ``-D`` with them.
1246ef07151SJuraj Linkeš
1256ef07151SJuraj Linkeš        Example:
1266ef07151SJuraj Linkeš            ::
1276ef07151SJuraj Linkeš
1286ef07151SJuraj Linkeš                meson_args = MesonArgs(enable_kmods=True).
1296ef07151SJuraj Linkeš        """
130517b4b26SJuraj Linkeš        self._default_library = f"--default-library={default_library}" if default_library else ""
131680d8a24SJuraj Linkeš        self._dpdk_args = " ".join(
132680d8a24SJuraj Linkeš            (
133680d8a24SJuraj Linkeš                f"-D{dpdk_arg_name}={dpdk_arg_value}"
134680d8a24SJuraj Linkeš                for dpdk_arg_name, dpdk_arg_value in dpdk_args.items()
135680d8a24SJuraj Linkeš            )
136680d8a24SJuraj Linkeš        )
137680d8a24SJuraj Linkeš
138680d8a24SJuraj Linkeš    def __str__(self) -> str:
1396ef07151SJuraj Linkeš        """The actual args."""
140680d8a24SJuraj Linkeš        return " ".join(f"{self._default_library} {self._dpdk_args}".split())
141c9d31a7eSJuraj Linkeš
142c9d31a7eSJuraj Linkeš
143c9d31a7eSJuraj Linkešclass _TarCompressionFormat(StrEnum):
144c9d31a7eSJuraj Linkeš    """Compression formats that tar can use.
145c9d31a7eSJuraj Linkeš
146c9d31a7eSJuraj Linkeš    Enum names are the shell compression commands
147c9d31a7eSJuraj Linkeš    and Enum values are the associated file extensions.
148c9d31a7eSJuraj Linkeš    """
149c9d31a7eSJuraj Linkeš
150c9d31a7eSJuraj Linkeš    gzip = "gz"
151c9d31a7eSJuraj Linkeš    compress = "Z"
152c9d31a7eSJuraj Linkeš    bzip2 = "bz2"
153c9d31a7eSJuraj Linkeš    lzip = "lz"
154c9d31a7eSJuraj Linkeš    lzma = "lzma"
155c9d31a7eSJuraj Linkeš    lzop = "lzo"
156c9d31a7eSJuraj Linkeš    xz = "xz"
157c9d31a7eSJuraj Linkeš    zstd = "zst"
158c9d31a7eSJuraj Linkeš
159c9d31a7eSJuraj Linkeš
160c9d31a7eSJuraj Linkešclass DPDKGitTarball(object):
1616ef07151SJuraj Linkeš    """Compressed tarball of DPDK from the repository.
162c9d31a7eSJuraj Linkeš
1636ef07151SJuraj Linkeš    The class supports the :class:`os.PathLike` protocol,
164c9d31a7eSJuraj Linkeš    which is used to get the Path of the tarball::
165c9d31a7eSJuraj Linkeš
166c9d31a7eSJuraj Linkeš        from pathlib import Path
167c9d31a7eSJuraj Linkeš        tarball = DPDKGitTarball("HEAD", "output")
168c9d31a7eSJuraj Linkeš        tarball_path = Path(tarball)
169c9d31a7eSJuraj Linkeš    """
170c9d31a7eSJuraj Linkeš
171c9d31a7eSJuraj Linkeš    _git_ref: str
172c9d31a7eSJuraj Linkeš    _tar_compression_format: _TarCompressionFormat
173c9d31a7eSJuraj Linkeš    _tarball_dir: Path
174c9d31a7eSJuraj Linkeš    _tarball_name: str
175c9d31a7eSJuraj Linkeš    _tarball_path: Path | None
176c9d31a7eSJuraj Linkeš
177c9d31a7eSJuraj Linkeš    def __init__(
178c9d31a7eSJuraj Linkeš        self,
179c9d31a7eSJuraj Linkeš        git_ref: str,
180c9d31a7eSJuraj Linkeš        output_dir: str,
181c9d31a7eSJuraj Linkeš        tar_compression_format: _TarCompressionFormat = _TarCompressionFormat.xz,
182c9d31a7eSJuraj Linkeš    ):
1836ef07151SJuraj Linkeš        """Create the tarball during initialization.
1846ef07151SJuraj Linkeš
1856ef07151SJuraj Linkeš        The DPDK version is specified with `git_ref`. The tarball will be compressed with
1866ef07151SJuraj Linkeš        `tar_compression_format`, which must be supported by the DTS execution environment.
1876ef07151SJuraj Linkeš        The resulting tarball will be put into `output_dir`.
1886ef07151SJuraj Linkeš
1896ef07151SJuraj Linkeš        Args:
1906ef07151SJuraj Linkeš            git_ref: A git commit ID, tag ID or tree ID.
1916ef07151SJuraj Linkeš            output_dir: The directory where to put the resulting tarball.
1926ef07151SJuraj Linkeš            tar_compression_format: The compression format to use.
1936ef07151SJuraj Linkeš        """
194c9d31a7eSJuraj Linkeš        self._git_ref = git_ref
195c9d31a7eSJuraj Linkeš        self._tar_compression_format = tar_compression_format
196c9d31a7eSJuraj Linkeš
197c9d31a7eSJuraj Linkeš        self._tarball_dir = Path(output_dir, "tarball")
198c9d31a7eSJuraj Linkeš
199c9d31a7eSJuraj Linkeš        self._create_tarball_dir()
200c9d31a7eSJuraj Linkeš
201c9d31a7eSJuraj Linkeš        self._tarball_name = (
202c9d31a7eSJuraj Linkeš            f"dpdk-tarball-{self._git_ref}.tar.{self._tar_compression_format.value}"
203c9d31a7eSJuraj Linkeš        )
204c9d31a7eSJuraj Linkeš        self._tarball_path = self._check_tarball_path()
205c9d31a7eSJuraj Linkeš        if not self._tarball_path:
206c9d31a7eSJuraj Linkeš            self._create_tarball()
207c9d31a7eSJuraj Linkeš
208c9d31a7eSJuraj Linkeš    def _create_tarball_dir(self) -> None:
209c9d31a7eSJuraj Linkeš        os.makedirs(self._tarball_dir, exist_ok=True)
210c9d31a7eSJuraj Linkeš
211c9d31a7eSJuraj Linkeš    def _check_tarball_path(self) -> Path | None:
212c9d31a7eSJuraj Linkeš        if self._tarball_name in os.listdir(self._tarball_dir):
213c9d31a7eSJuraj Linkeš            return Path(self._tarball_dir, self._tarball_name)
214c9d31a7eSJuraj Linkeš        return None
215c9d31a7eSJuraj Linkeš
216c9d31a7eSJuraj Linkeš    def _create_tarball(self) -> None:
217c9d31a7eSJuraj Linkeš        self._tarball_path = Path(self._tarball_dir, self._tarball_name)
218c9d31a7eSJuraj Linkeš
219c9d31a7eSJuraj Linkeš        atexit.register(self._delete_tarball)
220c9d31a7eSJuraj Linkeš
221c9d31a7eSJuraj Linkeš        result = subprocess.run(
222c9d31a7eSJuraj Linkeš            'git -C "$(git rev-parse --show-toplevel)" archive '
223c9d31a7eSJuraj Linkeš            f'{self._git_ref} --prefix="dpdk-tarball-{self._git_ref + os.sep}" | '
224c9d31a7eSJuraj Linkeš            f"{self._tar_compression_format} > {Path(self._tarball_path.absolute())}",
225c9d31a7eSJuraj Linkeš            shell=True,
226c9d31a7eSJuraj Linkeš            text=True,
227c9d31a7eSJuraj Linkeš            capture_output=True,
228c9d31a7eSJuraj Linkeš        )
229c9d31a7eSJuraj Linkeš
230c9d31a7eSJuraj Linkeš        if result.returncode != 0:
231c9d31a7eSJuraj Linkeš            raise SubprocessError(
232c9d31a7eSJuraj Linkeš                f"Git archive creation failed with exit code {result.returncode}.\n"
233c9d31a7eSJuraj Linkeš                f"Command: {result.args}\n"
234c9d31a7eSJuraj Linkeš                f"Stdout: {result.stdout}\n"
235c9d31a7eSJuraj Linkeš                f"Stderr: {result.stderr}"
236c9d31a7eSJuraj Linkeš            )
237c9d31a7eSJuraj Linkeš
238c9d31a7eSJuraj Linkeš        atexit.unregister(self._delete_tarball)
239c9d31a7eSJuraj Linkeš
240c9d31a7eSJuraj Linkeš    def _delete_tarball(self) -> None:
241c9d31a7eSJuraj Linkeš        if self._tarball_path and os.path.exists(self._tarball_path):
242c9d31a7eSJuraj Linkeš            os.remove(self._tarball_path)
243c9d31a7eSJuraj Linkeš
244840b1e01SJuraj Linkeš    def __fspath__(self) -> str:
2456ef07151SJuraj Linkeš        """The os.PathLike protocol implementation."""
246c9d31a7eSJuraj Linkeš        return str(self._tarball_path)
247