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