xref: /dpdk/dts/framework/utils.py (revision c9d31a7e1c94e82e56d5d7c28edd32874eef95e1)
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