amd-debug-tools 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of amd-debug-tools might be problematic. Click here for more details.

amd_debug/installer.py ADDED
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/python3
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import argparse
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ from amd_debug.common import (
9
+ print_color,
10
+ get_distro,
11
+ read_file,
12
+ systemd_in_use,
13
+ show_log_info,
14
+ fatal_error,
15
+ relaunch_sudo,
16
+ AmdTool,
17
+ )
18
+
19
+
20
+ class Headers: # pylint: disable=too-few-public-methods
21
+ """Headers for the script"""
22
+
23
+ MissingIasl = "ACPI extraction tool `iasl` is missing"
24
+ MissingEthtool = "Ethtool is missing"
25
+ InstallAction = "Attempting to install"
26
+ MissingFwupd = "Firmware update library `fwupd` is missing"
27
+ MissingPyudev = "Udev access library `pyudev` is missing"
28
+ MissingPackaging = "Python library `packaging` is missing"
29
+ MissingPandas = "Data library `pandas` is missing"
30
+ MissingTabulate = "Data library `tabulate` is missing"
31
+ MissingJinja2 = "Template library `jinja2` is missing"
32
+ MissingSeaborn = "Data visualization library `seaborn` is missing"
33
+
34
+
35
+ class DistroPackage:
36
+ """Base class for distro packages"""
37
+
38
+ def __init__(self, deb, rpm, arch, message):
39
+ self.deb = deb
40
+ self.rpm = rpm
41
+ self.arch = arch
42
+ self.message = message
43
+
44
+ def install(self):
45
+ """Install the package for a given distro"""
46
+ relaunch_sudo()
47
+ show_install_message(self.message)
48
+ dist = get_distro()
49
+ if dist in ("ubuntu", "debian"):
50
+ if not self.deb:
51
+ return False
52
+ installer = ["apt", "install", self.deb]
53
+ elif dist == "fedora":
54
+ if not self.rpm:
55
+ return False
56
+ release = read_file("/usr/lib/os-release")
57
+ variant = None
58
+ for line in release.split("\n"):
59
+ if line.startswith("VARIANT_ID"):
60
+ variant = line.split("=")[-1]
61
+ if variant != "workstation":
62
+ return False
63
+ installer = ["dnf", "install", "-y", self.rpm]
64
+ elif dist == "arch" or os.path.exists("/etc/arch-release"):
65
+ if not self.arch:
66
+ return False
67
+ installer = ["pacman", "-Sy", self.arch]
68
+ else:
69
+ return False
70
+
71
+ try:
72
+ subprocess.check_call(installer)
73
+ except subprocess.CalledProcessError as e:
74
+ fatal_error(e)
75
+ return True
76
+
77
+
78
+ class PyUdevPackage(DistroPackage):
79
+ """Pyudev package"""
80
+
81
+ def __init__(self):
82
+ super().__init__(
83
+ deb="python3-pyudev",
84
+ rpm="python3-pyudev",
85
+ arch="python-pyudev",
86
+ message=Headers.MissingPyudev,
87
+ )
88
+
89
+
90
+ class PackagingPackage(DistroPackage):
91
+ """Packaging package"""
92
+
93
+ def __init__(self):
94
+ super().__init__(
95
+ deb="python3-packaging",
96
+ rpm=None,
97
+ arch="python-packaging",
98
+ message=Headers.MissingPackaging,
99
+ )
100
+
101
+
102
+ class PandasPackage(DistroPackage):
103
+ """Class for handling the pandas package"""
104
+
105
+ def __init__(self):
106
+ super().__init__(
107
+ deb="python3-pandas",
108
+ rpm="python3-pandas",
109
+ arch="python-pandas",
110
+ message=Headers.MissingPandas,
111
+ )
112
+
113
+
114
+ class TabulatePackage(DistroPackage):
115
+ """Class for handling the tabulate package"""
116
+
117
+ def __init__(self):
118
+ super().__init__(
119
+ deb="python3-tabulate",
120
+ rpm="python3-tabulate",
121
+ arch="python-tabulate",
122
+ message=Headers.MissingTabulate,
123
+ )
124
+
125
+
126
+ class Jinja2Package(DistroPackage):
127
+ """Class for handling the jinja2 package"""
128
+
129
+ def __init__(self):
130
+ super().__init__(
131
+ deb="python3-jinja2",
132
+ rpm="python3-jinja2",
133
+ arch="python-jinja",
134
+ message=Headers.MissingJinja2,
135
+ )
136
+
137
+
138
+ class SeabornPackage(DistroPackage):
139
+ """Class for handling the seaborn package"""
140
+
141
+ def __init__(self):
142
+ super().__init__(
143
+ deb="python3-seaborn",
144
+ rpm="python3-seaborn",
145
+ arch="python-seaborn",
146
+ message=Headers.MissingSeaborn,
147
+ )
148
+
149
+
150
+ class IaslPackage(DistroPackage):
151
+ """Iasl package"""
152
+
153
+ def __init__(self):
154
+ super().__init__(
155
+ deb="acpica-tools",
156
+ rpm="acpica-tools",
157
+ arch="acpica",
158
+ message=Headers.MissingIasl,
159
+ )
160
+
161
+
162
+ class EthtoolPackage(DistroPackage):
163
+ """Ethtool package"""
164
+
165
+ def __init__(self):
166
+ super().__init__(
167
+ deb="ethtool",
168
+ rpm="ethtool",
169
+ arch="ethtool",
170
+ message=Headers.MissingEthtool,
171
+ )
172
+
173
+
174
+ class FwupdPackage(DistroPackage):
175
+ """Fwupd package"""
176
+
177
+ def __init__(self):
178
+ super().__init__(
179
+ deb="gir1.2-fwupd-2.0",
180
+ rpm=None,
181
+ arch=None,
182
+ message=Headers.MissingFwupd,
183
+ )
184
+
185
+
186
+ def show_install_message(message):
187
+ """Show an install message"""
188
+ action = Headers.InstallAction
189
+ message = f"{message}. {action}."
190
+ print_color(message, "👀")
191
+
192
+
193
+ class Installer(AmdTool):
194
+ """Installer class"""
195
+
196
+ def __init__(self, tool_debug):
197
+ log_prefix = "installer" if tool_debug else None
198
+ super().__init__(log_prefix)
199
+ self.systemd = systemd_in_use()
200
+ self.systemd_path = os.path.join("/", "lib", "systemd", "system-sleep")
201
+
202
+ # test if fwupd can report device firmware versions
203
+ try:
204
+ import gi # pylint: disable=import-outside-toplevel
205
+ from gi.repository import (
206
+ GLib as _,
207
+ ) # pylint: disable=import-outside-toplevel
208
+
209
+ gi.require_version("Fwupd", "2.0")
210
+ from gi.repository import (
211
+ Fwupd as _,
212
+ ) # pylint: disable=import-outside-toplevel
213
+
214
+ self.fwupd = True
215
+ except ImportError:
216
+ self.fwupd = False
217
+ except ValueError:
218
+ self.fwupd = False
219
+ self.requirements = []
220
+
221
+ def set_requirements(self, *args):
222
+ """Set the requirements for the installer"""
223
+ self.requirements = args
224
+
225
+ def install_dependencies(self) -> bool:
226
+ """Install the dependencies"""
227
+ if "iasl" in self.requirements:
228
+ try:
229
+ iasl = subprocess.call(["iasl", "-v"], stdout=subprocess.DEVNULL) == 0
230
+ except FileNotFoundError:
231
+ iasl = False
232
+ if not iasl:
233
+ package = IaslPackage()
234
+ if not package.install():
235
+ return False
236
+ if "ethtool" in self.requirements:
237
+ try:
238
+ ethtool = (
239
+ subprocess.call(["ethtool", "-h"], stdout=subprocess.DEVNULL) == 0
240
+ )
241
+ except FileNotFoundError:
242
+ ethtool = False
243
+ if not ethtool:
244
+ package = EthtoolPackage()
245
+ if not package.install():
246
+ return False
247
+ if "fwupd" in self.requirements and not self.fwupd:
248
+ package = FwupdPackage()
249
+ if not package.install():
250
+ return False
251
+ if "pyudev" in self.requirements:
252
+ try:
253
+ import pyudev as _ # pylint: disable=import-outside-toplevel
254
+ except ModuleNotFoundError:
255
+ package = PyUdevPackage()
256
+ if not package.install():
257
+ return False
258
+ if "packaging" in self.requirements:
259
+ try:
260
+ import packaging as _ # pylint: disable=import-outside-toplevel
261
+ except ModuleNotFoundError:
262
+ package = PackagingPackage()
263
+ if not package.install():
264
+ return False
265
+ if "pandas" in self.requirements:
266
+ try:
267
+ import pandas as _ # pylint: disable=import-outside-toplevel
268
+ except ModuleNotFoundError:
269
+ package = PandasPackage()
270
+ if not package.install():
271
+ return False
272
+ if "tabulate" in self.requirements:
273
+ try:
274
+ import tabulate as _ # pylint: disable=import-outside-toplevel
275
+ except ModuleNotFoundError:
276
+ package = TabulatePackage()
277
+ if not package.install():
278
+ return False
279
+ if "jinja2" in self.requirements:
280
+ try:
281
+ import jinja2 as _ # pylint: disable=import-outside-toplevel
282
+ except ModuleNotFoundError:
283
+ package = Jinja2Package()
284
+ if not package.install():
285
+ return False
286
+ if "seaborn" in self.requirements:
287
+ try:
288
+ import seaborn as _ # pylint: disable=import-outside-toplevel
289
+ except ModuleNotFoundError:
290
+ package = SeabornPackage()
291
+ if not package.install():
292
+ return False
293
+
294
+ return True
295
+
296
+ def _check_systemd(self) -> bool:
297
+ """Check if the systemd path exists"""
298
+ if not os.path.exists(self.systemd_path):
299
+ print_color(
300
+ f"Systemd path does not exist: {self.systemd_path}",
301
+ "❌",
302
+ )
303
+ return os.path.exists(self.systemd_path)
304
+
305
+ def remove(self) -> bool:
306
+ """Remove the amd-s2idle hook"""
307
+ if self._check_systemd():
308
+ f = "s2idle-hook"
309
+ t = os.path.join(self.systemd_path, f)
310
+ os.remove(t)
311
+ print_color(
312
+ f"Removed {f} from {self.systemd_path}",
313
+ "✅",
314
+ )
315
+ else:
316
+ print_color("Systemd path does not exist, not removing hook", "🚦")
317
+ f = "amd-s2idle"
318
+ d = os.path.join(
319
+ "/",
320
+ "usr",
321
+ "local",
322
+ "share",
323
+ "bash-completion",
324
+ "completions",
325
+ )
326
+ t = os.path.join(d, f)
327
+ if os.path.exists(t):
328
+ os.remove(t)
329
+ print_color(f"Removed {f} from {d}", "✅")
330
+ return True
331
+
332
+ def install(self) -> bool:
333
+ """Install the amd-s2idle hook"""
334
+ import amd_debug # pylint: disable=import-outside-toplevel
335
+
336
+ d = os.path.dirname(amd_debug.__file__)
337
+ if self._check_systemd():
338
+ f = "s2idle-hook"
339
+ s = os.path.join(d, f)
340
+ t = os.path.join(self.systemd_path, f)
341
+ with open(s, "r", encoding="utf-8") as r:
342
+ with open(t, "w", encoding="utf-8") as w:
343
+ for line in r.readlines():
344
+ if 'parser.add_argument("--path"' in line:
345
+ line = line.replace(
346
+ 'default=""',
347
+ f"default=\"{os.path.join(d, '..')}\"",
348
+ )
349
+ w.write(line)
350
+ os.chmod(t, 0o755)
351
+ print_color(
352
+ f"Installed {f} to {self.systemd_path}",
353
+ "✅",
354
+ )
355
+ else:
356
+ print_color("Systemd path does not exist, not installing hook", "🚦")
357
+ f = "amd-s2idle"
358
+ s = os.path.join(d, "bash", f)
359
+ t = os.path.join(
360
+ "/",
361
+ "usr",
362
+ "local",
363
+ "share",
364
+ "bash-completion",
365
+ "completions",
366
+ )
367
+ os.makedirs(t, exist_ok=True)
368
+ shutil.copy(s, t)
369
+ print_color(f"Installed {f} to {t}", "✅")
370
+ return True
371
+
372
+
373
+ def parse_args():
374
+ """Parse command line arguments"""
375
+ parser = argparse.ArgumentParser(
376
+ description="Install dependencies for AMD debug tools",
377
+ )
378
+ parser.add_argument(
379
+ "--tool-debug",
380
+ action="store_true",
381
+ help="Enable tool debug logging",
382
+ )
383
+ return parser.parse_args()
384
+
385
+
386
+ def install_dep_superset() -> bool:
387
+ """Install all python supserset dependencies"""
388
+ args = parse_args()
389
+ tool = Installer(tool_debug=args.tool_debug)
390
+ tool.set_requirements(
391
+ "iasl",
392
+ "ethtool",
393
+ "jinja2",
394
+ "pyudev",
395
+ "packaging",
396
+ "pandas",
397
+ "seaborn",
398
+ "tabulate",
399
+ )
400
+ ret = tool.install_dependencies()
401
+ if ret:
402
+ print_color("All dependencies installed", "✅")
403
+ show_log_info()
404
+ return ret