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