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