History

scripts: sphinx-pre-install: move missing logic to a separate class

Better manage dependencies by placing them on a distro-independent class.

Mauro Carvalho Chehab committed 1 year ago

Commit 2cab00f1 parent 272f5e01 file · +138 −113Browse files

Unified · Split

scripts/sphinx-pre-install.py+138−113
@@ -32,7 +32,7 @@ RECOMMENDED_VERSION = parse_version("3.4.3") · 31 unmodified lines
32MIN_PYTHON_VERSION = parse_version("3.7")32MIN_PYTHON_VERSION = parse_version("3.7")
33 33
34 34
35class DepType:35class DepManager:
36 36
37 # Internal types of dependencies.37 # Internal types of dependencies.
38 _SYS_TYPE = 038 _SYS_TYPE = 0
@@ -53,11 +53,18 @@ class DepType: · 14 unmodified lines
53 PYTHON_OPTIONAL = (_PHY_TYPE, False)53 PYTHON_OPTIONAL = (_PHY_TYPE, False)
54 PDF_OPTIONAL = (_PDF_TYPE, True)54 PDF_OPTIONAL = (_PDF_TYPE, True)
55 55
56 def __init__(self, pdf):
57 self.missing = {}
58 self.run = {}
59 self.need = 0
60 self.optional = 0
61 self.pdf = pdf
62
56 @staticmethod63 @staticmethod
57 def name(dtype):64 def name(dtype):
58 if dtype[0] == DepType._SYS_TYPE:65 if dtype[0] == DepManager._SYS_TYPE:
59 msg = "build"66 msg = "build"
60 elif dtype[0] == DepType._PHY_TYPE:67 elif dtype[0] == DepManager._PHY_TYPE:
61 msg = "Python"68 msg = "Python"
62 else:69 else:
63 msg = "PDF"70 msg = "PDF"
@@ -73,11 +80,75 @@ class DepType: · 9 unmodified lines
73 80
74 @staticmethod81 @staticmethod
75 def is_pdf(dtype):82 def is_pdf(dtype):
76 if (dtype[0] == DepType._PDF_TYPE):83 if (dtype[0] == DepManager._PDF_TYPE):
77 return True84 return True
78 85
79 return False86 return False
80 87
88 def add_package(self, package, dtype):
89 is_optional = DepManager.is_optional(dtype)
90 self.missing[package] = dtype
91 if is_optional:
92 self.optional += 1
93 else:
94 self.need += 1
95
96 def del_package(self, package):
97 if package in self.missing:
98 del self.missing[package]
99
100 def clear_deps(self):
101 """
102 Clear dependencies without changing needed/optional.
103
104 This is an ackward way to have a separate section to recommend
105 a package after system main dependencies.
106
107 TODO: rework the logic to prevent needing it
108 """
109
110 self.missing = {}
111
112 def check_missing(self, progs):
113 self.run = {}
114
115 for prog, dtype in sorted(self.missing.items()):
116 # At least on some LTS distros like CentOS 7, texlive doesn't
117 # provide all packages we need. When such distros are
118 # detected, we have to disable PDF output.
119 #
120 # So, we need to ignore the packages that distros would
121 # need for LaTeX to work
122 if DepManager.is_pdf(dtype) and not self.pdf:
123 self.optional -= 1
124 continue
125
126 if not dtype in self.run:
127 self.run[dtype] = []
128
129 self.run[dtype].append(progs.get(prog, prog))
130
131 install = []
132 for dtype in self.run.keys():
133 install += self.run[dtype]
134
135 return " ".join(sorted(set(install)))
136
137 def warn_install(self):
138
139 output_msg = ""
140
141 for dtype in sorted(self.run.keys()):
142 progs = " ".join(sorted(set(self.run[dtype])))
143
144 try:
145 name = DepManager.name(dtype)
146 output_msg += f'{name}:\t{progs}\n'
147 except KeyError:
148 raise KeyError(f"ERROR!!!: invalid dtype for {progs}: {dtype}")
149
150 if output_msg:
151 print(f"\n{output_msg}\n")
81 152
82class SphinxDependencyChecker:153class SphinxDependencyChecker:
83 # List of required texlive packages on Fedora and OpenSuse154 # List of required texlive packages on Fedora and OpenSuse
@@ -120,10 +191,8 @@ class SphinxDependencyChecker: · 36 unmodified lines
120 self.virtualenv = args.virtualenv191 self.virtualenv = args.virtualenv
121 self.version_check = args.version_check192 self.version_check = args.version_check
122 193
123 self.missing = {}194 self.deps = DepManager(self.pdf)
124 195
125 self.need = 0
126 self.optional = 0
127 self.need_symlink = 0196 self.need_symlink = 0
128 self.need_sphinx = 0197 self.need_sphinx = 0
129 self.need_pip = 0198 self.need_pip = 0
@@ -270,64 +339,18 @@ class SphinxDependencyChecker: · 140 unmodified lines
270 # Methods to check if a feature exists339 # Methods to check if a feature exists
271 #340 #
272 341
273 def check_missing(self, progs):
274 run = {}
275
276 for prog, dtype in sorted(self.missing.items()):
277 # At least on some LTS distros like CentOS 7, texlive doesn't
278 # provide all packages we need. When such distros are
279 # detected, we have to disable PDF output.
280 #
281 # So, we need to ignore the packages that distros would
282 # need for LaTeX to work
283 if DepType.is_pdf(dtype) and not self.pdf:
284 self.optional -= 1
285 continue
286
287 if not dtype in run:
288 run[dtype] = []
289
290 run[dtype].append(prog)
291
292 output_msg = ""
293
294 for dtype in sorted(run.keys()):
295 progs = " ".join(run[dtype])
296
297 if self.verbose_warn_install:
298 try:
299 name = DepType.name(dtype)
300 output_msg += f'{name}:\t{progs}\n'
301 except KeyError:
302 raise KeyError(f"ERROR!!!: invalid dtype for {progs}: {dtype}")
303
304 self.install += " " + progs
305
306 if output_msg:
307 print(f"\n{output_msg}\n")
308
309 self.install = self.install.lstrip()
310
311 def add_package(self, package, dtype):
312 is_optional = DepType.is_optional(dtype)
313 self.missing[package] = dtype
314 if is_optional:
315 self.optional += 1
316 else:
317 self.need += 1
318
319 def check_missing_file(self, files, package, dtype):342 def check_missing_file(self, files, package, dtype):
320 for f in files:343 for f in files:
321 if os.path.exists(f):344 if os.path.exists(f):
322 return345 return
323 self.add_package(package, dtype)346 self.deps.add_package(package, dtype)
324 347
325 def check_program(self, prog, dtype):348 def check_program(self, prog, dtype):
326 found = self.which(prog)349 found = self.which(prog)
327 if found:350 if found:
328 return found351 return found
329 352
330 self.add_package(prog, dtype)353 self.deps.add_package(prog, dtype)
331 354
332 return None355 return None
333 356
@@ -340,52 +363,52 @@ class SphinxDependencyChecker: · 6 unmodified lines
340 # add it as a mandatory package, as some parts of the doc builder363 # add it as a mandatory package, as some parts of the doc builder
341 # needs it.364 # needs it.
342 if not self.which("perl"):365 if not self.which("perl"):
343 self.add_package("perl", DepType.SYSTEM_MANDATORY)366 self.deps.add_package("perl", DepManager.SYSTEM_MANDATORY)
344 self.add_package(prog, dtype)367 self.deps.add_package(prog, dtype)
345 return368 return
346 369
347 try:370 try:
348 self.run(["perl", f"-M{prog}", "-e", "1"], check=True)371 self.run(["perl", f"-M{prog}", "-e", "1"], check=True)
349 except subprocess.CalledProcessError:372 except subprocess.CalledProcessError:
350 self.add_package(prog, dtype)373 self.deps.add_package(prog, dtype)
351 374
352 def check_python_module(self, module, is_optional=False):375 def check_python_module(self, module, is_optional=False):
353 if is_optional:376 if is_optional:
354 dtype = DepType.PYTHON_OPTIONAL377 dtype = DepManager.PYTHON_OPTIONAL
355 else:378 else:
356 dtype = DepType.PYTHON_MANDATORY379 dtype = DepManager.PYTHON_MANDATORY
357 380
358 try:381 try:
359 self.run([self.python_cmd, "-c", f"import {module}"], check=True)382 self.run([self.python_cmd, "-c", f"import {module}"], check=True)
360 except subprocess.CalledProcessError:383 except subprocess.CalledProcessError:
361 self.add_package(module, dtype)384 self.deps.add_package(module, dtype)
362 385
363 def check_rpm_missing(self, pkgs, dtype):386 def check_rpm_missing(self, pkgs, dtype):
364 for prog in pkgs:387 for prog in pkgs:
365 try:388 try:
366 self.run(["rpm", "-q", prog], check=True)389 self.run(["rpm", "-q", prog], check=True)
367 except subprocess.CalledProcessError:390 except subprocess.CalledProcessError:
368 self.add_package(prog, dtype)391 self.deps.add_package(prog, dtype)
369 392
370 def check_pacman_missing(self, pkgs, dtype):393 def check_pacman_missing(self, pkgs, dtype):
371 for prog in pkgs:394 for prog in pkgs:
372 try:395 try:
373 self.run(["pacman", "-Q", prog], check=True)396 self.run(["pacman", "-Q", prog], check=True)
374 except subprocess.CalledProcessError:397 except subprocess.CalledProcessError:
375 self.add_package(prog, dtype)398 self.deps.add_package(prog, dtype)
376 399
377 def check_missing_tex(self, is_optional=False):400 def check_missing_tex(self, is_optional=False):
378 if is_optional:401 if is_optional:
379 dtype = DepType.PDF_OPTIONAL402 dtype = DepManager.PDF_OPTIONAL
380 else:403 else:
381 dtype = DepType.PDF_MANDATORY404 dtype = DepManager.PDF_MANDATORY
382 405
383 kpsewhich = self.which("kpsewhich")406 kpsewhich = self.which("kpsewhich")
384 for prog, package in self.texlive.items():407 for prog, package in self.texlive.items():
385 408
386 # If kpsewhich is not there, just add it to deps409 # If kpsewhich is not there, just add it to deps
387 if not kpsewhich:410 if not kpsewhich:
388 self.add_package(package, dtype)411 self.deps.add_package(package, dtype)
389 continue412 continue
390 413
391 # Check if the package is needed414 # Check if the package is needed
@@ -396,11 +419,11 @@ class SphinxDependencyChecker: · 4 unmodified lines
396 419
397 # Didn't find. Add it420 # Didn't find. Add it
398 if not result.stdout.strip():421 if not result.stdout.strip():
399 self.add_package(package, dtype)422 self.deps.add_package(package, dtype)
400 423
401 except subprocess.CalledProcessError:424 except subprocess.CalledProcessError:
402 # kpsewhich returned an error. Add it, just in case425 # kpsewhich returned an error. Add it, just in case
403 self.add_package(package, dtype)426 self.deps.add_package(package, dtype)
404 427
405 def get_sphinx_fname(self):428 def get_sphinx_fname(self):
406 if "SPHINXBUILD" in os.environ:429 if "SPHINXBUILD" in os.environ:
@@ -478,6 +501,17 @@ class SphinxDependencyChecker: · 71 unmodified lines
478 return f.read().strip()501 return f.read().strip()
479 return ""502 return ""
480 503
504 def check_missing(self, progs):
505 self.install += self.deps.check_missing(progs)
506 if self.verbose_warn_install:
507 self.deps.warn_install()
508
509 if not self.deps.need and not self.deps.optional:
510 return False
511
512 return True
513
514
481 #515 #
482 # Distro-specific hints methods516 # Distro-specific hints methods
483 #517 #
@@ -511,13 +545,11 @@ class SphinxDependencyChecker: · 27 unmodified lines
511 }545 }
512 546
513 for package, files in pdf_pkgs.items():547 for package, files in pdf_pkgs.items():
514 self.check_missing_file(files, package, DepType.PDF_MANDATORY)548 self.check_missing_file(files, package, DepManager.PDF_MANDATORY)
515
516 self.check_program("dvipng", DepType.PDF_MANDATORY)
517 549
518 self.check_missing(progs)550 self.check_program("dvipng", DepManager.PDF_MANDATORY)
519 551
520 if not self.need and not self.optional:552 if self.check_missing(progs):
521 return553 return
522 554
523 if self.verbose_warn_install:555 if self.verbose_warn_install:
@@ -575,7 +607,7 @@ class SphinxDependencyChecker: · 51 unmodified lines
575 # RHEL 8 uses Python 3.6, which is not compatible with607 # RHEL 8 uses Python 3.6, which is not compatible with
576 # the build system anymore. Suggest Python 3.11608 # the build system anymore. Suggest Python 3.11
577 if rel == 8:609 if rel == 8:
578 self.add_package("python39", DepType.SYSTEM_MANDATORY)610 self.deps.add_package("python39", DepManager.SYSTEM_MANDATORY)
579 self.recommend_python = True611 self.recommend_python = True
580 612
581 if self.first_hint:613 if self.first_hint:
@@ -597,22 +629,18 @@ class SphinxDependencyChecker: · 15 unmodified lines
597 "/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc",629 "/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc",
598 ]630 ]
599 631
600 self.check_missing_file(pdf_pkgs, noto_sans_redhat, DepType.PDF_MANDATORY)632 self.check_missing_file(pdf_pkgs, noto_sans_redhat, DepManager.PDF_MANDATORY)
601 633
602 self.check_rpm_missing(fedora_tex_pkgs, DepType.PDF_MANDATORY)634 self.check_rpm_missing(fedora_tex_pkgs, DepManager.PDF_MANDATORY)
603 635
604 self.check_missing_tex(DepType.PDF_MANDATORY)636 self.check_missing_tex(DepManager.PDF_MANDATORY)
605 637
606 # There's no texlive-ctex on RHEL 8 repositories. This will638 # There's no texlive-ctex on RHEL 8 repositories. This will
607 # likely affect CJK pdf build only.639 # likely affect CJK pdf build only.
608 if not fedora and rel == 8:640 if not fedora and rel == 8:
609 if "texlive-ctex" in self.missing:641 self.deps.del_package("texlive-ctex")
610 del self.missing["texlive-ctex"]
611
612
613 self.check_missing(progs)
614 642
615 if not self.need and not self.optional:643 if self.check_missing(progs):
616 return644 return
617 645
618 if self.verbose_warn_install:646 if self.verbose_warn_install:
@@ -659,7 +687,7 @@ class SphinxDependencyChecker: · 40 unmodified lines
659 if rel == 15:687 if rel == 15:
660 if not self.which(self.python_cmd):688 if not self.which(self.python_cmd):
661 self.recommend_python = True689 self.recommend_python = True
662 self.add_package(self.python_cmd, DepType.SYSTEM_MANDATORY)690 self.deps.add_package(self.python_cmd, DepManager.SYSTEM_MANDATORY)
663 691
664 progs.update({692 progs.update({
665 "python-sphinx": "python311-Sphinx",693 "python-sphinx": "python311-Sphinx",
@@ -681,12 +709,11 @@ class SphinxDependencyChecker: · 15 unmodified lines
681 # "Noto Sans CJK SC" on openSUSE709 # "Noto Sans CJK SC" on openSUSE
682 710
683 if self.pdf:711 if self.pdf:
684 self.check_rpm_missing(suse_tex_pkgs, DepType.PDF_MANDATORY)712 self.check_rpm_missing(suse_tex_pkgs, DepManager.PDF_MANDATORY)
685 if self.pdf:713 if self.pdf:
686 self.check_missing_tex()714 self.check_missing_tex()
687 self.check_missing(progs)
688 715
689 if not self.need and not self.optional:716 if self.check_missing(progs):
690 return717 return
691 718
692 if self.verbose_warn_install:719 if self.verbose_warn_install:
@@ -730,13 +757,12 @@ class SphinxDependencyChecker: · 37 unmodified lines
730 "/usr/share/fonts/TTF/NotoSans-Regular.ttf",757 "/usr/share/fonts/TTF/NotoSans-Regular.ttf",
731 ]758 ]
732 759
733 self.check_missing_file(pdf_pkgs, noto_sans, DepType.PDF_MANDATORY)760 self.check_missing_file(pdf_pkgs, noto_sans, DepManager.PDF_MANDATORY)
734 self.check_rpm_missing(tex_pkgs, DepType.PDF_MANDATORY)761 self.check_rpm_missing(tex_pkgs, DepManager.PDF_MANDATORY)
735
736 self.check_missing(progs)
737 762
738 if not self.need and not self.optional:763 if self.check_missing(progs):
739 return764 return
765
740 if self.verbose_warn_install:766 if self.verbose_warn_install:
741 print("You should run:")767 print("You should run:")
742 print(f"\n\tsudo {packager_cmd} {self.install}")768 print(f"\n\tsudo {packager_cmd} {self.install}")
@@ -759,7 +785,7 @@ class SphinxDependencyChecker: · 16 unmodified lines
759 ]785 ]
760 786
761 if self.pdf:787 if self.pdf:
762 self.check_pacman_missing(archlinux_tex_pkgs, DepType.PDF_MANDATORY)788 self.check_pacman_missing(archlinux_tex_pkgs, DepManager.PDF_MANDATORY)
763 789
764 self.check_missing_file(790 self.check_missing_file(
765 ["/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc"],791 ["/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc"],
@@ -767,10 +793,9 @@ class SphinxDependencyChecker: · 1 unmodified line
767 2,793 2,
768 )794 )
769 795
770 self.check_missing(progs)796 if self.check_missing(progs):
771
772 if not self.need and not self.optional:
773 return797 return
798
774 if self.verbose_warn_install:799 if self.verbose_warn_install:
775 print("You should run:")800 print("You should run:")
776 print(f"\n\tsudo pacman -S {self.install}")801 print(f"\n\tsudo pacman -S {self.install}")
@@ -797,11 +822,9 @@ class SphinxDependencyChecker: · 20 unmodified lines
797 ],822 ],
798 }823 }
799 for package, files in pdf_pkgs.items():824 for package, files in pdf_pkgs.items():
800 self.check_missing_file(files, package, DepType.PDF_MANDATORY)825 self.check_missing_file(files, package, DepManager.PDF_MANDATORY)
801
802 self.check_missing(progs)
803 826
804 if not self.need and not self.optional:827 if self.check_missing(progs):
805 return828 return
806 829
807 if self.verbose_warn_install:830 if self.verbose_warn_install:
@@ -1039,16 +1062,18 @@ class SphinxDependencyChecker: · 231 unmodified lines
1039 1062
1040 print("\n2) As a package with:")1063 print("\n2) As a package with:")
1041 1064
1042 old_need = self.need1065 old_need = self.deps.need
1043 old_optional = self.optional1066 old_optional = self.deps.optional
1044 self.missing = {}1067
1045 self.pdf = False1068 self.pdf = False
1046 self.optional = 01069 self.optional = 0
1047 self.install = ""1070 self.install = ""
1048 old_verbose = self.verbose_warn_install1071 old_verbose = self.verbose_warn_install
1049 self.verbose_warn_install = 01072 self.verbose_warn_install = 0
1050 1073
1051 self.add_package("python-sphinx", DepType.PYTHON_MANDATORY)1074 self.deps.clear_deps()
1075
1076 self.deps.add_package("python-sphinx", DepManager.PYTHON_MANDATORY)
1052 1077
1053 self.check_distros()1078 self.check_distros()
1054 1079
@@ -1111,7 +1136,7 @@ class SphinxDependencyChecker: · 56 unmodified lines
1111 print("\n")1136 print("\n")
1112 else:1137 else:
1113 if self.need_sphinx:1138 if self.need_sphinx:
1114 self.need += 11139 self.deps.need += 1
1115 1140
1116 # Suggest newer versions if current ones are too old1141 # Suggest newer versions if current ones are too old
1117 if self.latest_avail_ver and self.latest_avail_ver >= self.min_version:1142 if self.latest_avail_ver and self.latest_avail_ver >= self.min_version:
@@ -1190,20 +1215,20 @@ class SphinxDependencyChecker: · 72 unmodified lines
1190 self.check_python_module("ensurepip")1215 self.check_python_module("ensurepip")
1191 1216
1192 # Check for needed programs/tools1217 # Check for needed programs/tools
1193 self.check_perl_module("Pod::Usage", DepType.SYSTEM_MANDATORY)1218 self.check_perl_module("Pod::Usage", DepManager.SYSTEM_MANDATORY)
1194 1219
1195 self.check_program("make", DepType.SYSTEM_MANDATORY)1220 self.check_program("make", DepManager.SYSTEM_MANDATORY)
1196 self.check_program("gcc", DepType.SYSTEM_MANDATORY)1221 self.check_program("gcc", DepManager.SYSTEM_MANDATORY)
1197 1222
1198 self.check_program("dot", DepType.SYSTEM_OPTIONAL)1223 self.check_program("dot", DepManager.SYSTEM_OPTIONAL)
1199 self.check_program("convert", DepType.SYSTEM_OPTIONAL)1224 self.check_program("convert", DepManager.SYSTEM_OPTIONAL)
1200 1225
1201 self.check_python_module("yaml")1226 self.check_python_module("yaml")
1202 1227
1203 if self.pdf:1228 if self.pdf:
1204 self.check_program("xelatex", DepType.PDF_MANDATORY)1229 self.check_program("xelatex", DepManager.PDF_MANDATORY)
1205 self.check_program("rsvg-convert", DepType.PDF_MANDATORY)1230 self.check_program("rsvg-convert", DepManager.PDF_MANDATORY)
1206 self.check_program("latexmk", DepType.PDF_MANDATORY)1231 self.check_program("latexmk", DepManager.PDF_MANDATORY)
1207 1232
1208 # Do distro-specific checks and output distro-install commands1233 # Do distro-specific checks and output distro-install commands
1209 self.check_distros()1234 self.check_distros()