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