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