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