xref: /dpdk/dts/framework/utils.py (revision 840b1e01af3391ecda5124322109f2d34b7d6d86)
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
7cecfe0aaSJuraj Linkešimport json
8c9d31a7eSJuraj Linkešimport os
9c9d31a7eSJuraj Linkešimport subprocess
10c9d31a7eSJuraj Linkešfrom enum import Enum
11c9d31a7eSJuraj Linkešfrom pathlib import Path
12c9d31a7eSJuraj Linkešfrom subprocess import SubprocessError
13c9d31a7eSJuraj Linkeš
14cecfe0aaSJuraj Linkešfrom scapy.packet import Packet  # type: ignore[import]
15cecfe0aaSJuraj Linkeš
16c9d31a7eSJuraj Linkešfrom .exception import ConfigurationError
17c9d31a7eSJuraj Linkeš
18*840b1e01SJuraj LinkešREGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
1957c58bf8SJuraj Linkeš
20812c4071SJuraj Linkeš
21c020b7ceSJuraj Linkešdef expand_range(range_str: str) -> list[int]:
22c020b7ceSJuraj Linkeš    """
23c020b7ceSJuraj Linkeš    Process range string into a list of integers. There are two possible formats:
24c020b7ceSJuraj Linkeš    n - a single integer
25c020b7ceSJuraj Linkeš    n-m - a range of integers
26c020b7ceSJuraj Linkeš
27c020b7ceSJuraj Linkeš    The returned range includes both n and m. Empty string returns an empty list.
28c020b7ceSJuraj Linkeš    """
29c020b7ceSJuraj Linkeš    expanded_range: list[int] = []
30c020b7ceSJuraj Linkeš    if range_str:
31c020b7ceSJuraj Linkeš        range_boundaries = range_str.split("-")
32c020b7ceSJuraj Linkeš        # will throw an exception when items in range_boundaries can't be converted,
33c020b7ceSJuraj Linkeš        # serving as type check
34517b4b26SJuraj Linkeš        expanded_range.extend(range(int(range_boundaries[0]), int(range_boundaries[-1]) + 1))
35c020b7ceSJuraj Linkeš
36c020b7ceSJuraj Linkeš    return expanded_range
37c020b7ceSJuraj Linkeš
38c020b7ceSJuraj Linkeš
39*840b1e01SJuraj Linkešdef get_packet_summaries(packets: list[Packet]) -> str:
40cecfe0aaSJuraj Linkeš    if len(packets) == 1:
41cecfe0aaSJuraj Linkeš        packet_summaries = packets[0].summary()
42cecfe0aaSJuraj Linkeš    else:
43517b4b26SJuraj Linkeš        packet_summaries = json.dumps(list(map(lambda pkt: pkt.summary(), packets)), indent=4)
44cecfe0aaSJuraj Linkeš    return f"Packet contents: \n{packet_summaries}"
45cecfe0aaSJuraj Linkeš
46cecfe0aaSJuraj Linkeš
47*840b1e01SJuraj Linkešclass StrEnum(Enum):
48*840b1e01SJuraj Linkeš    @staticmethod
49*840b1e01SJuraj Linkeš    def _generate_next_value_(name: str, start: int, count: int, last_values: object) -> str:
50*840b1e01SJuraj Linkeš        return name
51*840b1e01SJuraj Linkeš
52*840b1e01SJuraj Linkeš    def __str__(self) -> str:
53*840b1e01SJuraj Linkeš        return self.name
54680d8a24SJuraj Linkeš
55680d8a24SJuraj Linkeš
56680d8a24SJuraj Linkešclass MesonArgs(object):
57680d8a24SJuraj Linkeš    """
58680d8a24SJuraj Linkeš    Aggregate the arguments needed to build DPDK:
59680d8a24SJuraj Linkeš    default_library: Default library type, Meson allows "shared", "static" and "both".
60680d8a24SJuraj Linkeš               Defaults to None, in which case the argument won't be used.
61680d8a24SJuraj Linkeš    Keyword arguments: The arguments found in meson_options.txt in root DPDK directory.
62680d8a24SJuraj Linkeš               Do not use -D with them, for example:
63680d8a24SJuraj Linkeš               meson_args = MesonArgs(enable_kmods=True).
64680d8a24SJuraj Linkeš    """
65680d8a24SJuraj Linkeš
66680d8a24SJuraj Linkeš    _default_library: str
67680d8a24SJuraj Linkeš
68680d8a24SJuraj Linkeš    def __init__(self, default_library: str | None = None, **dpdk_args: str | bool):
69517b4b26SJuraj Linkeš        self._default_library = f"--default-library={default_library}" if default_library else ""
70680d8a24SJuraj Linkeš        self._dpdk_args = " ".join(
71680d8a24SJuraj Linkeš            (
72680d8a24SJuraj Linkeš                f"-D{dpdk_arg_name}={dpdk_arg_value}"
73680d8a24SJuraj Linkeš                for dpdk_arg_name, dpdk_arg_value in dpdk_args.items()
74680d8a24SJuraj Linkeš            )
75680d8a24SJuraj Linkeš        )
76680d8a24SJuraj Linkeš
77680d8a24SJuraj Linkeš    def __str__(self) -> str:
78680d8a24SJuraj Linkeš        return " ".join(f"{self._default_library} {self._dpdk_args}".split())
79c9d31a7eSJuraj Linkeš
80c9d31a7eSJuraj Linkeš
81c9d31a7eSJuraj Linkešclass _TarCompressionFormat(StrEnum):
82c9d31a7eSJuraj Linkeš    """Compression formats that tar can use.
83c9d31a7eSJuraj Linkeš
84c9d31a7eSJuraj Linkeš    Enum names are the shell compression commands
85c9d31a7eSJuraj Linkeš    and Enum values are the associated file extensions.
86c9d31a7eSJuraj Linkeš    """
87c9d31a7eSJuraj Linkeš
88c9d31a7eSJuraj Linkeš    gzip = "gz"
89c9d31a7eSJuraj Linkeš    compress = "Z"
90c9d31a7eSJuraj Linkeš    bzip2 = "bz2"
91c9d31a7eSJuraj Linkeš    lzip = "lz"
92c9d31a7eSJuraj Linkeš    lzma = "lzma"
93c9d31a7eSJuraj Linkeš    lzop = "lzo"
94c9d31a7eSJuraj Linkeš    xz = "xz"
95c9d31a7eSJuraj Linkeš    zstd = "zst"
96c9d31a7eSJuraj Linkeš
97c9d31a7eSJuraj Linkeš
98c9d31a7eSJuraj Linkešclass DPDKGitTarball(object):
99c9d31a7eSJuraj Linkeš    """Create a compressed tarball of DPDK from the repository.
100c9d31a7eSJuraj Linkeš
101c9d31a7eSJuraj Linkeš    The DPDK version is specified with git object git_ref.
102c9d31a7eSJuraj Linkeš    The tarball will be compressed with _TarCompressionFormat,
103c9d31a7eSJuraj Linkeš    which must be supported by the DTS execution environment.
104c9d31a7eSJuraj Linkeš    The resulting tarball will be put into output_dir.
105c9d31a7eSJuraj Linkeš
106c9d31a7eSJuraj Linkeš    The class supports the os.PathLike protocol,
107c9d31a7eSJuraj Linkeš    which is used to get the Path of the tarball::
108c9d31a7eSJuraj Linkeš
109c9d31a7eSJuraj Linkeš        from pathlib import Path
110c9d31a7eSJuraj Linkeš        tarball = DPDKGitTarball("HEAD", "output")
111c9d31a7eSJuraj Linkeš        tarball_path = Path(tarball)
112c9d31a7eSJuraj Linkeš
113c9d31a7eSJuraj Linkeš    Arguments:
114c9d31a7eSJuraj Linkeš        git_ref: A git commit ID, tag ID or tree ID.
115c9d31a7eSJuraj Linkeš        output_dir: The directory where to put the resulting tarball.
116c9d31a7eSJuraj Linkeš        tar_compression_format: The compression format to use.
117c9d31a7eSJuraj Linkeš    """
118c9d31a7eSJuraj Linkeš
119c9d31a7eSJuraj Linkeš    _git_ref: str
120c9d31a7eSJuraj Linkeš    _tar_compression_format: _TarCompressionFormat
121c9d31a7eSJuraj Linkeš    _tarball_dir: Path
122c9d31a7eSJuraj Linkeš    _tarball_name: str
123c9d31a7eSJuraj Linkeš    _tarball_path: Path | None
124c9d31a7eSJuraj Linkeš
125c9d31a7eSJuraj Linkeš    def __init__(
126c9d31a7eSJuraj Linkeš        self,
127c9d31a7eSJuraj Linkeš        git_ref: str,
128c9d31a7eSJuraj Linkeš        output_dir: str,
129c9d31a7eSJuraj Linkeš        tar_compression_format: _TarCompressionFormat = _TarCompressionFormat.xz,
130c9d31a7eSJuraj Linkeš    ):
131c9d31a7eSJuraj Linkeš        self._git_ref = git_ref
132c9d31a7eSJuraj Linkeš        self._tar_compression_format = tar_compression_format
133c9d31a7eSJuraj Linkeš
134c9d31a7eSJuraj Linkeš        self._tarball_dir = Path(output_dir, "tarball")
135c9d31a7eSJuraj Linkeš
136c9d31a7eSJuraj Linkeš        self._get_commit_id()
137c9d31a7eSJuraj Linkeš        self._create_tarball_dir()
138c9d31a7eSJuraj Linkeš
139c9d31a7eSJuraj Linkeš        self._tarball_name = (
140c9d31a7eSJuraj Linkeš            f"dpdk-tarball-{self._git_ref}.tar.{self._tar_compression_format.value}"
141c9d31a7eSJuraj Linkeš        )
142c9d31a7eSJuraj Linkeš        self._tarball_path = self._check_tarball_path()
143c9d31a7eSJuraj Linkeš        if not self._tarball_path:
144c9d31a7eSJuraj Linkeš            self._create_tarball()
145c9d31a7eSJuraj Linkeš
146c9d31a7eSJuraj Linkeš    def _get_commit_id(self) -> None:
147c9d31a7eSJuraj Linkeš        result = subprocess.run(
148c9d31a7eSJuraj Linkeš            ["git", "rev-parse", "--verify", self._git_ref],
149c9d31a7eSJuraj Linkeš            text=True,
150c9d31a7eSJuraj Linkeš            capture_output=True,
151c9d31a7eSJuraj Linkeš        )
152c9d31a7eSJuraj Linkeš        if result.returncode != 0:
153c9d31a7eSJuraj Linkeš            raise ConfigurationError(
154c9d31a7eSJuraj Linkeš                f"{self._git_ref} is neither a path to an existing DPDK "
155c9d31a7eSJuraj Linkeš                "archive nor a valid git reference.\n"
156c9d31a7eSJuraj Linkeš                f"Command: {result.args}\n"
157c9d31a7eSJuraj Linkeš                f"Stdout: {result.stdout}\n"
158c9d31a7eSJuraj Linkeš                f"Stderr: {result.stderr}"
159c9d31a7eSJuraj Linkeš            )
160c9d31a7eSJuraj Linkeš        self._git_ref = result.stdout.strip()
161c9d31a7eSJuraj Linkeš
162c9d31a7eSJuraj Linkeš    def _create_tarball_dir(self) -> None:
163c9d31a7eSJuraj Linkeš        os.makedirs(self._tarball_dir, exist_ok=True)
164c9d31a7eSJuraj Linkeš
165c9d31a7eSJuraj Linkeš    def _check_tarball_path(self) -> Path | None:
166c9d31a7eSJuraj Linkeš        if self._tarball_name in os.listdir(self._tarball_dir):
167c9d31a7eSJuraj Linkeš            return Path(self._tarball_dir, self._tarball_name)
168c9d31a7eSJuraj Linkeš        return None
169c9d31a7eSJuraj Linkeš
170c9d31a7eSJuraj Linkeš    def _create_tarball(self) -> None:
171c9d31a7eSJuraj Linkeš        self._tarball_path = Path(self._tarball_dir, self._tarball_name)
172c9d31a7eSJuraj Linkeš
173c9d31a7eSJuraj Linkeš        atexit.register(self._delete_tarball)
174c9d31a7eSJuraj Linkeš
175c9d31a7eSJuraj Linkeš        result = subprocess.run(
176c9d31a7eSJuraj Linkeš            'git -C "$(git rev-parse --show-toplevel)" archive '
177c9d31a7eSJuraj Linkeš            f'{self._git_ref} --prefix="dpdk-tarball-{self._git_ref + os.sep}" | '
178c9d31a7eSJuraj Linkeš            f"{self._tar_compression_format} > {Path(self._tarball_path.absolute())}",
179c9d31a7eSJuraj Linkeš            shell=True,
180c9d31a7eSJuraj Linkeš            text=True,
181c9d31a7eSJuraj Linkeš            capture_output=True,
182c9d31a7eSJuraj Linkeš        )
183c9d31a7eSJuraj Linkeš
184c9d31a7eSJuraj Linkeš        if result.returncode != 0:
185c9d31a7eSJuraj Linkeš            raise SubprocessError(
186c9d31a7eSJuraj Linkeš                f"Git archive creation failed with exit code {result.returncode}.\n"
187c9d31a7eSJuraj Linkeš                f"Command: {result.args}\n"
188c9d31a7eSJuraj Linkeš                f"Stdout: {result.stdout}\n"
189c9d31a7eSJuraj Linkeš                f"Stderr: {result.stderr}"
190c9d31a7eSJuraj Linkeš            )
191c9d31a7eSJuraj Linkeš
192c9d31a7eSJuraj Linkeš        atexit.unregister(self._delete_tarball)
193c9d31a7eSJuraj Linkeš
194c9d31a7eSJuraj Linkeš    def _delete_tarball(self) -> None:
195c9d31a7eSJuraj Linkeš        if self._tarball_path and os.path.exists(self._tarball_path):
196c9d31a7eSJuraj Linkeš            os.remove(self._tarball_path)
197c9d31a7eSJuraj Linkeš
198*840b1e01SJuraj Linkeš    def __fspath__(self) -> str:
199c9d31a7eSJuraj Linkeš        return str(self._tarball_path)
200