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