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 5812c4071SJuraj Linkeš 6c9d31a7eSJuraj Linkešimport atexit 7*cecfe0aaSJuraj Linkešimport json 8c9d31a7eSJuraj Linkešimport os 9c9d31a7eSJuraj Linkešimport subprocess 1057c58bf8SJuraj Linkešimport sys 11c9d31a7eSJuraj Linkešfrom enum import Enum 12c9d31a7eSJuraj Linkešfrom pathlib import Path 13c9d31a7eSJuraj Linkešfrom subprocess import SubprocessError 14c9d31a7eSJuraj Linkeš 15*cecfe0aaSJuraj Linkešfrom scapy.packet import Packet # type: ignore[import] 16*cecfe0aaSJuraj Linkeš 17c9d31a7eSJuraj Linkešfrom .exception import ConfigurationError 18c9d31a7eSJuraj Linkeš 19c9d31a7eSJuraj Linkeš 20c9d31a7eSJuraj Linkešclass StrEnum(Enum): 21c9d31a7eSJuraj Linkeš @staticmethod 22c9d31a7eSJuraj Linkeš def _generate_next_value_( 23c9d31a7eSJuraj Linkeš name: str, start: int, count: int, last_values: object 24c9d31a7eSJuraj Linkeš ) -> str: 25c9d31a7eSJuraj Linkeš return name 26c9d31a7eSJuraj Linkeš 27c9d31a7eSJuraj Linkeš def __str__(self) -> str: 28c9d31a7eSJuraj Linkeš return self.name 2957c58bf8SJuraj Linkeš 3057c58bf8SJuraj Linkeš 3188489c05SJeremy SpewockREGEX_FOR_PCI_ADDRESS = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/" 3288489c05SJeremy Spewock 3388489c05SJeremy Spewock 3457c58bf8SJuraj Linkešdef check_dts_python_version() -> None: 3557c58bf8SJuraj Linkeš if sys.version_info.major < 3 or ( 3657c58bf8SJuraj Linkeš sys.version_info.major == 3 and sys.version_info.minor < 10 3757c58bf8SJuraj Linkeš ): 3857c58bf8SJuraj Linkeš print( 3957c58bf8SJuraj Linkeš RED( 4057c58bf8SJuraj Linkeš ( 4157c58bf8SJuraj Linkeš "WARNING: DTS execution node's python version is lower than" 4257c58bf8SJuraj Linkeš "python 3.10, is deprecated and will not work in future releases." 4357c58bf8SJuraj Linkeš ) 4457c58bf8SJuraj Linkeš ), 4557c58bf8SJuraj Linkeš file=sys.stderr, 4657c58bf8SJuraj Linkeš ) 4757c58bf8SJuraj Linkeš print(RED("Please use Python >= 3.10 instead"), file=sys.stderr) 4857c58bf8SJuraj Linkeš 49812c4071SJuraj Linkeš 50c020b7ceSJuraj Linkešdef expand_range(range_str: str) -> list[int]: 51c020b7ceSJuraj Linkeš """ 52c020b7ceSJuraj Linkeš Process range string into a list of integers. There are two possible formats: 53c020b7ceSJuraj Linkeš n - a single integer 54c020b7ceSJuraj Linkeš n-m - a range of integers 55c020b7ceSJuraj Linkeš 56c020b7ceSJuraj Linkeš The returned range includes both n and m. Empty string returns an empty list. 57c020b7ceSJuraj Linkeš """ 58c020b7ceSJuraj Linkeš expanded_range: list[int] = [] 59c020b7ceSJuraj Linkeš if range_str: 60c020b7ceSJuraj Linkeš range_boundaries = range_str.split("-") 61c020b7ceSJuraj Linkeš # will throw an exception when items in range_boundaries can't be converted, 62c020b7ceSJuraj Linkeš # serving as type check 63c020b7ceSJuraj Linkeš expanded_range.extend( 64c020b7ceSJuraj Linkeš range(int(range_boundaries[0]), int(range_boundaries[-1]) + 1) 65c020b7ceSJuraj Linkeš ) 66c020b7ceSJuraj Linkeš 67c020b7ceSJuraj Linkeš return expanded_range 68c020b7ceSJuraj Linkeš 69c020b7ceSJuraj Linkeš 70*cecfe0aaSJuraj Linkešdef get_packet_summaries(packets: list[Packet]): 71*cecfe0aaSJuraj Linkeš if len(packets) == 1: 72*cecfe0aaSJuraj Linkeš packet_summaries = packets[0].summary() 73*cecfe0aaSJuraj Linkeš else: 74*cecfe0aaSJuraj Linkeš packet_summaries = json.dumps( 75*cecfe0aaSJuraj Linkeš list(map(lambda pkt: pkt.summary(), packets)), indent=4 76*cecfe0aaSJuraj Linkeš ) 77*cecfe0aaSJuraj Linkeš return f"Packet contents: \n{packet_summaries}" 78*cecfe0aaSJuraj Linkeš 79*cecfe0aaSJuraj Linkeš 80812c4071SJuraj Linkešdef RED(text: str) -> str: 81812c4071SJuraj Linkeš return f"\u001B[31;1m{str(text)}\u001B[0m" 82680d8a24SJuraj Linkeš 83680d8a24SJuraj Linkeš 84680d8a24SJuraj Linkešclass MesonArgs(object): 85680d8a24SJuraj Linkeš """ 86680d8a24SJuraj Linkeš Aggregate the arguments needed to build DPDK: 87680d8a24SJuraj Linkeš default_library: Default library type, Meson allows "shared", "static" and "both". 88680d8a24SJuraj Linkeš Defaults to None, in which case the argument won't be used. 89680d8a24SJuraj Linkeš Keyword arguments: The arguments found in meson_options.txt in root DPDK directory. 90680d8a24SJuraj Linkeš Do not use -D with them, for example: 91680d8a24SJuraj Linkeš meson_args = MesonArgs(enable_kmods=True). 92680d8a24SJuraj Linkeš """ 93680d8a24SJuraj Linkeš 94680d8a24SJuraj Linkeš _default_library: str 95680d8a24SJuraj Linkeš 96680d8a24SJuraj Linkeš def __init__(self, default_library: str | None = None, **dpdk_args: str | bool): 97680d8a24SJuraj Linkeš self._default_library = ( 98680d8a24SJuraj Linkeš f"--default-library={default_library}" if default_library else "" 99680d8a24SJuraj Linkeš ) 100680d8a24SJuraj Linkeš self._dpdk_args = " ".join( 101680d8a24SJuraj Linkeš ( 102680d8a24SJuraj Linkeš f"-D{dpdk_arg_name}={dpdk_arg_value}" 103680d8a24SJuraj Linkeš for dpdk_arg_name, dpdk_arg_value in dpdk_args.items() 104680d8a24SJuraj Linkeš ) 105680d8a24SJuraj Linkeš ) 106680d8a24SJuraj Linkeš 107680d8a24SJuraj Linkeš def __str__(self) -> str: 108680d8a24SJuraj Linkeš return " ".join(f"{self._default_library} {self._dpdk_args}".split()) 109c9d31a7eSJuraj Linkeš 110c9d31a7eSJuraj Linkeš 111c9d31a7eSJuraj Linkešclass _TarCompressionFormat(StrEnum): 112c9d31a7eSJuraj Linkeš """Compression formats that tar can use. 113c9d31a7eSJuraj Linkeš 114c9d31a7eSJuraj Linkeš Enum names are the shell compression commands 115c9d31a7eSJuraj Linkeš and Enum values are the associated file extensions. 116c9d31a7eSJuraj Linkeš """ 117c9d31a7eSJuraj Linkeš 118c9d31a7eSJuraj Linkeš gzip = "gz" 119c9d31a7eSJuraj Linkeš compress = "Z" 120c9d31a7eSJuraj Linkeš bzip2 = "bz2" 121c9d31a7eSJuraj Linkeš lzip = "lz" 122c9d31a7eSJuraj Linkeš lzma = "lzma" 123c9d31a7eSJuraj Linkeš lzop = "lzo" 124c9d31a7eSJuraj Linkeš xz = "xz" 125c9d31a7eSJuraj Linkeš zstd = "zst" 126c9d31a7eSJuraj Linkeš 127c9d31a7eSJuraj Linkeš 128c9d31a7eSJuraj Linkešclass DPDKGitTarball(object): 129c9d31a7eSJuraj Linkeš """Create a compressed tarball of DPDK from the repository. 130c9d31a7eSJuraj Linkeš 131c9d31a7eSJuraj Linkeš The DPDK version is specified with git object git_ref. 132c9d31a7eSJuraj Linkeš The tarball will be compressed with _TarCompressionFormat, 133c9d31a7eSJuraj Linkeš which must be supported by the DTS execution environment. 134c9d31a7eSJuraj Linkeš The resulting tarball will be put into output_dir. 135c9d31a7eSJuraj Linkeš 136c9d31a7eSJuraj Linkeš The class supports the os.PathLike protocol, 137c9d31a7eSJuraj Linkeš which is used to get the Path of the tarball:: 138c9d31a7eSJuraj Linkeš 139c9d31a7eSJuraj Linkeš from pathlib import Path 140c9d31a7eSJuraj Linkeš tarball = DPDKGitTarball("HEAD", "output") 141c9d31a7eSJuraj Linkeš tarball_path = Path(tarball) 142c9d31a7eSJuraj Linkeš 143c9d31a7eSJuraj Linkeš Arguments: 144c9d31a7eSJuraj Linkeš git_ref: A git commit ID, tag ID or tree ID. 145c9d31a7eSJuraj Linkeš output_dir: The directory where to put the resulting tarball. 146c9d31a7eSJuraj Linkeš tar_compression_format: The compression format to use. 147c9d31a7eSJuraj Linkeš """ 148c9d31a7eSJuraj Linkeš 149c9d31a7eSJuraj Linkeš _git_ref: str 150c9d31a7eSJuraj Linkeš _tar_compression_format: _TarCompressionFormat 151c9d31a7eSJuraj Linkeš _tarball_dir: Path 152c9d31a7eSJuraj Linkeš _tarball_name: str 153c9d31a7eSJuraj Linkeš _tarball_path: Path | None 154c9d31a7eSJuraj Linkeš 155c9d31a7eSJuraj Linkeš def __init__( 156c9d31a7eSJuraj Linkeš self, 157c9d31a7eSJuraj Linkeš git_ref: str, 158c9d31a7eSJuraj Linkeš output_dir: str, 159c9d31a7eSJuraj Linkeš tar_compression_format: _TarCompressionFormat = _TarCompressionFormat.xz, 160c9d31a7eSJuraj Linkeš ): 161c9d31a7eSJuraj Linkeš self._git_ref = git_ref 162c9d31a7eSJuraj Linkeš self._tar_compression_format = tar_compression_format 163c9d31a7eSJuraj Linkeš 164c9d31a7eSJuraj Linkeš self._tarball_dir = Path(output_dir, "tarball") 165c9d31a7eSJuraj Linkeš 166c9d31a7eSJuraj Linkeš self._get_commit_id() 167c9d31a7eSJuraj Linkeš self._create_tarball_dir() 168c9d31a7eSJuraj Linkeš 169c9d31a7eSJuraj Linkeš self._tarball_name = ( 170c9d31a7eSJuraj Linkeš f"dpdk-tarball-{self._git_ref}.tar.{self._tar_compression_format.value}" 171c9d31a7eSJuraj Linkeš ) 172c9d31a7eSJuraj Linkeš self._tarball_path = self._check_tarball_path() 173c9d31a7eSJuraj Linkeš if not self._tarball_path: 174c9d31a7eSJuraj Linkeš self._create_tarball() 175c9d31a7eSJuraj Linkeš 176c9d31a7eSJuraj Linkeš def _get_commit_id(self) -> None: 177c9d31a7eSJuraj Linkeš result = subprocess.run( 178c9d31a7eSJuraj Linkeš ["git", "rev-parse", "--verify", self._git_ref], 179c9d31a7eSJuraj Linkeš text=True, 180c9d31a7eSJuraj Linkeš capture_output=True, 181c9d31a7eSJuraj Linkeš ) 182c9d31a7eSJuraj Linkeš if result.returncode != 0: 183c9d31a7eSJuraj Linkeš raise ConfigurationError( 184c9d31a7eSJuraj Linkeš f"{self._git_ref} is neither a path to an existing DPDK " 185c9d31a7eSJuraj Linkeš "archive nor a valid git reference.\n" 186c9d31a7eSJuraj Linkeš f"Command: {result.args}\n" 187c9d31a7eSJuraj Linkeš f"Stdout: {result.stdout}\n" 188c9d31a7eSJuraj Linkeš f"Stderr: {result.stderr}" 189c9d31a7eSJuraj Linkeš ) 190c9d31a7eSJuraj Linkeš self._git_ref = result.stdout.strip() 191c9d31a7eSJuraj Linkeš 192c9d31a7eSJuraj Linkeš def _create_tarball_dir(self) -> None: 193c9d31a7eSJuraj Linkeš os.makedirs(self._tarball_dir, exist_ok=True) 194c9d31a7eSJuraj Linkeš 195c9d31a7eSJuraj Linkeš def _check_tarball_path(self) -> Path | None: 196c9d31a7eSJuraj Linkeš if self._tarball_name in os.listdir(self._tarball_dir): 197c9d31a7eSJuraj Linkeš return Path(self._tarball_dir, self._tarball_name) 198c9d31a7eSJuraj Linkeš return None 199c9d31a7eSJuraj Linkeš 200c9d31a7eSJuraj Linkeš def _create_tarball(self) -> None: 201c9d31a7eSJuraj Linkeš self._tarball_path = Path(self._tarball_dir, self._tarball_name) 202c9d31a7eSJuraj Linkeš 203c9d31a7eSJuraj Linkeš atexit.register(self._delete_tarball) 204c9d31a7eSJuraj Linkeš 205c9d31a7eSJuraj Linkeš result = subprocess.run( 206c9d31a7eSJuraj Linkeš 'git -C "$(git rev-parse --show-toplevel)" archive ' 207c9d31a7eSJuraj Linkeš f'{self._git_ref} --prefix="dpdk-tarball-{self._git_ref + os.sep}" | ' 208c9d31a7eSJuraj Linkeš f"{self._tar_compression_format} > {Path(self._tarball_path.absolute())}", 209c9d31a7eSJuraj Linkeš shell=True, 210c9d31a7eSJuraj Linkeš text=True, 211c9d31a7eSJuraj Linkeš capture_output=True, 212c9d31a7eSJuraj Linkeš ) 213c9d31a7eSJuraj Linkeš 214c9d31a7eSJuraj Linkeš if result.returncode != 0: 215c9d31a7eSJuraj Linkeš raise SubprocessError( 216c9d31a7eSJuraj Linkeš f"Git archive creation failed with exit code {result.returncode}.\n" 217c9d31a7eSJuraj Linkeš f"Command: {result.args}\n" 218c9d31a7eSJuraj Linkeš f"Stdout: {result.stdout}\n" 219c9d31a7eSJuraj Linkeš f"Stderr: {result.stderr}" 220c9d31a7eSJuraj Linkeš ) 221c9d31a7eSJuraj Linkeš 222c9d31a7eSJuraj Linkeš atexit.unregister(self._delete_tarball) 223c9d31a7eSJuraj Linkeš 224c9d31a7eSJuraj Linkeš def _delete_tarball(self) -> None: 225c9d31a7eSJuraj Linkeš if self._tarball_path and os.path.exists(self._tarball_path): 226c9d31a7eSJuraj Linkeš os.remove(self._tarball_path) 227c9d31a7eSJuraj Linkeš 228c9d31a7eSJuraj Linkeš def __fspath__(self): 229c9d31a7eSJuraj Linkeš return str(self._tarball_path) 230