1# SPDX-License-Identifier: BSD-3-Clause 2# Copyright(c) 2010-2014 Intel Corporation 3# Copyright(c) 2022-2023 PANTHEON.tech s.r.o. 4# Copyright(c) 2022-2023 University of New Hampshire 5 6import atexit 7import os 8import subprocess 9import sys 10from enum import Enum 11from pathlib import Path 12from subprocess import SubprocessError 13 14from .exception import ConfigurationError 15 16 17class StrEnum(Enum): 18 @staticmethod 19 def _generate_next_value_( 20 name: str, start: int, count: int, last_values: object 21 ) -> str: 22 return name 23 24 def __str__(self) -> str: 25 return self.name 26 27 28REGEX_FOR_PCI_ADDRESS = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/" 29 30 31def check_dts_python_version() -> None: 32 if sys.version_info.major < 3 or ( 33 sys.version_info.major == 3 and sys.version_info.minor < 10 34 ): 35 print( 36 RED( 37 ( 38 "WARNING: DTS execution node's python version is lower than" 39 "python 3.10, is deprecated and will not work in future releases." 40 ) 41 ), 42 file=sys.stderr, 43 ) 44 print(RED("Please use Python >= 3.10 instead"), file=sys.stderr) 45 46 47def expand_range(range_str: str) -> list[int]: 48 """ 49 Process range string into a list of integers. There are two possible formats: 50 n - a single integer 51 n-m - a range of integers 52 53 The returned range includes both n and m. Empty string returns an empty list. 54 """ 55 expanded_range: list[int] = [] 56 if range_str: 57 range_boundaries = range_str.split("-") 58 # will throw an exception when items in range_boundaries can't be converted, 59 # serving as type check 60 expanded_range.extend( 61 range(int(range_boundaries[0]), int(range_boundaries[-1]) + 1) 62 ) 63 64 return expanded_range 65 66 67def RED(text: str) -> str: 68 return f"\u001B[31;1m{str(text)}\u001B[0m" 69 70 71class MesonArgs(object): 72 """ 73 Aggregate the arguments needed to build DPDK: 74 default_library: Default library type, Meson allows "shared", "static" and "both". 75 Defaults to None, in which case the argument won't be used. 76 Keyword arguments: The arguments found in meson_options.txt in root DPDK directory. 77 Do not use -D with them, for example: 78 meson_args = MesonArgs(enable_kmods=True). 79 """ 80 81 _default_library: str 82 83 def __init__(self, default_library: str | None = None, **dpdk_args: str | bool): 84 self._default_library = ( 85 f"--default-library={default_library}" if default_library else "" 86 ) 87 self._dpdk_args = " ".join( 88 ( 89 f"-D{dpdk_arg_name}={dpdk_arg_value}" 90 for dpdk_arg_name, dpdk_arg_value in dpdk_args.items() 91 ) 92 ) 93 94 def __str__(self) -> str: 95 return " ".join(f"{self._default_library} {self._dpdk_args}".split()) 96 97 98class _TarCompressionFormat(StrEnum): 99 """Compression formats that tar can use. 100 101 Enum names are the shell compression commands 102 and Enum values are the associated file extensions. 103 """ 104 105 gzip = "gz" 106 compress = "Z" 107 bzip2 = "bz2" 108 lzip = "lz" 109 lzma = "lzma" 110 lzop = "lzo" 111 xz = "xz" 112 zstd = "zst" 113 114 115class DPDKGitTarball(object): 116 """Create a compressed tarball of DPDK from the repository. 117 118 The DPDK version is specified with git object git_ref. 119 The tarball will be compressed with _TarCompressionFormat, 120 which must be supported by the DTS execution environment. 121 The resulting tarball will be put into output_dir. 122 123 The class supports the os.PathLike protocol, 124 which is used to get the Path of the tarball:: 125 126 from pathlib import Path 127 tarball = DPDKGitTarball("HEAD", "output") 128 tarball_path = Path(tarball) 129 130 Arguments: 131 git_ref: A git commit ID, tag ID or tree ID. 132 output_dir: The directory where to put the resulting tarball. 133 tar_compression_format: The compression format to use. 134 """ 135 136 _git_ref: str 137 _tar_compression_format: _TarCompressionFormat 138 _tarball_dir: Path 139 _tarball_name: str 140 _tarball_path: Path | None 141 142 def __init__( 143 self, 144 git_ref: str, 145 output_dir: str, 146 tar_compression_format: _TarCompressionFormat = _TarCompressionFormat.xz, 147 ): 148 self._git_ref = git_ref 149 self._tar_compression_format = tar_compression_format 150 151 self._tarball_dir = Path(output_dir, "tarball") 152 153 self._get_commit_id() 154 self._create_tarball_dir() 155 156 self._tarball_name = ( 157 f"dpdk-tarball-{self._git_ref}.tar.{self._tar_compression_format.value}" 158 ) 159 self._tarball_path = self._check_tarball_path() 160 if not self._tarball_path: 161 self._create_tarball() 162 163 def _get_commit_id(self) -> None: 164 result = subprocess.run( 165 ["git", "rev-parse", "--verify", self._git_ref], 166 text=True, 167 capture_output=True, 168 ) 169 if result.returncode != 0: 170 raise ConfigurationError( 171 f"{self._git_ref} is neither a path to an existing DPDK " 172 "archive nor a valid git reference.\n" 173 f"Command: {result.args}\n" 174 f"Stdout: {result.stdout}\n" 175 f"Stderr: {result.stderr}" 176 ) 177 self._git_ref = result.stdout.strip() 178 179 def _create_tarball_dir(self) -> None: 180 os.makedirs(self._tarball_dir, exist_ok=True) 181 182 def _check_tarball_path(self) -> Path | None: 183 if self._tarball_name in os.listdir(self._tarball_dir): 184 return Path(self._tarball_dir, self._tarball_name) 185 return None 186 187 def _create_tarball(self) -> None: 188 self._tarball_path = Path(self._tarball_dir, self._tarball_name) 189 190 atexit.register(self._delete_tarball) 191 192 result = subprocess.run( 193 'git -C "$(git rev-parse --show-toplevel)" archive ' 194 f'{self._git_ref} --prefix="dpdk-tarball-{self._git_ref + os.sep}" | ' 195 f"{self._tar_compression_format} > {Path(self._tarball_path.absolute())}", 196 shell=True, 197 text=True, 198 capture_output=True, 199 ) 200 201 if result.returncode != 0: 202 raise SubprocessError( 203 f"Git archive creation failed with exit code {result.returncode}.\n" 204 f"Command: {result.args}\n" 205 f"Stdout: {result.stdout}\n" 206 f"Stderr: {result.stderr}" 207 ) 208 209 atexit.unregister(self._delete_tarball) 210 211 def _delete_tarball(self) -> None: 212 if self._tarball_path and os.path.exists(self._tarball_path): 213 os.remove(self._tarball_path) 214 215 def __fspath__(self): 216 return str(self._tarball_path) 217