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 RED(text: str) -> str: 65 return f"\u001B[31;1m{str(text)}\u001B[0m" 66 67 68class MesonArgs(object): 69 """ 70 Aggregate the arguments needed to build DPDK: 71 default_library: Default library type, Meson allows "shared", "static" and "both". 72 Defaults to None, in which case the argument won't be used. 73 Keyword arguments: The arguments found in meson_options.txt in root DPDK directory. 74 Do not use -D with them, for example: 75 meson_args = MesonArgs(enable_kmods=True). 76 """ 77 78 _default_library: str 79 80 def __init__(self, default_library: str | None = None, **dpdk_args: str | bool): 81 self._default_library = ( 82 f"--default-library={default_library}" if default_library else "" 83 ) 84 self._dpdk_args = " ".join( 85 ( 86 f"-D{dpdk_arg_name}={dpdk_arg_value}" 87 for dpdk_arg_name, dpdk_arg_value in dpdk_args.items() 88 ) 89 ) 90 91 def __str__(self) -> str: 92 return " ".join(f"{self._default_library} {self._dpdk_args}".split()) 93 94 95class _TarCompressionFormat(StrEnum): 96 """Compression formats that tar can use. 97 98 Enum names are the shell compression commands 99 and Enum values are the associated file extensions. 100 """ 101 102 gzip = "gz" 103 compress = "Z" 104 bzip2 = "bz2" 105 lzip = "lz" 106 lzma = "lzma" 107 lzop = "lzo" 108 xz = "xz" 109 zstd = "zst" 110 111 112class DPDKGitTarball(object): 113 """Create a compressed tarball of DPDK from the repository. 114 115 The DPDK version is specified with git object git_ref. 116 The tarball will be compressed with _TarCompressionFormat, 117 which must be supported by the DTS execution environment. 118 The resulting tarball will be put into output_dir. 119 120 The class supports the os.PathLike protocol, 121 which is used to get the Path of the tarball:: 122 123 from pathlib import Path 124 tarball = DPDKGitTarball("HEAD", "output") 125 tarball_path = Path(tarball) 126 127 Arguments: 128 git_ref: A git commit ID, tag ID or tree ID. 129 output_dir: The directory where to put the resulting tarball. 130 tar_compression_format: The compression format to use. 131 """ 132 133 _git_ref: str 134 _tar_compression_format: _TarCompressionFormat 135 _tarball_dir: Path 136 _tarball_name: str 137 _tarball_path: Path | None 138 139 def __init__( 140 self, 141 git_ref: str, 142 output_dir: str, 143 tar_compression_format: _TarCompressionFormat = _TarCompressionFormat.xz, 144 ): 145 self._git_ref = git_ref 146 self._tar_compression_format = tar_compression_format 147 148 self._tarball_dir = Path(output_dir, "tarball") 149 150 self._get_commit_id() 151 self._create_tarball_dir() 152 153 self._tarball_name = ( 154 f"dpdk-tarball-{self._git_ref}.tar.{self._tar_compression_format.value}" 155 ) 156 self._tarball_path = self._check_tarball_path() 157 if not self._tarball_path: 158 self._create_tarball() 159 160 def _get_commit_id(self) -> None: 161 result = subprocess.run( 162 ["git", "rev-parse", "--verify", self._git_ref], 163 text=True, 164 capture_output=True, 165 ) 166 if result.returncode != 0: 167 raise ConfigurationError( 168 f"{self._git_ref} is neither a path to an existing DPDK " 169 "archive nor a valid git reference.\n" 170 f"Command: {result.args}\n" 171 f"Stdout: {result.stdout}\n" 172 f"Stderr: {result.stderr}" 173 ) 174 self._git_ref = result.stdout.strip() 175 176 def _create_tarball_dir(self) -> None: 177 os.makedirs(self._tarball_dir, exist_ok=True) 178 179 def _check_tarball_path(self) -> Path | None: 180 if self._tarball_name in os.listdir(self._tarball_dir): 181 return Path(self._tarball_dir, self._tarball_name) 182 return None 183 184 def _create_tarball(self) -> None: 185 self._tarball_path = Path(self._tarball_dir, self._tarball_name) 186 187 atexit.register(self._delete_tarball) 188 189 result = subprocess.run( 190 'git -C "$(git rev-parse --show-toplevel)" archive ' 191 f'{self._git_ref} --prefix="dpdk-tarball-{self._git_ref + os.sep}" | ' 192 f"{self._tar_compression_format} > {Path(self._tarball_path.absolute())}", 193 shell=True, 194 text=True, 195 capture_output=True, 196 ) 197 198 if result.returncode != 0: 199 raise SubprocessError( 200 f"Git archive creation failed with exit code {result.returncode}.\n" 201 f"Command: {result.args}\n" 202 f"Stdout: {result.stdout}\n" 203 f"Stderr: {result.stderr}" 204 ) 205 206 atexit.unregister(self._delete_tarball) 207 208 def _delete_tarball(self) -> None: 209 if self._tarball_path and os.path.exists(self._tarball_path): 210 os.remove(self._tarball_path) 211 212 def __fspath__(self): 213 return str(self._tarball_path) 214