xref: /linux/tools/docs/sphinx-pre-install (revision abd61d1ff8f0ea4cb099a1f3d5015dea7c8471cf)
1*abd61d1fSMauro Carvalho Chehab#!/usr/bin/env python3
2*abd61d1fSMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0-or-later
3*abd61d1fSMauro Carvalho Chehab# Copyright (c) 2017-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
4*abd61d1fSMauro Carvalho Chehab#
5*abd61d1fSMauro Carvalho Chehab# pylint: disable=C0103,C0114,C0115,C0116,C0301,C0302
6*abd61d1fSMauro Carvalho Chehab# pylint: disable=R0902,R0904,R0911,R0912,R0914,R0915,R1705,R1710,E1121
7*abd61d1fSMauro Carvalho Chehab
8*abd61d1fSMauro Carvalho Chehab# Note: this script requires at least Python 3.6 to run.
9*abd61d1fSMauro Carvalho Chehab# Don't add changes not compatible with it, it is meant to report
10*abd61d1fSMauro Carvalho Chehab# incompatible python versions.
11*abd61d1fSMauro Carvalho Chehab
12*abd61d1fSMauro Carvalho Chehab"""
13*abd61d1fSMauro Carvalho ChehabDependency checker for Sphinx documentation Kernel build.
14*abd61d1fSMauro Carvalho Chehab
15*abd61d1fSMauro Carvalho ChehabThis module provides tools to check for all required dependencies needed to
16*abd61d1fSMauro Carvalho Chehabbuild documentation using Sphinx, including system packages, Python modules
17*abd61d1fSMauro Carvalho Chehaband LaTeX packages for PDF generation.
18*abd61d1fSMauro Carvalho Chehab
19*abd61d1fSMauro Carvalho ChehabIt detect packages for a subset of Linux distributions used by Kernel
20*abd61d1fSMauro Carvalho Chehabmaintainers, showing hints and missing dependencies.
21*abd61d1fSMauro Carvalho Chehab
22*abd61d1fSMauro Carvalho ChehabThe main class SphinxDependencyChecker handles the dependency checking logic
23*abd61d1fSMauro Carvalho Chehaband provides recommendations for installing missing packages. It supports both
24*abd61d1fSMauro Carvalho Chehabsystem package installations and  Python virtual environments. By default,
25*abd61d1fSMauro Carvalho Chehabsystem pacage install is recommended.
26*abd61d1fSMauro Carvalho Chehab"""
27*abd61d1fSMauro Carvalho Chehab
28*abd61d1fSMauro Carvalho Chehabimport argparse
29*abd61d1fSMauro Carvalho Chehabimport os
30*abd61d1fSMauro Carvalho Chehabimport re
31*abd61d1fSMauro Carvalho Chehabimport subprocess
32*abd61d1fSMauro Carvalho Chehabimport sys
33*abd61d1fSMauro Carvalho Chehabfrom glob import glob
34*abd61d1fSMauro Carvalho Chehab
35*abd61d1fSMauro Carvalho Chehab
36*abd61d1fSMauro Carvalho Chehabdef parse_version(version):
37*abd61d1fSMauro Carvalho Chehab    """Convert a major.minor.patch version into a tuple"""
38*abd61d1fSMauro Carvalho Chehab    return tuple(int(x) for x in version.split("."))
39*abd61d1fSMauro Carvalho Chehab
40*abd61d1fSMauro Carvalho Chehab
41*abd61d1fSMauro Carvalho Chehabdef ver_str(version):
42*abd61d1fSMauro Carvalho Chehab    """Returns a version tuple as major.minor.patch"""
43*abd61d1fSMauro Carvalho Chehab
44*abd61d1fSMauro Carvalho Chehab    return ".".join([str(x) for x in version])
45*abd61d1fSMauro Carvalho Chehab
46*abd61d1fSMauro Carvalho Chehab
47*abd61d1fSMauro Carvalho ChehabRECOMMENDED_VERSION = parse_version("3.4.3")
48*abd61d1fSMauro Carvalho ChehabMIN_PYTHON_VERSION = parse_version("3.7")
49*abd61d1fSMauro Carvalho Chehab
50*abd61d1fSMauro Carvalho Chehab
51*abd61d1fSMauro Carvalho Chehabclass DepManager:
52*abd61d1fSMauro Carvalho Chehab    """
53*abd61d1fSMauro Carvalho Chehab    Manage package dependencies. There are three types of dependencies:
54*abd61d1fSMauro Carvalho Chehab
55*abd61d1fSMauro Carvalho Chehab    - System: dependencies required for docs build;
56*abd61d1fSMauro Carvalho Chehab    - Python: python dependencies for a native distro Sphinx install;
57*abd61d1fSMauro Carvalho Chehab    - PDF: dependencies needed by PDF builds.
58*abd61d1fSMauro Carvalho Chehab
59*abd61d1fSMauro Carvalho Chehab    Each dependency can be mandatory or optional. Not installing an optional
60*abd61d1fSMauro Carvalho Chehab    dependency won't break the build, but will cause degradation at the
61*abd61d1fSMauro Carvalho Chehab    docs output.
62*abd61d1fSMauro Carvalho Chehab    """
63*abd61d1fSMauro Carvalho Chehab
64*abd61d1fSMauro Carvalho Chehab    # Internal types of dependencies. Don't use them outside DepManager class.
65*abd61d1fSMauro Carvalho Chehab    _SYS_TYPE = 0
66*abd61d1fSMauro Carvalho Chehab    _PHY_TYPE = 1
67*abd61d1fSMauro Carvalho Chehab    _PDF_TYPE = 2
68*abd61d1fSMauro Carvalho Chehab
69*abd61d1fSMauro Carvalho Chehab    # Dependencies visible outside the class.
70*abd61d1fSMauro Carvalho Chehab    # The keys are tuple with: (type, is_mandatory flag).
71*abd61d1fSMauro Carvalho Chehab    #
72*abd61d1fSMauro Carvalho Chehab    # Currently we're not using all optional dep types. Yet, we'll keep all
73*abd61d1fSMauro Carvalho Chehab    # possible combinations here. They're not many, and that makes easier
74*abd61d1fSMauro Carvalho Chehab    # if later needed and for the name() method below
75*abd61d1fSMauro Carvalho Chehab
76*abd61d1fSMauro Carvalho Chehab    SYSTEM_MANDATORY = (_SYS_TYPE, True)
77*abd61d1fSMauro Carvalho Chehab    PYTHON_MANDATORY = (_PHY_TYPE, True)
78*abd61d1fSMauro Carvalho Chehab    PDF_MANDATORY = (_PDF_TYPE, True)
79*abd61d1fSMauro Carvalho Chehab
80*abd61d1fSMauro Carvalho Chehab    SYSTEM_OPTIONAL = (_SYS_TYPE, False)
81*abd61d1fSMauro Carvalho Chehab    PYTHON_OPTIONAL = (_PHY_TYPE, False)
82*abd61d1fSMauro Carvalho Chehab    PDF_OPTIONAL = (_PDF_TYPE, True)
83*abd61d1fSMauro Carvalho Chehab
84*abd61d1fSMauro Carvalho Chehab    def __init__(self, pdf):
85*abd61d1fSMauro Carvalho Chehab        """
86*abd61d1fSMauro Carvalho Chehab        Initialize internal vars:
87*abd61d1fSMauro Carvalho Chehab
88*abd61d1fSMauro Carvalho Chehab        - missing: missing dependencies list, containing a distro-independent
89*abd61d1fSMauro Carvalho Chehab                   name for a missing dependency and its type.
90*abd61d1fSMauro Carvalho Chehab        - missing_pkg: ancillary dict containing missing dependencies in
91*abd61d1fSMauro Carvalho Chehab                       distro namespace, organized by type.
92*abd61d1fSMauro Carvalho Chehab        - need: total number of needed dependencies. Never cleaned.
93*abd61d1fSMauro Carvalho Chehab        - optional: total number of optional dependencies. Never cleaned.
94*abd61d1fSMauro Carvalho Chehab        - pdf: Is PDF support enabled?
95*abd61d1fSMauro Carvalho Chehab        """
96*abd61d1fSMauro Carvalho Chehab        self.missing = {}
97*abd61d1fSMauro Carvalho Chehab        self.missing_pkg = {}
98*abd61d1fSMauro Carvalho Chehab        self.need = 0
99*abd61d1fSMauro Carvalho Chehab        self.optional = 0
100*abd61d1fSMauro Carvalho Chehab        self.pdf = pdf
101*abd61d1fSMauro Carvalho Chehab
102*abd61d1fSMauro Carvalho Chehab    @staticmethod
103*abd61d1fSMauro Carvalho Chehab    def name(dtype):
104*abd61d1fSMauro Carvalho Chehab        """
105*abd61d1fSMauro Carvalho Chehab        Ancillary routine to output a warn/error message reporting
106*abd61d1fSMauro Carvalho Chehab        missing dependencies.
107*abd61d1fSMauro Carvalho Chehab        """
108*abd61d1fSMauro Carvalho Chehab        if dtype[0] == DepManager._SYS_TYPE:
109*abd61d1fSMauro Carvalho Chehab            msg = "build"
110*abd61d1fSMauro Carvalho Chehab        elif dtype[0] == DepManager._PHY_TYPE:
111*abd61d1fSMauro Carvalho Chehab            msg = "Python"
112*abd61d1fSMauro Carvalho Chehab        else:
113*abd61d1fSMauro Carvalho Chehab            msg = "PDF"
114*abd61d1fSMauro Carvalho Chehab
115*abd61d1fSMauro Carvalho Chehab        if dtype[1]:
116*abd61d1fSMauro Carvalho Chehab            return f"ERROR: {msg} mandatory deps missing"
117*abd61d1fSMauro Carvalho Chehab        else:
118*abd61d1fSMauro Carvalho Chehab            return f"Warning: {msg} optional deps missing"
119*abd61d1fSMauro Carvalho Chehab
120*abd61d1fSMauro Carvalho Chehab    @staticmethod
121*abd61d1fSMauro Carvalho Chehab    def is_optional(dtype):
122*abd61d1fSMauro Carvalho Chehab        """Ancillary routine to report if a dependency is optional"""
123*abd61d1fSMauro Carvalho Chehab        return not dtype[1]
124*abd61d1fSMauro Carvalho Chehab
125*abd61d1fSMauro Carvalho Chehab    @staticmethod
126*abd61d1fSMauro Carvalho Chehab    def is_pdf(dtype):
127*abd61d1fSMauro Carvalho Chehab        """Ancillary routine to report if a dependency is for PDF generation"""
128*abd61d1fSMauro Carvalho Chehab        if dtype[0] == DepManager._PDF_TYPE:
129*abd61d1fSMauro Carvalho Chehab            return True
130*abd61d1fSMauro Carvalho Chehab
131*abd61d1fSMauro Carvalho Chehab        return False
132*abd61d1fSMauro Carvalho Chehab
133*abd61d1fSMauro Carvalho Chehab    def add_package(self, package, dtype):
134*abd61d1fSMauro Carvalho Chehab        """
135*abd61d1fSMauro Carvalho Chehab        Add a package at the self.missing() dictionary.
136*abd61d1fSMauro Carvalho Chehab        Doesn't update missing_pkg.
137*abd61d1fSMauro Carvalho Chehab        """
138*abd61d1fSMauro Carvalho Chehab        is_optional = DepManager.is_optional(dtype)
139*abd61d1fSMauro Carvalho Chehab        self.missing[package] = dtype
140*abd61d1fSMauro Carvalho Chehab        if is_optional:
141*abd61d1fSMauro Carvalho Chehab            self.optional += 1
142*abd61d1fSMauro Carvalho Chehab        else:
143*abd61d1fSMauro Carvalho Chehab            self.need += 1
144*abd61d1fSMauro Carvalho Chehab
145*abd61d1fSMauro Carvalho Chehab    def del_package(self, package):
146*abd61d1fSMauro Carvalho Chehab        """
147*abd61d1fSMauro Carvalho Chehab        Remove a package at the self.missing() dictionary.
148*abd61d1fSMauro Carvalho Chehab        Doesn't update missing_pkg.
149*abd61d1fSMauro Carvalho Chehab        """
150*abd61d1fSMauro Carvalho Chehab        if package in self.missing:
151*abd61d1fSMauro Carvalho Chehab            del self.missing[package]
152*abd61d1fSMauro Carvalho Chehab
153*abd61d1fSMauro Carvalho Chehab    def clear_deps(self):
154*abd61d1fSMauro Carvalho Chehab        """
155*abd61d1fSMauro Carvalho Chehab        Clear dependencies without changing needed/optional.
156*abd61d1fSMauro Carvalho Chehab
157*abd61d1fSMauro Carvalho Chehab        This is an ackward way to have a separate section to recommend
158*abd61d1fSMauro Carvalho Chehab        a package after system main dependencies.
159*abd61d1fSMauro Carvalho Chehab
160*abd61d1fSMauro Carvalho Chehab        TODO: rework the logic to prevent needing it.
161*abd61d1fSMauro Carvalho Chehab        """
162*abd61d1fSMauro Carvalho Chehab
163*abd61d1fSMauro Carvalho Chehab        self.missing = {}
164*abd61d1fSMauro Carvalho Chehab        self.missing_pkg = {}
165*abd61d1fSMauro Carvalho Chehab
166*abd61d1fSMauro Carvalho Chehab    def check_missing(self, progs):
167*abd61d1fSMauro Carvalho Chehab        """
168*abd61d1fSMauro Carvalho Chehab        Update self.missing_pkg, using progs dict to convert from the
169*abd61d1fSMauro Carvalho Chehab        agnostic package name to distro-specific one.
170*abd61d1fSMauro Carvalho Chehab
171*abd61d1fSMauro Carvalho Chehab        Returns an string with the packages to be installed, sorted and
172*abd61d1fSMauro Carvalho Chehab        with eventual duplicates removed.
173*abd61d1fSMauro Carvalho Chehab        """
174*abd61d1fSMauro Carvalho Chehab
175*abd61d1fSMauro Carvalho Chehab        self.missing_pkg = {}
176*abd61d1fSMauro Carvalho Chehab
177*abd61d1fSMauro Carvalho Chehab        for prog, dtype in sorted(self.missing.items()):
178*abd61d1fSMauro Carvalho Chehab            # At least on some LTS distros like CentOS 7, texlive doesn't
179*abd61d1fSMauro Carvalho Chehab            # provide all packages we need. When such distros are
180*abd61d1fSMauro Carvalho Chehab            # detected, we have to disable PDF output.
181*abd61d1fSMauro Carvalho Chehab            #
182*abd61d1fSMauro Carvalho Chehab            # So, we need to ignore the packages that distros would
183*abd61d1fSMauro Carvalho Chehab            # need for LaTeX to work
184*abd61d1fSMauro Carvalho Chehab            if DepManager.is_pdf(dtype) and not self.pdf:
185*abd61d1fSMauro Carvalho Chehab                self.optional -= 1
186*abd61d1fSMauro Carvalho Chehab                continue
187*abd61d1fSMauro Carvalho Chehab
188*abd61d1fSMauro Carvalho Chehab            if not dtype in self.missing_pkg:
189*abd61d1fSMauro Carvalho Chehab                self.missing_pkg[dtype] = []
190*abd61d1fSMauro Carvalho Chehab
191*abd61d1fSMauro Carvalho Chehab            self.missing_pkg[dtype].append(progs.get(prog, prog))
192*abd61d1fSMauro Carvalho Chehab
193*abd61d1fSMauro Carvalho Chehab        install = []
194*abd61d1fSMauro Carvalho Chehab        for dtype, pkgs in self.missing_pkg.items():
195*abd61d1fSMauro Carvalho Chehab            install += pkgs
196*abd61d1fSMauro Carvalho Chehab
197*abd61d1fSMauro Carvalho Chehab        return " ".join(sorted(set(install)))
198*abd61d1fSMauro Carvalho Chehab
199*abd61d1fSMauro Carvalho Chehab    def warn_install(self):
200*abd61d1fSMauro Carvalho Chehab        """
201*abd61d1fSMauro Carvalho Chehab        Emit warnings/errors related to missing packages.
202*abd61d1fSMauro Carvalho Chehab        """
203*abd61d1fSMauro Carvalho Chehab
204*abd61d1fSMauro Carvalho Chehab        output_msg = ""
205*abd61d1fSMauro Carvalho Chehab
206*abd61d1fSMauro Carvalho Chehab        for dtype in sorted(self.missing_pkg.keys()):
207*abd61d1fSMauro Carvalho Chehab            progs = " ".join(sorted(set(self.missing_pkg[dtype])))
208*abd61d1fSMauro Carvalho Chehab
209*abd61d1fSMauro Carvalho Chehab            try:
210*abd61d1fSMauro Carvalho Chehab                name = DepManager.name(dtype)
211*abd61d1fSMauro Carvalho Chehab                output_msg += f'{name}:\t{progs}\n'
212*abd61d1fSMauro Carvalho Chehab            except KeyError:
213*abd61d1fSMauro Carvalho Chehab                raise KeyError(f"ERROR!!!: invalid dtype for {progs}: {dtype}")
214*abd61d1fSMauro Carvalho Chehab
215*abd61d1fSMauro Carvalho Chehab        if output_msg:
216*abd61d1fSMauro Carvalho Chehab            print(f"\n{output_msg}")
217*abd61d1fSMauro Carvalho Chehab
218*abd61d1fSMauro Carvalho Chehabclass AncillaryMethods:
219*abd61d1fSMauro Carvalho Chehab    """
220*abd61d1fSMauro Carvalho Chehab    Ancillary methods that checks for missing dependencies for different
221*abd61d1fSMauro Carvalho Chehab    types of types, like binaries, python modules, rpm deps, etc.
222*abd61d1fSMauro Carvalho Chehab    """
223*abd61d1fSMauro Carvalho Chehab
224*abd61d1fSMauro Carvalho Chehab    @staticmethod
225*abd61d1fSMauro Carvalho Chehab    def which(prog):
226*abd61d1fSMauro Carvalho Chehab        """
227*abd61d1fSMauro Carvalho Chehab        Our own implementation of which(). We could instead use
228*abd61d1fSMauro Carvalho Chehab        shutil.which(), but this function is simple enough.
229*abd61d1fSMauro Carvalho Chehab        Probably faster to use this implementation than to import shutil.
230*abd61d1fSMauro Carvalho Chehab        """
231*abd61d1fSMauro Carvalho Chehab        for path in os.environ.get("PATH", "").split(":"):
232*abd61d1fSMauro Carvalho Chehab            full_path = os.path.join(path, prog)
233*abd61d1fSMauro Carvalho Chehab            if os.access(full_path, os.X_OK):
234*abd61d1fSMauro Carvalho Chehab                return full_path
235*abd61d1fSMauro Carvalho Chehab
236*abd61d1fSMauro Carvalho Chehab        return None
237*abd61d1fSMauro Carvalho Chehab
238*abd61d1fSMauro Carvalho Chehab    @staticmethod
239*abd61d1fSMauro Carvalho Chehab    def get_python_version(cmd):
240*abd61d1fSMauro Carvalho Chehab        """
241*abd61d1fSMauro Carvalho Chehab        Get python version from a Python binary. As we need to detect if
242*abd61d1fSMauro Carvalho Chehab        are out there newer python binaries, we can't rely on sys.release here.
243*abd61d1fSMauro Carvalho Chehab        """
244*abd61d1fSMauro Carvalho Chehab
245*abd61d1fSMauro Carvalho Chehab        result = SphinxDependencyChecker.run([cmd, "--version"],
246*abd61d1fSMauro Carvalho Chehab                                            capture_output=True, text=True)
247*abd61d1fSMauro Carvalho Chehab        version = result.stdout.strip()
248*abd61d1fSMauro Carvalho Chehab
249*abd61d1fSMauro Carvalho Chehab        match = re.search(r"(\d+\.\d+\.\d+)", version)
250*abd61d1fSMauro Carvalho Chehab        if match:
251*abd61d1fSMauro Carvalho Chehab            return parse_version(match.group(1))
252*abd61d1fSMauro Carvalho Chehab
253*abd61d1fSMauro Carvalho Chehab        print(f"Can't parse version {version}")
254*abd61d1fSMauro Carvalho Chehab        return (0, 0, 0)
255*abd61d1fSMauro Carvalho Chehab
256*abd61d1fSMauro Carvalho Chehab    @staticmethod
257*abd61d1fSMauro Carvalho Chehab    def find_python():
258*abd61d1fSMauro Carvalho Chehab        """
259*abd61d1fSMauro Carvalho Chehab        Detect if are out there any python 3.xy version newer than the
260*abd61d1fSMauro Carvalho Chehab        current one.
261*abd61d1fSMauro Carvalho Chehab
262*abd61d1fSMauro Carvalho Chehab        Note: this routine is limited to up to 2 digits for python3. We
263*abd61d1fSMauro Carvalho Chehab        may need to update it one day, hopefully on a distant future.
264*abd61d1fSMauro Carvalho Chehab        """
265*abd61d1fSMauro Carvalho Chehab        patterns = [
266*abd61d1fSMauro Carvalho Chehab            "python3.[0-9]",
267*abd61d1fSMauro Carvalho Chehab            "python3.[0-9][0-9]",
268*abd61d1fSMauro Carvalho Chehab        ]
269*abd61d1fSMauro Carvalho Chehab
270*abd61d1fSMauro Carvalho Chehab        # Seek for a python binary newer than MIN_PYTHON_VERSION
271*abd61d1fSMauro Carvalho Chehab        for path in os.getenv("PATH", "").split(":"):
272*abd61d1fSMauro Carvalho Chehab            for pattern in patterns:
273*abd61d1fSMauro Carvalho Chehab                for cmd in glob(os.path.join(path, pattern)):
274*abd61d1fSMauro Carvalho Chehab                    if os.path.isfile(cmd) and os.access(cmd, os.X_OK):
275*abd61d1fSMauro Carvalho Chehab                        version = SphinxDependencyChecker.get_python_version(cmd)
276*abd61d1fSMauro Carvalho Chehab                        if version >= MIN_PYTHON_VERSION:
277*abd61d1fSMauro Carvalho Chehab                            return cmd
278*abd61d1fSMauro Carvalho Chehab
279*abd61d1fSMauro Carvalho Chehab    @staticmethod
280*abd61d1fSMauro Carvalho Chehab    def check_python():
281*abd61d1fSMauro Carvalho Chehab        """
282*abd61d1fSMauro Carvalho Chehab        Check if the current python binary satisfies our minimal requirement
283*abd61d1fSMauro Carvalho Chehab        for Sphinx build. If not, re-run with a newer version if found.
284*abd61d1fSMauro Carvalho Chehab        """
285*abd61d1fSMauro Carvalho Chehab        cur_ver = sys.version_info[:3]
286*abd61d1fSMauro Carvalho Chehab        if cur_ver >= MIN_PYTHON_VERSION:
287*abd61d1fSMauro Carvalho Chehab            ver = ver_str(cur_ver)
288*abd61d1fSMauro Carvalho Chehab            print(f"Python version: {ver}")
289*abd61d1fSMauro Carvalho Chehab
290*abd61d1fSMauro Carvalho Chehab            # This could be useful for debugging purposes
291*abd61d1fSMauro Carvalho Chehab            if SphinxDependencyChecker.which("docutils"):
292*abd61d1fSMauro Carvalho Chehab                result = SphinxDependencyChecker.run(["docutils", "--version"],
293*abd61d1fSMauro Carvalho Chehab                                                    capture_output=True, text=True)
294*abd61d1fSMauro Carvalho Chehab                ver = result.stdout.strip()
295*abd61d1fSMauro Carvalho Chehab                match = re.search(r"(\d+\.\d+\.\d+)", ver)
296*abd61d1fSMauro Carvalho Chehab                if match:
297*abd61d1fSMauro Carvalho Chehab                    ver = match.group(1)
298*abd61d1fSMauro Carvalho Chehab
299*abd61d1fSMauro Carvalho Chehab                print(f"Docutils version: {ver}")
300*abd61d1fSMauro Carvalho Chehab
301*abd61d1fSMauro Carvalho Chehab            return
302*abd61d1fSMauro Carvalho Chehab
303*abd61d1fSMauro Carvalho Chehab        python_ver = ver_str(cur_ver)
304*abd61d1fSMauro Carvalho Chehab
305*abd61d1fSMauro Carvalho Chehab        new_python_cmd = SphinxDependencyChecker.find_python()
306*abd61d1fSMauro Carvalho Chehab        if not new_python_cmd:
307*abd61d1fSMauro Carvalho Chehab            print(f"ERROR: Python version {python_ver} is not spported anymore\n")
308*abd61d1fSMauro Carvalho Chehab            print("       Can't find a new version. This script may fail")
309*abd61d1fSMauro Carvalho Chehab            return
310*abd61d1fSMauro Carvalho Chehab
311*abd61d1fSMauro Carvalho Chehab        # Restart script using the newer version
312*abd61d1fSMauro Carvalho Chehab        script_path = os.path.abspath(sys.argv[0])
313*abd61d1fSMauro Carvalho Chehab        args = [new_python_cmd, script_path] + sys.argv[1:]
314*abd61d1fSMauro Carvalho Chehab
315*abd61d1fSMauro Carvalho Chehab        print(f"Python {python_ver} not supported. Changing to {new_python_cmd}")
316*abd61d1fSMauro Carvalho Chehab
317*abd61d1fSMauro Carvalho Chehab        try:
318*abd61d1fSMauro Carvalho Chehab            os.execv(new_python_cmd, args)
319*abd61d1fSMauro Carvalho Chehab        except OSError as e:
320*abd61d1fSMauro Carvalho Chehab            sys.exit(f"Failed to restart with {new_python_cmd}: {e}")
321*abd61d1fSMauro Carvalho Chehab
322*abd61d1fSMauro Carvalho Chehab    @staticmethod
323*abd61d1fSMauro Carvalho Chehab    def run(*args, **kwargs):
324*abd61d1fSMauro Carvalho Chehab        """
325*abd61d1fSMauro Carvalho Chehab        Excecute a command, hiding its output by default.
326*abd61d1fSMauro Carvalho Chehab        Preserve comatibility with older Python versions.
327*abd61d1fSMauro Carvalho Chehab        """
328*abd61d1fSMauro Carvalho Chehab
329*abd61d1fSMauro Carvalho Chehab        capture_output = kwargs.pop('capture_output', False)
330*abd61d1fSMauro Carvalho Chehab
331*abd61d1fSMauro Carvalho Chehab        if capture_output:
332*abd61d1fSMauro Carvalho Chehab            if 'stdout' not in kwargs:
333*abd61d1fSMauro Carvalho Chehab                kwargs['stdout'] = subprocess.PIPE
334*abd61d1fSMauro Carvalho Chehab            if 'stderr' not in kwargs:
335*abd61d1fSMauro Carvalho Chehab                kwargs['stderr'] = subprocess.PIPE
336*abd61d1fSMauro Carvalho Chehab        else:
337*abd61d1fSMauro Carvalho Chehab            if 'stdout' not in kwargs:
338*abd61d1fSMauro Carvalho Chehab                kwargs['stdout'] = subprocess.DEVNULL
339*abd61d1fSMauro Carvalho Chehab            if 'stderr' not in kwargs:
340*abd61d1fSMauro Carvalho Chehab                kwargs['stderr'] = subprocess.DEVNULL
341*abd61d1fSMauro Carvalho Chehab
342*abd61d1fSMauro Carvalho Chehab        # Don't break with older Python versions
343*abd61d1fSMauro Carvalho Chehab        if 'text' in kwargs and sys.version_info < (3, 7):
344*abd61d1fSMauro Carvalho Chehab            kwargs['universal_newlines'] = kwargs.pop('text')
345*abd61d1fSMauro Carvalho Chehab
346*abd61d1fSMauro Carvalho Chehab        return subprocess.run(*args, **kwargs)
347*abd61d1fSMauro Carvalho Chehab
348*abd61d1fSMauro Carvalho Chehabclass MissingCheckers(AncillaryMethods):
349*abd61d1fSMauro Carvalho Chehab    """
350*abd61d1fSMauro Carvalho Chehab    Contains some ancillary checkers for different types of binaries and
351*abd61d1fSMauro Carvalho Chehab    package managers.
352*abd61d1fSMauro Carvalho Chehab    """
353*abd61d1fSMauro Carvalho Chehab
354*abd61d1fSMauro Carvalho Chehab    def __init__(self, args, texlive):
355*abd61d1fSMauro Carvalho Chehab        """
356*abd61d1fSMauro Carvalho Chehab        Initialize its internal variables
357*abd61d1fSMauro Carvalho Chehab        """
358*abd61d1fSMauro Carvalho Chehab        self.pdf = args.pdf
359*abd61d1fSMauro Carvalho Chehab        self.virtualenv = args.virtualenv
360*abd61d1fSMauro Carvalho Chehab        self.version_check = args.version_check
361*abd61d1fSMauro Carvalho Chehab        self.texlive = texlive
362*abd61d1fSMauro Carvalho Chehab
363*abd61d1fSMauro Carvalho Chehab        self.min_version = (0, 0, 0)
364*abd61d1fSMauro Carvalho Chehab        self.cur_version = (0, 0, 0)
365*abd61d1fSMauro Carvalho Chehab
366*abd61d1fSMauro Carvalho Chehab        self.deps = DepManager(self.pdf)
367*abd61d1fSMauro Carvalho Chehab
368*abd61d1fSMauro Carvalho Chehab        self.need_symlink = 0
369*abd61d1fSMauro Carvalho Chehab        self.need_sphinx = 0
370*abd61d1fSMauro Carvalho Chehab
371*abd61d1fSMauro Carvalho Chehab        self.verbose_warn_install = 1
372*abd61d1fSMauro Carvalho Chehab
373*abd61d1fSMauro Carvalho Chehab        self.virtenv_dir = ""
374*abd61d1fSMauro Carvalho Chehab        self.install = ""
375*abd61d1fSMauro Carvalho Chehab        self.python_cmd = ""
376*abd61d1fSMauro Carvalho Chehab
377*abd61d1fSMauro Carvalho Chehab        self.virtenv_prefix = ["sphinx_", "Sphinx_" ]
378*abd61d1fSMauro Carvalho Chehab
379*abd61d1fSMauro Carvalho Chehab    def check_missing_file(self, files, package, dtype):
380*abd61d1fSMauro Carvalho Chehab        """
381*abd61d1fSMauro Carvalho Chehab        Does the file exists? If not, add it to missing dependencies.
382*abd61d1fSMauro Carvalho Chehab        """
383*abd61d1fSMauro Carvalho Chehab        for f in files:
384*abd61d1fSMauro Carvalho Chehab            if os.path.exists(f):
385*abd61d1fSMauro Carvalho Chehab                return
386*abd61d1fSMauro Carvalho Chehab        self.deps.add_package(package, dtype)
387*abd61d1fSMauro Carvalho Chehab
388*abd61d1fSMauro Carvalho Chehab    def check_program(self, prog, dtype):
389*abd61d1fSMauro Carvalho Chehab        """
390*abd61d1fSMauro Carvalho Chehab        Does the program exists and it is at the PATH?
391*abd61d1fSMauro Carvalho Chehab        If not, add it to missing dependencies.
392*abd61d1fSMauro Carvalho Chehab        """
393*abd61d1fSMauro Carvalho Chehab        found = self.which(prog)
394*abd61d1fSMauro Carvalho Chehab        if found:
395*abd61d1fSMauro Carvalho Chehab            return found
396*abd61d1fSMauro Carvalho Chehab
397*abd61d1fSMauro Carvalho Chehab        self.deps.add_package(prog, dtype)
398*abd61d1fSMauro Carvalho Chehab
399*abd61d1fSMauro Carvalho Chehab        return None
400*abd61d1fSMauro Carvalho Chehab
401*abd61d1fSMauro Carvalho Chehab    def check_perl_module(self, prog, dtype):
402*abd61d1fSMauro Carvalho Chehab        """
403*abd61d1fSMauro Carvalho Chehab        Does perl have a dependency? Is it available?
404*abd61d1fSMauro Carvalho Chehab        If not, add it to missing dependencies.
405*abd61d1fSMauro Carvalho Chehab
406*abd61d1fSMauro Carvalho Chehab        Right now, we still need Perl for doc build, as it is required
407*abd61d1fSMauro Carvalho Chehab        by some tools called at docs or kernel build time, like:
408*abd61d1fSMauro Carvalho Chehab
409*abd61d1fSMauro Carvalho Chehab            scripts/documentation-file-ref-check
410*abd61d1fSMauro Carvalho Chehab
411*abd61d1fSMauro Carvalho Chehab        Also, checkpatch is on Perl.
412*abd61d1fSMauro Carvalho Chehab        """
413*abd61d1fSMauro Carvalho Chehab
414*abd61d1fSMauro Carvalho Chehab        # While testing with lxc download template, one of the
415*abd61d1fSMauro Carvalho Chehab        # distros (Oracle) didn't have perl - nor even an option to install
416*abd61d1fSMauro Carvalho Chehab        # before installing oraclelinux-release-el9 package.
417*abd61d1fSMauro Carvalho Chehab        #
418*abd61d1fSMauro Carvalho Chehab        # Check it before running an error. If perl is not there,
419*abd61d1fSMauro Carvalho Chehab        # add it as a mandatory package, as some parts of the doc builder
420*abd61d1fSMauro Carvalho Chehab        # needs it.
421*abd61d1fSMauro Carvalho Chehab        if not self.which("perl"):
422*abd61d1fSMauro Carvalho Chehab            self.deps.add_package("perl", DepManager.SYSTEM_MANDATORY)
423*abd61d1fSMauro Carvalho Chehab            self.deps.add_package(prog, dtype)
424*abd61d1fSMauro Carvalho Chehab            return
425*abd61d1fSMauro Carvalho Chehab
426*abd61d1fSMauro Carvalho Chehab        try:
427*abd61d1fSMauro Carvalho Chehab            self.run(["perl", f"-M{prog}", "-e", "1"], check=True)
428*abd61d1fSMauro Carvalho Chehab        except subprocess.CalledProcessError:
429*abd61d1fSMauro Carvalho Chehab            self.deps.add_package(prog, dtype)
430*abd61d1fSMauro Carvalho Chehab
431*abd61d1fSMauro Carvalho Chehab    def check_python_module(self, module, is_optional=False):
432*abd61d1fSMauro Carvalho Chehab        """
433*abd61d1fSMauro Carvalho Chehab        Does a python module exists outside venv? If not, add it to missing
434*abd61d1fSMauro Carvalho Chehab        dependencies.
435*abd61d1fSMauro Carvalho Chehab        """
436*abd61d1fSMauro Carvalho Chehab        if is_optional:
437*abd61d1fSMauro Carvalho Chehab            dtype = DepManager.PYTHON_OPTIONAL
438*abd61d1fSMauro Carvalho Chehab        else:
439*abd61d1fSMauro Carvalho Chehab            dtype = DepManager.PYTHON_MANDATORY
440*abd61d1fSMauro Carvalho Chehab
441*abd61d1fSMauro Carvalho Chehab        try:
442*abd61d1fSMauro Carvalho Chehab            self.run([self.python_cmd, "-c", f"import {module}"], check=True)
443*abd61d1fSMauro Carvalho Chehab        except subprocess.CalledProcessError:
444*abd61d1fSMauro Carvalho Chehab            self.deps.add_package(module, dtype)
445*abd61d1fSMauro Carvalho Chehab
446*abd61d1fSMauro Carvalho Chehab    def check_rpm_missing(self, pkgs, dtype):
447*abd61d1fSMauro Carvalho Chehab        """
448*abd61d1fSMauro Carvalho Chehab        Does a rpm package exists? If not, add it to missing dependencies.
449*abd61d1fSMauro Carvalho Chehab        """
450*abd61d1fSMauro Carvalho Chehab        for prog in pkgs:
451*abd61d1fSMauro Carvalho Chehab            try:
452*abd61d1fSMauro Carvalho Chehab                self.run(["rpm", "-q", prog], check=True)
453*abd61d1fSMauro Carvalho Chehab            except subprocess.CalledProcessError:
454*abd61d1fSMauro Carvalho Chehab                self.deps.add_package(prog, dtype)
455*abd61d1fSMauro Carvalho Chehab
456*abd61d1fSMauro Carvalho Chehab    def check_pacman_missing(self, pkgs, dtype):
457*abd61d1fSMauro Carvalho Chehab        """
458*abd61d1fSMauro Carvalho Chehab        Does a pacman package exists? If not, add it to missing dependencies.
459*abd61d1fSMauro Carvalho Chehab        """
460*abd61d1fSMauro Carvalho Chehab        for prog in pkgs:
461*abd61d1fSMauro Carvalho Chehab            try:
462*abd61d1fSMauro Carvalho Chehab                self.run(["pacman", "-Q", prog], check=True)
463*abd61d1fSMauro Carvalho Chehab            except subprocess.CalledProcessError:
464*abd61d1fSMauro Carvalho Chehab                self.deps.add_package(prog, dtype)
465*abd61d1fSMauro Carvalho Chehab
466*abd61d1fSMauro Carvalho Chehab    def check_missing_tex(self, is_optional=False):
467*abd61d1fSMauro Carvalho Chehab        """
468*abd61d1fSMauro Carvalho Chehab        Does a LaTeX package exists? If not, add it to missing dependencies.
469*abd61d1fSMauro Carvalho Chehab        """
470*abd61d1fSMauro Carvalho Chehab        if is_optional:
471*abd61d1fSMauro Carvalho Chehab            dtype = DepManager.PDF_OPTIONAL
472*abd61d1fSMauro Carvalho Chehab        else:
473*abd61d1fSMauro Carvalho Chehab            dtype = DepManager.PDF_MANDATORY
474*abd61d1fSMauro Carvalho Chehab
475*abd61d1fSMauro Carvalho Chehab        kpsewhich = self.which("kpsewhich")
476*abd61d1fSMauro Carvalho Chehab        for prog, package in self.texlive.items():
477*abd61d1fSMauro Carvalho Chehab
478*abd61d1fSMauro Carvalho Chehab            # If kpsewhich is not there, just add it to deps
479*abd61d1fSMauro Carvalho Chehab            if not kpsewhich:
480*abd61d1fSMauro Carvalho Chehab                self.deps.add_package(package, dtype)
481*abd61d1fSMauro Carvalho Chehab                continue
482*abd61d1fSMauro Carvalho Chehab
483*abd61d1fSMauro Carvalho Chehab            # Check if the package is needed
484*abd61d1fSMauro Carvalho Chehab            try:
485*abd61d1fSMauro Carvalho Chehab                result = self.run(
486*abd61d1fSMauro Carvalho Chehab                    [kpsewhich, prog], stdout=subprocess.PIPE, text=True, check=True
487*abd61d1fSMauro Carvalho Chehab                )
488*abd61d1fSMauro Carvalho Chehab
489*abd61d1fSMauro Carvalho Chehab                # Didn't find. Add it
490*abd61d1fSMauro Carvalho Chehab                if not result.stdout.strip():
491*abd61d1fSMauro Carvalho Chehab                    self.deps.add_package(package, dtype)
492*abd61d1fSMauro Carvalho Chehab
493*abd61d1fSMauro Carvalho Chehab            except subprocess.CalledProcessError:
494*abd61d1fSMauro Carvalho Chehab                # kpsewhich returned an error. Add it, just in case
495*abd61d1fSMauro Carvalho Chehab                self.deps.add_package(package, dtype)
496*abd61d1fSMauro Carvalho Chehab
497*abd61d1fSMauro Carvalho Chehab    def get_sphinx_fname(self):
498*abd61d1fSMauro Carvalho Chehab        """
499*abd61d1fSMauro Carvalho Chehab        Gets the binary filename for sphinx-build.
500*abd61d1fSMauro Carvalho Chehab        """
501*abd61d1fSMauro Carvalho Chehab        if "SPHINXBUILD" in os.environ:
502*abd61d1fSMauro Carvalho Chehab            return os.environ["SPHINXBUILD"]
503*abd61d1fSMauro Carvalho Chehab
504*abd61d1fSMauro Carvalho Chehab        fname = "sphinx-build"
505*abd61d1fSMauro Carvalho Chehab        if self.which(fname):
506*abd61d1fSMauro Carvalho Chehab            return fname
507*abd61d1fSMauro Carvalho Chehab
508*abd61d1fSMauro Carvalho Chehab        fname = "sphinx-build-3"
509*abd61d1fSMauro Carvalho Chehab        if self.which(fname):
510*abd61d1fSMauro Carvalho Chehab            self.need_symlink = 1
511*abd61d1fSMauro Carvalho Chehab            return fname
512*abd61d1fSMauro Carvalho Chehab
513*abd61d1fSMauro Carvalho Chehab        return ""
514*abd61d1fSMauro Carvalho Chehab
515*abd61d1fSMauro Carvalho Chehab    def get_sphinx_version(self, cmd):
516*abd61d1fSMauro Carvalho Chehab        """
517*abd61d1fSMauro Carvalho Chehab        Gets sphinx-build version.
518*abd61d1fSMauro Carvalho Chehab        """
519*abd61d1fSMauro Carvalho Chehab        try:
520*abd61d1fSMauro Carvalho Chehab            result = self.run([cmd, "--version"],
521*abd61d1fSMauro Carvalho Chehab                              stdout=subprocess.PIPE,
522*abd61d1fSMauro Carvalho Chehab                              stderr=subprocess.STDOUT,
523*abd61d1fSMauro Carvalho Chehab                              text=True, check=True)
524*abd61d1fSMauro Carvalho Chehab        except (subprocess.CalledProcessError, FileNotFoundError):
525*abd61d1fSMauro Carvalho Chehab            return None
526*abd61d1fSMauro Carvalho Chehab
527*abd61d1fSMauro Carvalho Chehab        for line in result.stdout.split("\n"):
528*abd61d1fSMauro Carvalho Chehab            match = re.match(r"^sphinx-build\s+([\d\.]+)(?:\+(?:/[\da-f]+)|b\d+)?\s*$", line)
529*abd61d1fSMauro Carvalho Chehab            if match:
530*abd61d1fSMauro Carvalho Chehab                return parse_version(match.group(1))
531*abd61d1fSMauro Carvalho Chehab
532*abd61d1fSMauro Carvalho Chehab            match = re.match(r"^Sphinx.*\s+([\d\.]+)\s*$", line)
533*abd61d1fSMauro Carvalho Chehab            if match:
534*abd61d1fSMauro Carvalho Chehab                return parse_version(match.group(1))
535*abd61d1fSMauro Carvalho Chehab
536*abd61d1fSMauro Carvalho Chehab    def check_sphinx(self, conf):
537*abd61d1fSMauro Carvalho Chehab        """
538*abd61d1fSMauro Carvalho Chehab        Checks Sphinx minimal requirements
539*abd61d1fSMauro Carvalho Chehab        """
540*abd61d1fSMauro Carvalho Chehab        try:
541*abd61d1fSMauro Carvalho Chehab            with open(conf, "r", encoding="utf-8") as f:
542*abd61d1fSMauro Carvalho Chehab                for line in f:
543*abd61d1fSMauro Carvalho Chehab                    match = re.match(r"^\s*needs_sphinx\s*=\s*[\'\"]([\d\.]+)[\'\"]", line)
544*abd61d1fSMauro Carvalho Chehab                    if match:
545*abd61d1fSMauro Carvalho Chehab                        self.min_version = parse_version(match.group(1))
546*abd61d1fSMauro Carvalho Chehab                        break
547*abd61d1fSMauro Carvalho Chehab        except IOError:
548*abd61d1fSMauro Carvalho Chehab            sys.exit(f"Can't open {conf}")
549*abd61d1fSMauro Carvalho Chehab
550*abd61d1fSMauro Carvalho Chehab        if not self.min_version:
551*abd61d1fSMauro Carvalho Chehab            sys.exit(f"Can't get needs_sphinx version from {conf}")
552*abd61d1fSMauro Carvalho Chehab
553*abd61d1fSMauro Carvalho Chehab        self.virtenv_dir = self.virtenv_prefix[0] + "latest"
554*abd61d1fSMauro Carvalho Chehab
555*abd61d1fSMauro Carvalho Chehab        sphinx = self.get_sphinx_fname()
556*abd61d1fSMauro Carvalho Chehab        if not sphinx:
557*abd61d1fSMauro Carvalho Chehab            self.need_sphinx = 1
558*abd61d1fSMauro Carvalho Chehab            return
559*abd61d1fSMauro Carvalho Chehab
560*abd61d1fSMauro Carvalho Chehab        self.cur_version = self.get_sphinx_version(sphinx)
561*abd61d1fSMauro Carvalho Chehab        if not self.cur_version:
562*abd61d1fSMauro Carvalho Chehab            sys.exit(f"{sphinx} didn't return its version")
563*abd61d1fSMauro Carvalho Chehab
564*abd61d1fSMauro Carvalho Chehab        if self.cur_version < self.min_version:
565*abd61d1fSMauro Carvalho Chehab            curver = ver_str(self.cur_version)
566*abd61d1fSMauro Carvalho Chehab            minver = ver_str(self.min_version)
567*abd61d1fSMauro Carvalho Chehab
568*abd61d1fSMauro Carvalho Chehab            print(f"ERROR: Sphinx version is {curver}. It should be >= {minver}")
569*abd61d1fSMauro Carvalho Chehab            self.need_sphinx = 1
570*abd61d1fSMauro Carvalho Chehab            return
571*abd61d1fSMauro Carvalho Chehab
572*abd61d1fSMauro Carvalho Chehab        # On version check mode, just assume Sphinx has all mandatory deps
573*abd61d1fSMauro Carvalho Chehab        if self.version_check and self.cur_version >= RECOMMENDED_VERSION:
574*abd61d1fSMauro Carvalho Chehab            sys.exit(0)
575*abd61d1fSMauro Carvalho Chehab
576*abd61d1fSMauro Carvalho Chehab    def catcheck(self, filename):
577*abd61d1fSMauro Carvalho Chehab        """
578*abd61d1fSMauro Carvalho Chehab        Reads a file if it exists, returning as string.
579*abd61d1fSMauro Carvalho Chehab        If not found, returns an empty string.
580*abd61d1fSMauro Carvalho Chehab        """
581*abd61d1fSMauro Carvalho Chehab        if os.path.exists(filename):
582*abd61d1fSMauro Carvalho Chehab            with open(filename, "r", encoding="utf-8") as f:
583*abd61d1fSMauro Carvalho Chehab                return f.read().strip()
584*abd61d1fSMauro Carvalho Chehab        return ""
585*abd61d1fSMauro Carvalho Chehab
586*abd61d1fSMauro Carvalho Chehab    def get_system_release(self):
587*abd61d1fSMauro Carvalho Chehab        """
588*abd61d1fSMauro Carvalho Chehab        Determine the system type. There's no unique way that would work
589*abd61d1fSMauro Carvalho Chehab        with all distros with a minimal package install. So, several
590*abd61d1fSMauro Carvalho Chehab        methods are used here.
591*abd61d1fSMauro Carvalho Chehab
592*abd61d1fSMauro Carvalho Chehab        By default, it will use lsb_release function. If not available, it will
593*abd61d1fSMauro Carvalho Chehab        fail back to reading the known different places where the distro name
594*abd61d1fSMauro Carvalho Chehab        is stored.
595*abd61d1fSMauro Carvalho Chehab
596*abd61d1fSMauro Carvalho Chehab        Several modern distros now have /etc/os-release, which usually have
597*abd61d1fSMauro Carvalho Chehab        a decent coverage.
598*abd61d1fSMauro Carvalho Chehab        """
599*abd61d1fSMauro Carvalho Chehab
600*abd61d1fSMauro Carvalho Chehab        system_release = ""
601*abd61d1fSMauro Carvalho Chehab
602*abd61d1fSMauro Carvalho Chehab        if self.which("lsb_release"):
603*abd61d1fSMauro Carvalho Chehab            result = self.run(["lsb_release", "-d"], capture_output=True, text=True)
604*abd61d1fSMauro Carvalho Chehab            system_release = result.stdout.replace("Description:", "").strip()
605*abd61d1fSMauro Carvalho Chehab
606*abd61d1fSMauro Carvalho Chehab        release_files = [
607*abd61d1fSMauro Carvalho Chehab            "/etc/system-release",
608*abd61d1fSMauro Carvalho Chehab            "/etc/redhat-release",
609*abd61d1fSMauro Carvalho Chehab            "/etc/lsb-release",
610*abd61d1fSMauro Carvalho Chehab            "/etc/gentoo-release",
611*abd61d1fSMauro Carvalho Chehab        ]
612*abd61d1fSMauro Carvalho Chehab
613*abd61d1fSMauro Carvalho Chehab        if not system_release:
614*abd61d1fSMauro Carvalho Chehab            for f in release_files:
615*abd61d1fSMauro Carvalho Chehab                system_release = self.catcheck(f)
616*abd61d1fSMauro Carvalho Chehab                if system_release:
617*abd61d1fSMauro Carvalho Chehab                    break
618*abd61d1fSMauro Carvalho Chehab
619*abd61d1fSMauro Carvalho Chehab        # This seems more common than LSB these days
620*abd61d1fSMauro Carvalho Chehab        if not system_release:
621*abd61d1fSMauro Carvalho Chehab            os_var = {}
622*abd61d1fSMauro Carvalho Chehab            try:
623*abd61d1fSMauro Carvalho Chehab                with open("/etc/os-release", "r", encoding="utf-8") as f:
624*abd61d1fSMauro Carvalho Chehab                    for line in f:
625*abd61d1fSMauro Carvalho Chehab                        match = re.match(r"^([\w\d\_]+)=\"?([^\"]*)\"?\n", line)
626*abd61d1fSMauro Carvalho Chehab                        if match:
627*abd61d1fSMauro Carvalho Chehab                            os_var[match.group(1)] = match.group(2)
628*abd61d1fSMauro Carvalho Chehab
629*abd61d1fSMauro Carvalho Chehab                system_release = os_var.get("NAME", "")
630*abd61d1fSMauro Carvalho Chehab                if "VERSION_ID" in os_var:
631*abd61d1fSMauro Carvalho Chehab                    system_release += " " + os_var["VERSION_ID"]
632*abd61d1fSMauro Carvalho Chehab                elif "VERSION" in os_var:
633*abd61d1fSMauro Carvalho Chehab                    system_release += " " + os_var["VERSION"]
634*abd61d1fSMauro Carvalho Chehab            except IOError:
635*abd61d1fSMauro Carvalho Chehab                pass
636*abd61d1fSMauro Carvalho Chehab
637*abd61d1fSMauro Carvalho Chehab        if not system_release:
638*abd61d1fSMauro Carvalho Chehab            system_release = self.catcheck("/etc/issue")
639*abd61d1fSMauro Carvalho Chehab
640*abd61d1fSMauro Carvalho Chehab        system_release = system_release.strip()
641*abd61d1fSMauro Carvalho Chehab
642*abd61d1fSMauro Carvalho Chehab        return system_release
643*abd61d1fSMauro Carvalho Chehab
644*abd61d1fSMauro Carvalho Chehabclass SphinxDependencyChecker(MissingCheckers):
645*abd61d1fSMauro Carvalho Chehab    """
646*abd61d1fSMauro Carvalho Chehab    Main class for checking Sphinx documentation build dependencies.
647*abd61d1fSMauro Carvalho Chehab
648*abd61d1fSMauro Carvalho Chehab    - Check for missing system packages;
649*abd61d1fSMauro Carvalho Chehab    - Check for missing Python modules;
650*abd61d1fSMauro Carvalho Chehab    - Check for missing LaTeX packages needed by PDF generation;
651*abd61d1fSMauro Carvalho Chehab    - Propose Sphinx install via Python Virtual environment;
652*abd61d1fSMauro Carvalho Chehab    - Propose Sphinx install via distro-specific package install.
653*abd61d1fSMauro Carvalho Chehab    """
654*abd61d1fSMauro Carvalho Chehab    def __init__(self, args):
655*abd61d1fSMauro Carvalho Chehab        """Initialize checker variables"""
656*abd61d1fSMauro Carvalho Chehab
657*abd61d1fSMauro Carvalho Chehab        # List of required texlive packages on Fedora and OpenSuse
658*abd61d1fSMauro Carvalho Chehab        texlive = {
659*abd61d1fSMauro Carvalho Chehab            "amsfonts.sty":       "texlive-amsfonts",
660*abd61d1fSMauro Carvalho Chehab            "amsmath.sty":        "texlive-amsmath",
661*abd61d1fSMauro Carvalho Chehab            "amssymb.sty":        "texlive-amsfonts",
662*abd61d1fSMauro Carvalho Chehab            "amsthm.sty":         "texlive-amscls",
663*abd61d1fSMauro Carvalho Chehab            "anyfontsize.sty":    "texlive-anyfontsize",
664*abd61d1fSMauro Carvalho Chehab            "atbegshi.sty":       "texlive-oberdiek",
665*abd61d1fSMauro Carvalho Chehab            "bm.sty":             "texlive-tools",
666*abd61d1fSMauro Carvalho Chehab            "capt-of.sty":        "texlive-capt-of",
667*abd61d1fSMauro Carvalho Chehab            "cmap.sty":           "texlive-cmap",
668*abd61d1fSMauro Carvalho Chehab            "ctexhook.sty":       "texlive-ctex",
669*abd61d1fSMauro Carvalho Chehab            "ecrm1000.tfm":       "texlive-ec",
670*abd61d1fSMauro Carvalho Chehab            "eqparbox.sty":       "texlive-eqparbox",
671*abd61d1fSMauro Carvalho Chehab            "eu1enc.def":         "texlive-euenc",
672*abd61d1fSMauro Carvalho Chehab            "fancybox.sty":       "texlive-fancybox",
673*abd61d1fSMauro Carvalho Chehab            "fancyvrb.sty":       "texlive-fancyvrb",
674*abd61d1fSMauro Carvalho Chehab            "float.sty":          "texlive-float",
675*abd61d1fSMauro Carvalho Chehab            "fncychap.sty":       "texlive-fncychap",
676*abd61d1fSMauro Carvalho Chehab            "footnote.sty":       "texlive-mdwtools",
677*abd61d1fSMauro Carvalho Chehab            "framed.sty":         "texlive-framed",
678*abd61d1fSMauro Carvalho Chehab            "luatex85.sty":       "texlive-luatex85",
679*abd61d1fSMauro Carvalho Chehab            "multirow.sty":       "texlive-multirow",
680*abd61d1fSMauro Carvalho Chehab            "needspace.sty":      "texlive-needspace",
681*abd61d1fSMauro Carvalho Chehab            "palatino.sty":       "texlive-psnfss",
682*abd61d1fSMauro Carvalho Chehab            "parskip.sty":        "texlive-parskip",
683*abd61d1fSMauro Carvalho Chehab            "polyglossia.sty":    "texlive-polyglossia",
684*abd61d1fSMauro Carvalho Chehab            "tabulary.sty":       "texlive-tabulary",
685*abd61d1fSMauro Carvalho Chehab            "threeparttable.sty": "texlive-threeparttable",
686*abd61d1fSMauro Carvalho Chehab            "titlesec.sty":       "texlive-titlesec",
687*abd61d1fSMauro Carvalho Chehab            "ucs.sty":            "texlive-ucs",
688*abd61d1fSMauro Carvalho Chehab            "upquote.sty":        "texlive-upquote",
689*abd61d1fSMauro Carvalho Chehab            "wrapfig.sty":        "texlive-wrapfig",
690*abd61d1fSMauro Carvalho Chehab        }
691*abd61d1fSMauro Carvalho Chehab
692*abd61d1fSMauro Carvalho Chehab        super().__init__(args, texlive)
693*abd61d1fSMauro Carvalho Chehab
694*abd61d1fSMauro Carvalho Chehab        self.need_pip = False
695*abd61d1fSMauro Carvalho Chehab        self.rec_sphinx_upgrade = 0
696*abd61d1fSMauro Carvalho Chehab
697*abd61d1fSMauro Carvalho Chehab        self.system_release = self.get_system_release()
698*abd61d1fSMauro Carvalho Chehab        self.activate_cmd = ""
699*abd61d1fSMauro Carvalho Chehab
700*abd61d1fSMauro Carvalho Chehab        # Some distros may not have a Sphinx shipped package compatible with
701*abd61d1fSMauro Carvalho Chehab        # our minimal requirements
702*abd61d1fSMauro Carvalho Chehab        self.package_supported = True
703*abd61d1fSMauro Carvalho Chehab
704*abd61d1fSMauro Carvalho Chehab        # Recommend a new python version
705*abd61d1fSMauro Carvalho Chehab        self.recommend_python = None
706*abd61d1fSMauro Carvalho Chehab
707*abd61d1fSMauro Carvalho Chehab        # Certain hints are meant to be shown only once
708*abd61d1fSMauro Carvalho Chehab        self.distro_msg = None
709*abd61d1fSMauro Carvalho Chehab
710*abd61d1fSMauro Carvalho Chehab        self.latest_avail_ver = (0, 0, 0)
711*abd61d1fSMauro Carvalho Chehab        self.venv_ver = (0, 0, 0)
712*abd61d1fSMauro Carvalho Chehab
713*abd61d1fSMauro Carvalho Chehab        prefix = os.environ.get("srctree", ".") + "/"
714*abd61d1fSMauro Carvalho Chehab
715*abd61d1fSMauro Carvalho Chehab        self.conf = prefix + "Documentation/conf.py"
716*abd61d1fSMauro Carvalho Chehab        self.requirement_file = prefix + "Documentation/sphinx/requirements.txt"
717*abd61d1fSMauro Carvalho Chehab
718*abd61d1fSMauro Carvalho Chehab    def get_install_progs(self, progs, cmd, extra=None):
719*abd61d1fSMauro Carvalho Chehab        """
720*abd61d1fSMauro Carvalho Chehab        Check for missing dependencies using the provided program mapping.
721*abd61d1fSMauro Carvalho Chehab
722*abd61d1fSMauro Carvalho Chehab        The actual distro-specific programs are mapped via progs argument.
723*abd61d1fSMauro Carvalho Chehab        """
724*abd61d1fSMauro Carvalho Chehab        install = self.deps.check_missing(progs)
725*abd61d1fSMauro Carvalho Chehab
726*abd61d1fSMauro Carvalho Chehab        if self.verbose_warn_install:
727*abd61d1fSMauro Carvalho Chehab            self.deps.warn_install()
728*abd61d1fSMauro Carvalho Chehab
729*abd61d1fSMauro Carvalho Chehab        if not install:
730*abd61d1fSMauro Carvalho Chehab            return
731*abd61d1fSMauro Carvalho Chehab
732*abd61d1fSMauro Carvalho Chehab        if cmd:
733*abd61d1fSMauro Carvalho Chehab            if self.verbose_warn_install:
734*abd61d1fSMauro Carvalho Chehab                msg = "You should run:"
735*abd61d1fSMauro Carvalho Chehab            else:
736*abd61d1fSMauro Carvalho Chehab                msg = ""
737*abd61d1fSMauro Carvalho Chehab
738*abd61d1fSMauro Carvalho Chehab            if extra:
739*abd61d1fSMauro Carvalho Chehab                msg += "\n\t" + extra.replace("\n", "\n\t")
740*abd61d1fSMauro Carvalho Chehab
741*abd61d1fSMauro Carvalho Chehab            return(msg + "\n\tsudo " + cmd + " " + install)
742*abd61d1fSMauro Carvalho Chehab
743*abd61d1fSMauro Carvalho Chehab        return None
744*abd61d1fSMauro Carvalho Chehab
745*abd61d1fSMauro Carvalho Chehab    #
746*abd61d1fSMauro Carvalho Chehab    # Distro-specific hints methods
747*abd61d1fSMauro Carvalho Chehab    #
748*abd61d1fSMauro Carvalho Chehab
749*abd61d1fSMauro Carvalho Chehab    def give_debian_hints(self):
750*abd61d1fSMauro Carvalho Chehab        """
751*abd61d1fSMauro Carvalho Chehab        Provide package installation hints for Debian-based distros.
752*abd61d1fSMauro Carvalho Chehab        """
753*abd61d1fSMauro Carvalho Chehab        progs = {
754*abd61d1fSMauro Carvalho Chehab            "Pod::Usage":    "perl-modules",
755*abd61d1fSMauro Carvalho Chehab            "convert":       "imagemagick",
756*abd61d1fSMauro Carvalho Chehab            "dot":           "graphviz",
757*abd61d1fSMauro Carvalho Chehab            "ensurepip":     "python3-venv",
758*abd61d1fSMauro Carvalho Chehab            "python-sphinx": "python3-sphinx",
759*abd61d1fSMauro Carvalho Chehab            "rsvg-convert":  "librsvg2-bin",
760*abd61d1fSMauro Carvalho Chehab            "virtualenv":    "virtualenv",
761*abd61d1fSMauro Carvalho Chehab            "xelatex":       "texlive-xetex",
762*abd61d1fSMauro Carvalho Chehab            "yaml":          "python3-yaml",
763*abd61d1fSMauro Carvalho Chehab        }
764*abd61d1fSMauro Carvalho Chehab
765*abd61d1fSMauro Carvalho Chehab        if self.pdf:
766*abd61d1fSMauro Carvalho Chehab            pdf_pkgs = {
767*abd61d1fSMauro Carvalho Chehab                "fonts-dejavu": [
768*abd61d1fSMauro Carvalho Chehab                    "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
769*abd61d1fSMauro Carvalho Chehab                ],
770*abd61d1fSMauro Carvalho Chehab                "fonts-noto-cjk": [
771*abd61d1fSMauro Carvalho Chehab                    "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
772*abd61d1fSMauro Carvalho Chehab                    "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
773*abd61d1fSMauro Carvalho Chehab                    "/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc",
774*abd61d1fSMauro Carvalho Chehab                ],
775*abd61d1fSMauro Carvalho Chehab                "tex-gyre": [
776*abd61d1fSMauro Carvalho Chehab                    "/usr/share/texmf/tex/latex/tex-gyre/tgtermes.sty"
777*abd61d1fSMauro Carvalho Chehab                ],
778*abd61d1fSMauro Carvalho Chehab                "texlive-fonts-recommended": [
779*abd61d1fSMauro Carvalho Chehab                    "/usr/share/texlive/texmf-dist/fonts/tfm/adobe/zapfding/pzdr.tfm",
780*abd61d1fSMauro Carvalho Chehab                ],
781*abd61d1fSMauro Carvalho Chehab                "texlive-lang-chinese": [
782*abd61d1fSMauro Carvalho Chehab                    "/usr/share/texlive/texmf-dist/tex/latex/ctex/ctexhook.sty",
783*abd61d1fSMauro Carvalho Chehab                ],
784*abd61d1fSMauro Carvalho Chehab            }
785*abd61d1fSMauro Carvalho Chehab
786*abd61d1fSMauro Carvalho Chehab            for package, files in pdf_pkgs.items():
787*abd61d1fSMauro Carvalho Chehab                self.check_missing_file(files, package, DepManager.PDF_MANDATORY)
788*abd61d1fSMauro Carvalho Chehab
789*abd61d1fSMauro Carvalho Chehab            self.check_program("dvipng", DepManager.PDF_MANDATORY)
790*abd61d1fSMauro Carvalho Chehab
791*abd61d1fSMauro Carvalho Chehab        if not self.distro_msg:
792*abd61d1fSMauro Carvalho Chehab            self.distro_msg = \
793*abd61d1fSMauro Carvalho Chehab                "Note: ImageMagick is broken on some distros, affecting PDF output. For more details:\n" \
794*abd61d1fSMauro Carvalho Chehab                "\thttps://askubuntu.com/questions/1158894/imagemagick-still-broken-using-with-usr-bin-convert"
795*abd61d1fSMauro Carvalho Chehab
796*abd61d1fSMauro Carvalho Chehab        return self.get_install_progs(progs, "apt-get install")
797*abd61d1fSMauro Carvalho Chehab
798*abd61d1fSMauro Carvalho Chehab    def give_redhat_hints(self):
799*abd61d1fSMauro Carvalho Chehab        """
800*abd61d1fSMauro Carvalho Chehab        Provide package installation hints for RedHat-based distros
801*abd61d1fSMauro Carvalho Chehab        (Fedora, RHEL and RHEL-based variants).
802*abd61d1fSMauro Carvalho Chehab        """
803*abd61d1fSMauro Carvalho Chehab        progs = {
804*abd61d1fSMauro Carvalho Chehab            "Pod::Usage":       "perl-Pod-Usage",
805*abd61d1fSMauro Carvalho Chehab            "convert":          "ImageMagick",
806*abd61d1fSMauro Carvalho Chehab            "dot":              "graphviz",
807*abd61d1fSMauro Carvalho Chehab            "python-sphinx":    "python3-sphinx",
808*abd61d1fSMauro Carvalho Chehab            "rsvg-convert":     "librsvg2-tools",
809*abd61d1fSMauro Carvalho Chehab            "virtualenv":       "python3-virtualenv",
810*abd61d1fSMauro Carvalho Chehab            "xelatex":          "texlive-xetex-bin",
811*abd61d1fSMauro Carvalho Chehab            "yaml":             "python3-pyyaml",
812*abd61d1fSMauro Carvalho Chehab        }
813*abd61d1fSMauro Carvalho Chehab
814*abd61d1fSMauro Carvalho Chehab        fedora_tex_pkgs = [
815*abd61d1fSMauro Carvalho Chehab            "dejavu-sans-fonts",
816*abd61d1fSMauro Carvalho Chehab            "dejavu-sans-mono-fonts",
817*abd61d1fSMauro Carvalho Chehab            "dejavu-serif-fonts",
818*abd61d1fSMauro Carvalho Chehab            "texlive-collection-fontsrecommended",
819*abd61d1fSMauro Carvalho Chehab            "texlive-collection-latex",
820*abd61d1fSMauro Carvalho Chehab            "texlive-xecjk",
821*abd61d1fSMauro Carvalho Chehab        ]
822*abd61d1fSMauro Carvalho Chehab
823*abd61d1fSMauro Carvalho Chehab        fedora = False
824*abd61d1fSMauro Carvalho Chehab        rel = None
825*abd61d1fSMauro Carvalho Chehab
826*abd61d1fSMauro Carvalho Chehab        match = re.search(r"(release|Linux)\s+(\d+)", self.system_release)
827*abd61d1fSMauro Carvalho Chehab        if match:
828*abd61d1fSMauro Carvalho Chehab            rel = int(match.group(2))
829*abd61d1fSMauro Carvalho Chehab
830*abd61d1fSMauro Carvalho Chehab        if not rel:
831*abd61d1fSMauro Carvalho Chehab            print("Couldn't identify release number")
832*abd61d1fSMauro Carvalho Chehab            noto_sans_redhat = None
833*abd61d1fSMauro Carvalho Chehab            self.pdf = False
834*abd61d1fSMauro Carvalho Chehab        elif re.search("Fedora", self.system_release):
835*abd61d1fSMauro Carvalho Chehab            # Fedora 38 and upper use this CJK font
836*abd61d1fSMauro Carvalho Chehab
837*abd61d1fSMauro Carvalho Chehab            noto_sans_redhat = "google-noto-sans-cjk-fonts"
838*abd61d1fSMauro Carvalho Chehab            fedora = True
839*abd61d1fSMauro Carvalho Chehab        else:
840*abd61d1fSMauro Carvalho Chehab            # Almalinux, CentOS, RHEL, ...
841*abd61d1fSMauro Carvalho Chehab
842*abd61d1fSMauro Carvalho Chehab            # at least up to version 9 (and Fedora < 38), that's the CJK font
843*abd61d1fSMauro Carvalho Chehab            noto_sans_redhat = "google-noto-sans-cjk-ttc-fonts"
844*abd61d1fSMauro Carvalho Chehab
845*abd61d1fSMauro Carvalho Chehab            progs["virtualenv"] = "python-virtualenv"
846*abd61d1fSMauro Carvalho Chehab
847*abd61d1fSMauro Carvalho Chehab            if not rel or rel < 8:
848*abd61d1fSMauro Carvalho Chehab                print("ERROR: Distro not supported. Too old?")
849*abd61d1fSMauro Carvalho Chehab                return
850*abd61d1fSMauro Carvalho Chehab
851*abd61d1fSMauro Carvalho Chehab            # RHEL 8 uses Python 3.6, which is not compatible with
852*abd61d1fSMauro Carvalho Chehab            # the build system anymore. Suggest Python 3.11
853*abd61d1fSMauro Carvalho Chehab            if rel == 8:
854*abd61d1fSMauro Carvalho Chehab                self.check_program("python3.9", DepManager.SYSTEM_MANDATORY)
855*abd61d1fSMauro Carvalho Chehab                progs["python3.9"] = "python39"
856*abd61d1fSMauro Carvalho Chehab                progs["yaml"] = "python39-pyyaml"
857*abd61d1fSMauro Carvalho Chehab
858*abd61d1fSMauro Carvalho Chehab                self.recommend_python = True
859*abd61d1fSMauro Carvalho Chehab
860*abd61d1fSMauro Carvalho Chehab                # There's no python39-sphinx package. Only pip is supported
861*abd61d1fSMauro Carvalho Chehab                self.package_supported = False
862*abd61d1fSMauro Carvalho Chehab
863*abd61d1fSMauro Carvalho Chehab            if not self.distro_msg:
864*abd61d1fSMauro Carvalho Chehab                self.distro_msg = \
865*abd61d1fSMauro Carvalho Chehab                    "Note: RHEL-based distros typically require extra repositories.\n" \
866*abd61d1fSMauro Carvalho Chehab                    "For most, enabling epel and crb are enough:\n" \
867*abd61d1fSMauro Carvalho Chehab                    "\tsudo dnf install -y epel-release\n" \
868*abd61d1fSMauro Carvalho Chehab                    "\tsudo dnf config-manager --set-enabled crb\n" \
869*abd61d1fSMauro Carvalho Chehab                    "Yet, some may have other required repositories. Those commands could be useful:\n" \
870*abd61d1fSMauro Carvalho Chehab                    "\tsudo dnf repolist all\n" \
871*abd61d1fSMauro Carvalho Chehab                    "\tsudo dnf repoquery --available --info <pkgs>\n" \
872*abd61d1fSMauro Carvalho Chehab                    "\tsudo dnf config-manager --set-enabled '*' # enable all - probably not what you want"
873*abd61d1fSMauro Carvalho Chehab
874*abd61d1fSMauro Carvalho Chehab        if self.pdf:
875*abd61d1fSMauro Carvalho Chehab            pdf_pkgs = [
876*abd61d1fSMauro Carvalho Chehab                "/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc",
877*abd61d1fSMauro Carvalho Chehab                "/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc",
878*abd61d1fSMauro Carvalho Chehab            ]
879*abd61d1fSMauro Carvalho Chehab
880*abd61d1fSMauro Carvalho Chehab            self.check_missing_file(pdf_pkgs, noto_sans_redhat, DepManager.PDF_MANDATORY)
881*abd61d1fSMauro Carvalho Chehab
882*abd61d1fSMauro Carvalho Chehab            self.check_rpm_missing(fedora_tex_pkgs, DepManager.PDF_MANDATORY)
883*abd61d1fSMauro Carvalho Chehab
884*abd61d1fSMauro Carvalho Chehab            self.check_missing_tex(DepManager.PDF_MANDATORY)
885*abd61d1fSMauro Carvalho Chehab
886*abd61d1fSMauro Carvalho Chehab            # There's no texlive-ctex on RHEL 8 repositories. This will
887*abd61d1fSMauro Carvalho Chehab            # likely affect CJK pdf build only.
888*abd61d1fSMauro Carvalho Chehab            if not fedora and rel == 8:
889*abd61d1fSMauro Carvalho Chehab                self.deps.del_package("texlive-ctex")
890*abd61d1fSMauro Carvalho Chehab
891*abd61d1fSMauro Carvalho Chehab        return self.get_install_progs(progs, "dnf install")
892*abd61d1fSMauro Carvalho Chehab
893*abd61d1fSMauro Carvalho Chehab    def give_opensuse_hints(self):
894*abd61d1fSMauro Carvalho Chehab        """
895*abd61d1fSMauro Carvalho Chehab        Provide package installation hints for openSUSE-based distros
896*abd61d1fSMauro Carvalho Chehab        (Leap and Tumbleweed).
897*abd61d1fSMauro Carvalho Chehab        """
898*abd61d1fSMauro Carvalho Chehab        progs = {
899*abd61d1fSMauro Carvalho Chehab            "Pod::Usage":    "perl-Pod-Usage",
900*abd61d1fSMauro Carvalho Chehab            "convert":       "ImageMagick",
901*abd61d1fSMauro Carvalho Chehab            "dot":           "graphviz",
902*abd61d1fSMauro Carvalho Chehab            "python-sphinx": "python3-sphinx",
903*abd61d1fSMauro Carvalho Chehab            "virtualenv":    "python3-virtualenv",
904*abd61d1fSMauro Carvalho Chehab            "xelatex":       "texlive-xetex-bin texlive-dejavu",
905*abd61d1fSMauro Carvalho Chehab            "yaml":          "python3-pyyaml",
906*abd61d1fSMauro Carvalho Chehab        }
907*abd61d1fSMauro Carvalho Chehab
908*abd61d1fSMauro Carvalho Chehab        suse_tex_pkgs = [
909*abd61d1fSMauro Carvalho Chehab            "texlive-babel-english",
910*abd61d1fSMauro Carvalho Chehab            "texlive-caption",
911*abd61d1fSMauro Carvalho Chehab            "texlive-colortbl",
912*abd61d1fSMauro Carvalho Chehab            "texlive-courier",
913*abd61d1fSMauro Carvalho Chehab            "texlive-dvips",
914*abd61d1fSMauro Carvalho Chehab            "texlive-helvetic",
915*abd61d1fSMauro Carvalho Chehab            "texlive-makeindex",
916*abd61d1fSMauro Carvalho Chehab            "texlive-metafont",
917*abd61d1fSMauro Carvalho Chehab            "texlive-metapost",
918*abd61d1fSMauro Carvalho Chehab            "texlive-palatino",
919*abd61d1fSMauro Carvalho Chehab            "texlive-preview",
920*abd61d1fSMauro Carvalho Chehab            "texlive-times",
921*abd61d1fSMauro Carvalho Chehab            "texlive-zapfchan",
922*abd61d1fSMauro Carvalho Chehab            "texlive-zapfding",
923*abd61d1fSMauro Carvalho Chehab        ]
924*abd61d1fSMauro Carvalho Chehab
925*abd61d1fSMauro Carvalho Chehab        progs["latexmk"] = "texlive-latexmk-bin"
926*abd61d1fSMauro Carvalho Chehab
927*abd61d1fSMauro Carvalho Chehab        match = re.search(r"(Leap)\s+(\d+).(\d)", self.system_release)
928*abd61d1fSMauro Carvalho Chehab        if match:
929*abd61d1fSMauro Carvalho Chehab            rel = int(match.group(2))
930*abd61d1fSMauro Carvalho Chehab
931*abd61d1fSMauro Carvalho Chehab            # Leap 15.x uses Python 3.6, which is not compatible with
932*abd61d1fSMauro Carvalho Chehab            # the build system anymore. Suggest Python 3.11
933*abd61d1fSMauro Carvalho Chehab            if rel == 15:
934*abd61d1fSMauro Carvalho Chehab                if not self.which(self.python_cmd):
935*abd61d1fSMauro Carvalho Chehab                    self.check_program("python3.11", DepManager.SYSTEM_MANDATORY)
936*abd61d1fSMauro Carvalho Chehab                    progs["python3.11"] = "python311"
937*abd61d1fSMauro Carvalho Chehab                    self.recommend_python = True
938*abd61d1fSMauro Carvalho Chehab
939*abd61d1fSMauro Carvalho Chehab                progs.update({
940*abd61d1fSMauro Carvalho Chehab                    "python-sphinx": "python311-Sphinx python311-Sphinx-latex",
941*abd61d1fSMauro Carvalho Chehab                    "virtualenv":    "python311-virtualenv",
942*abd61d1fSMauro Carvalho Chehab                    "yaml":          "python311-PyYAML",
943*abd61d1fSMauro Carvalho Chehab                })
944*abd61d1fSMauro Carvalho Chehab        else:
945*abd61d1fSMauro Carvalho Chehab            # Tumbleweed defaults to Python 3.11
946*abd61d1fSMauro Carvalho Chehab
947*abd61d1fSMauro Carvalho Chehab            progs.update({
948*abd61d1fSMauro Carvalho Chehab                "python-sphinx": "python313-Sphinx python313-Sphinx-latex",
949*abd61d1fSMauro Carvalho Chehab                "virtualenv":    "python313-virtualenv",
950*abd61d1fSMauro Carvalho Chehab                "yaml":          "python313-PyYAML",
951*abd61d1fSMauro Carvalho Chehab            })
952*abd61d1fSMauro Carvalho Chehab
953*abd61d1fSMauro Carvalho Chehab        # FIXME: add support for installing CJK fonts
954*abd61d1fSMauro Carvalho Chehab        #
955*abd61d1fSMauro Carvalho Chehab        # I tried hard, but was unable to find a way to install
956*abd61d1fSMauro Carvalho Chehab        # "Noto Sans CJK SC" on openSUSE
957*abd61d1fSMauro Carvalho Chehab
958*abd61d1fSMauro Carvalho Chehab        if self.pdf:
959*abd61d1fSMauro Carvalho Chehab            self.check_rpm_missing(suse_tex_pkgs, DepManager.PDF_MANDATORY)
960*abd61d1fSMauro Carvalho Chehab        if self.pdf:
961*abd61d1fSMauro Carvalho Chehab            self.check_missing_tex()
962*abd61d1fSMauro Carvalho Chehab
963*abd61d1fSMauro Carvalho Chehab        return self.get_install_progs(progs, "zypper install --no-recommends")
964*abd61d1fSMauro Carvalho Chehab
965*abd61d1fSMauro Carvalho Chehab    def give_mageia_hints(self):
966*abd61d1fSMauro Carvalho Chehab        """
967*abd61d1fSMauro Carvalho Chehab        Provide package installation hints for Mageia and OpenMandriva.
968*abd61d1fSMauro Carvalho Chehab        """
969*abd61d1fSMauro Carvalho Chehab        progs = {
970*abd61d1fSMauro Carvalho Chehab            "Pod::Usage":    "perl-Pod-Usage",
971*abd61d1fSMauro Carvalho Chehab            "convert":       "ImageMagick",
972*abd61d1fSMauro Carvalho Chehab            "dot":           "graphviz",
973*abd61d1fSMauro Carvalho Chehab            "python-sphinx": "python3-sphinx",
974*abd61d1fSMauro Carvalho Chehab            "rsvg-convert":  "librsvg2",
975*abd61d1fSMauro Carvalho Chehab            "virtualenv":    "python3-virtualenv",
976*abd61d1fSMauro Carvalho Chehab            "xelatex":       "texlive",
977*abd61d1fSMauro Carvalho Chehab            "yaml":          "python3-yaml",
978*abd61d1fSMauro Carvalho Chehab        }
979*abd61d1fSMauro Carvalho Chehab
980*abd61d1fSMauro Carvalho Chehab        tex_pkgs = [
981*abd61d1fSMauro Carvalho Chehab            "texlive-fontsextra",
982*abd61d1fSMauro Carvalho Chehab            "texlive-fonts-asian",
983*abd61d1fSMauro Carvalho Chehab            "fonts-ttf-dejavu",
984*abd61d1fSMauro Carvalho Chehab        ]
985*abd61d1fSMauro Carvalho Chehab
986*abd61d1fSMauro Carvalho Chehab        if re.search(r"OpenMandriva", self.system_release):
987*abd61d1fSMauro Carvalho Chehab            packager_cmd = "dnf install"
988*abd61d1fSMauro Carvalho Chehab            noto_sans = "noto-sans-cjk-fonts"
989*abd61d1fSMauro Carvalho Chehab            tex_pkgs = [
990*abd61d1fSMauro Carvalho Chehab                "texlive-collection-basic",
991*abd61d1fSMauro Carvalho Chehab                "texlive-collection-langcjk",
992*abd61d1fSMauro Carvalho Chehab                "texlive-collection-fontsextra",
993*abd61d1fSMauro Carvalho Chehab                "texlive-collection-fontsrecommended"
994*abd61d1fSMauro Carvalho Chehab            ]
995*abd61d1fSMauro Carvalho Chehab
996*abd61d1fSMauro Carvalho Chehab            # Tested on OpenMandriva Lx 4.3
997*abd61d1fSMauro Carvalho Chehab            progs["convert"] = "imagemagick"
998*abd61d1fSMauro Carvalho Chehab            progs["yaml"] = "python-pyyaml"
999*abd61d1fSMauro Carvalho Chehab            progs["python-virtualenv"] = "python-virtualenv"
1000*abd61d1fSMauro Carvalho Chehab            progs["python-sphinx"] = "python-sphinx"
1001*abd61d1fSMauro Carvalho Chehab            progs["xelatex"] = "texlive"
1002*abd61d1fSMauro Carvalho Chehab
1003*abd61d1fSMauro Carvalho Chehab            self.check_program("python-virtualenv", DepManager.PYTHON_MANDATORY)
1004*abd61d1fSMauro Carvalho Chehab
1005*abd61d1fSMauro Carvalho Chehab            # On my tests with openMandriva LX 4.0 docker image, upgraded
1006*abd61d1fSMauro Carvalho Chehab            # to 4.3, python-virtualenv package is broken: it is missing
1007*abd61d1fSMauro Carvalho Chehab            # ensurepip. Without it, the alternative would be to run:
1008*abd61d1fSMauro Carvalho Chehab            # python3 -m venv --without-pip ~/sphinx_latest, but running
1009*abd61d1fSMauro Carvalho Chehab            # pip there won't install sphinx at venv.
1010*abd61d1fSMauro Carvalho Chehab            #
1011*abd61d1fSMauro Carvalho Chehab            # Add a note about that.
1012*abd61d1fSMauro Carvalho Chehab
1013*abd61d1fSMauro Carvalho Chehab            if not self.distro_msg:
1014*abd61d1fSMauro Carvalho Chehab                self.distro_msg = \
1015*abd61d1fSMauro Carvalho Chehab                    "Notes:\n"\
1016*abd61d1fSMauro Carvalho Chehab                    "1. for venv, ensurepip could be broken, preventing its install method.\n" \
1017*abd61d1fSMauro Carvalho Chehab                    "2. at least on OpenMandriva LX 4.3, texlive packages seem broken"
1018*abd61d1fSMauro Carvalho Chehab
1019*abd61d1fSMauro Carvalho Chehab        else:
1020*abd61d1fSMauro Carvalho Chehab            packager_cmd = "urpmi"
1021*abd61d1fSMauro Carvalho Chehab            noto_sans = "google-noto-sans-cjk-ttc-fonts"
1022*abd61d1fSMauro Carvalho Chehab
1023*abd61d1fSMauro Carvalho Chehab        progs["latexmk"] = "texlive-collection-basic"
1024*abd61d1fSMauro Carvalho Chehab
1025*abd61d1fSMauro Carvalho Chehab        if self.pdf:
1026*abd61d1fSMauro Carvalho Chehab            pdf_pkgs = [
1027*abd61d1fSMauro Carvalho Chehab                "/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc",
1028*abd61d1fSMauro Carvalho Chehab                "/usr/share/fonts/TTF/NotoSans-Regular.ttf",
1029*abd61d1fSMauro Carvalho Chehab            ]
1030*abd61d1fSMauro Carvalho Chehab
1031*abd61d1fSMauro Carvalho Chehab            self.check_missing_file(pdf_pkgs, noto_sans, DepManager.PDF_MANDATORY)
1032*abd61d1fSMauro Carvalho Chehab            self.check_rpm_missing(tex_pkgs, DepManager.PDF_MANDATORY)
1033*abd61d1fSMauro Carvalho Chehab
1034*abd61d1fSMauro Carvalho Chehab        return self.get_install_progs(progs, packager_cmd)
1035*abd61d1fSMauro Carvalho Chehab
1036*abd61d1fSMauro Carvalho Chehab    def give_arch_linux_hints(self):
1037*abd61d1fSMauro Carvalho Chehab        """
1038*abd61d1fSMauro Carvalho Chehab        Provide package installation hints for ArchLinux.
1039*abd61d1fSMauro Carvalho Chehab        """
1040*abd61d1fSMauro Carvalho Chehab        progs = {
1041*abd61d1fSMauro Carvalho Chehab            "convert":      "imagemagick",
1042*abd61d1fSMauro Carvalho Chehab            "dot":          "graphviz",
1043*abd61d1fSMauro Carvalho Chehab            "latexmk":      "texlive-core",
1044*abd61d1fSMauro Carvalho Chehab            "rsvg-convert": "extra/librsvg",
1045*abd61d1fSMauro Carvalho Chehab            "virtualenv":   "python-virtualenv",
1046*abd61d1fSMauro Carvalho Chehab            "xelatex":      "texlive-xetex",
1047*abd61d1fSMauro Carvalho Chehab            "yaml":         "python-yaml",
1048*abd61d1fSMauro Carvalho Chehab        }
1049*abd61d1fSMauro Carvalho Chehab
1050*abd61d1fSMauro Carvalho Chehab        archlinux_tex_pkgs = [
1051*abd61d1fSMauro Carvalho Chehab            "texlive-basic",
1052*abd61d1fSMauro Carvalho Chehab            "texlive-binextra",
1053*abd61d1fSMauro Carvalho Chehab            "texlive-core",
1054*abd61d1fSMauro Carvalho Chehab            "texlive-fontsrecommended",
1055*abd61d1fSMauro Carvalho Chehab            "texlive-langchinese",
1056*abd61d1fSMauro Carvalho Chehab            "texlive-langcjk",
1057*abd61d1fSMauro Carvalho Chehab            "texlive-latexextra",
1058*abd61d1fSMauro Carvalho Chehab            "ttf-dejavu",
1059*abd61d1fSMauro Carvalho Chehab        ]
1060*abd61d1fSMauro Carvalho Chehab
1061*abd61d1fSMauro Carvalho Chehab        if self.pdf:
1062*abd61d1fSMauro Carvalho Chehab            self.check_pacman_missing(archlinux_tex_pkgs,
1063*abd61d1fSMauro Carvalho Chehab                                      DepManager.PDF_MANDATORY)
1064*abd61d1fSMauro Carvalho Chehab
1065*abd61d1fSMauro Carvalho Chehab            self.check_missing_file(["/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc"],
1066*abd61d1fSMauro Carvalho Chehab                                    "noto-fonts-cjk",
1067*abd61d1fSMauro Carvalho Chehab                                    DepManager.PDF_MANDATORY)
1068*abd61d1fSMauro Carvalho Chehab
1069*abd61d1fSMauro Carvalho Chehab
1070*abd61d1fSMauro Carvalho Chehab        return self.get_install_progs(progs, "pacman -S")
1071*abd61d1fSMauro Carvalho Chehab
1072*abd61d1fSMauro Carvalho Chehab    def give_gentoo_hints(self):
1073*abd61d1fSMauro Carvalho Chehab        """
1074*abd61d1fSMauro Carvalho Chehab        Provide package installation hints for Gentoo.
1075*abd61d1fSMauro Carvalho Chehab        """
1076*abd61d1fSMauro Carvalho Chehab        texlive_deps = [
1077*abd61d1fSMauro Carvalho Chehab            "dev-texlive/texlive-fontsrecommended",
1078*abd61d1fSMauro Carvalho Chehab            "dev-texlive/texlive-latexextra",
1079*abd61d1fSMauro Carvalho Chehab            "dev-texlive/texlive-xetex",
1080*abd61d1fSMauro Carvalho Chehab            "media-fonts/dejavu",
1081*abd61d1fSMauro Carvalho Chehab        ]
1082*abd61d1fSMauro Carvalho Chehab
1083*abd61d1fSMauro Carvalho Chehab        progs = {
1084*abd61d1fSMauro Carvalho Chehab            "convert":       "media-gfx/imagemagick",
1085*abd61d1fSMauro Carvalho Chehab            "dot":           "media-gfx/graphviz",
1086*abd61d1fSMauro Carvalho Chehab            "rsvg-convert":  "gnome-base/librsvg",
1087*abd61d1fSMauro Carvalho Chehab            "virtualenv":    "dev-python/virtualenv",
1088*abd61d1fSMauro Carvalho Chehab            "xelatex":       " ".join(texlive_deps),
1089*abd61d1fSMauro Carvalho Chehab            "yaml":          "dev-python/pyyaml",
1090*abd61d1fSMauro Carvalho Chehab            "python-sphinx": "dev-python/sphinx",
1091*abd61d1fSMauro Carvalho Chehab        }
1092*abd61d1fSMauro Carvalho Chehab
1093*abd61d1fSMauro Carvalho Chehab        if self.pdf:
1094*abd61d1fSMauro Carvalho Chehab            pdf_pkgs = {
1095*abd61d1fSMauro Carvalho Chehab                "media-fonts/dejavu": [
1096*abd61d1fSMauro Carvalho Chehab                    "/usr/share/fonts/dejavu/DejaVuSans.ttf",
1097*abd61d1fSMauro Carvalho Chehab                ],
1098*abd61d1fSMauro Carvalho Chehab                "media-fonts/noto-cjk": [
1099*abd61d1fSMauro Carvalho Chehab                    "/usr/share/fonts/noto-cjk/NotoSansCJKsc-Regular.otf",
1100*abd61d1fSMauro Carvalho Chehab                    "/usr/share/fonts/noto-cjk/NotoSerifCJK-Regular.ttc",
1101*abd61d1fSMauro Carvalho Chehab                ],
1102*abd61d1fSMauro Carvalho Chehab            }
1103*abd61d1fSMauro Carvalho Chehab            for package, files in pdf_pkgs.items():
1104*abd61d1fSMauro Carvalho Chehab                self.check_missing_file(files, package, DepManager.PDF_MANDATORY)
1105*abd61d1fSMauro Carvalho Chehab
1106*abd61d1fSMauro Carvalho Chehab        # Handling dependencies is a nightmare, as Gentoo refuses to emerge
1107*abd61d1fSMauro Carvalho Chehab        # some packages if there's no package.use file describing them.
1108*abd61d1fSMauro Carvalho Chehab        # To make it worse, compilation flags shall also be present there
1109*abd61d1fSMauro Carvalho Chehab        # for some packages. If USE is not perfect, error/warning messages
1110*abd61d1fSMauro Carvalho Chehab        #   like those are shown:
1111*abd61d1fSMauro Carvalho Chehab        #
1112*abd61d1fSMauro Carvalho Chehab        #   !!! The following binary packages have been ignored due to non matching USE:
1113*abd61d1fSMauro Carvalho Chehab        #
1114*abd61d1fSMauro Carvalho Chehab        #    =media-gfx/graphviz-12.2.1-r1 X pdf -python_single_target_python3_13 qt6 svg
1115*abd61d1fSMauro Carvalho Chehab        #    =media-gfx/graphviz-12.2.1-r1 X pdf python_single_target_python3_12 -python_single_target_python3_13 qt6 svg
1116*abd61d1fSMauro Carvalho Chehab        #    =media-gfx/graphviz-12.2.1-r1 X pdf qt6 svg
1117*abd61d1fSMauro Carvalho Chehab        #    =media-gfx/graphviz-12.2.1-r1 X pdf -python_single_target_python3_10 qt6 svg
1118*abd61d1fSMauro Carvalho Chehab        #    =media-gfx/graphviz-12.2.1-r1 X pdf -python_single_target_python3_10 python_single_target_python3_12 -python_single_target_python3_13 qt6 svg
1119*abd61d1fSMauro Carvalho Chehab        #    =media-fonts/noto-cjk-20190416 X
1120*abd61d1fSMauro Carvalho Chehab        #    =app-text/texlive-core-2024-r1 X cjk -xetex
1121*abd61d1fSMauro Carvalho Chehab        #    =app-text/texlive-core-2024-r1 X -xetex
1122*abd61d1fSMauro Carvalho Chehab        #    =app-text/texlive-core-2024-r1 -xetex
1123*abd61d1fSMauro Carvalho Chehab        #    =dev-libs/zziplib-0.13.79-r1 sdl
1124*abd61d1fSMauro Carvalho Chehab        #
1125*abd61d1fSMauro Carvalho Chehab        # And will ignore such packages, installing the remaining ones. That
1126*abd61d1fSMauro Carvalho Chehab        # affects mostly the image extension and PDF generation.
1127*abd61d1fSMauro Carvalho Chehab
1128*abd61d1fSMauro Carvalho Chehab        # Package dependencies and the minimal needed args:
1129*abd61d1fSMauro Carvalho Chehab        portages = {
1130*abd61d1fSMauro Carvalho Chehab            "graphviz": "media-gfx/graphviz",
1131*abd61d1fSMauro Carvalho Chehab            "imagemagick": "media-gfx/imagemagick",
1132*abd61d1fSMauro Carvalho Chehab            "media-libs": "media-libs/harfbuzz icu",
1133*abd61d1fSMauro Carvalho Chehab            "media-fonts": "media-fonts/noto-cjk",
1134*abd61d1fSMauro Carvalho Chehab            "texlive": "app-text/texlive-core xetex",
1135*abd61d1fSMauro Carvalho Chehab            "zziblib": "dev-libs/zziplib sdl",
1136*abd61d1fSMauro Carvalho Chehab        }
1137*abd61d1fSMauro Carvalho Chehab
1138*abd61d1fSMauro Carvalho Chehab        extra_cmds = ""
1139*abd61d1fSMauro Carvalho Chehab        if not self.distro_msg:
1140*abd61d1fSMauro Carvalho Chehab            self.distro_msg = "Note: Gentoo requires package.use to be adjusted before emerging packages"
1141*abd61d1fSMauro Carvalho Chehab
1142*abd61d1fSMauro Carvalho Chehab            use_base = "/etc/portage/package.use"
1143*abd61d1fSMauro Carvalho Chehab            files = glob(f"{use_base}/*")
1144*abd61d1fSMauro Carvalho Chehab
1145*abd61d1fSMauro Carvalho Chehab            for fname, portage in portages.items():
1146*abd61d1fSMauro Carvalho Chehab                install = False
1147*abd61d1fSMauro Carvalho Chehab
1148*abd61d1fSMauro Carvalho Chehab                while install is False:
1149*abd61d1fSMauro Carvalho Chehab                    if not files:
1150*abd61d1fSMauro Carvalho Chehab                        # No files under package.usage. Install all
1151*abd61d1fSMauro Carvalho Chehab                        install = True
1152*abd61d1fSMauro Carvalho Chehab                        break
1153*abd61d1fSMauro Carvalho Chehab
1154*abd61d1fSMauro Carvalho Chehab                    args = portage.split(" ")
1155*abd61d1fSMauro Carvalho Chehab
1156*abd61d1fSMauro Carvalho Chehab                    name = args.pop(0)
1157*abd61d1fSMauro Carvalho Chehab
1158*abd61d1fSMauro Carvalho Chehab                    cmd = ["grep", "-l", "-E", rf"^{name}\b" ] + files
1159*abd61d1fSMauro Carvalho Chehab                    result = self.run(cmd, stdout=subprocess.PIPE, text=True)
1160*abd61d1fSMauro Carvalho Chehab                    if result.returncode or not result.stdout.strip():
1161*abd61d1fSMauro Carvalho Chehab                        # File containing portage name not found
1162*abd61d1fSMauro Carvalho Chehab                        install = True
1163*abd61d1fSMauro Carvalho Chehab                        break
1164*abd61d1fSMauro Carvalho Chehab
1165*abd61d1fSMauro Carvalho Chehab                    # Ensure that needed USE flags are present
1166*abd61d1fSMauro Carvalho Chehab                    if args:
1167*abd61d1fSMauro Carvalho Chehab                        match_fname = result.stdout.strip()
1168*abd61d1fSMauro Carvalho Chehab                        with open(match_fname, 'r', encoding='utf8',
1169*abd61d1fSMauro Carvalho Chehab                                errors='backslashreplace') as fp:
1170*abd61d1fSMauro Carvalho Chehab                            for line in fp:
1171*abd61d1fSMauro Carvalho Chehab                                for arg in args:
1172*abd61d1fSMauro Carvalho Chehab                                    if arg.startswith("-"):
1173*abd61d1fSMauro Carvalho Chehab                                        continue
1174*abd61d1fSMauro Carvalho Chehab
1175*abd61d1fSMauro Carvalho Chehab                                if not re.search(rf"\s*{arg}\b", line):
1176*abd61d1fSMauro Carvalho Chehab                                    # Needed file argument not found
1177*abd61d1fSMauro Carvalho Chehab                                    install = True
1178*abd61d1fSMauro Carvalho Chehab                                    break
1179*abd61d1fSMauro Carvalho Chehab
1180*abd61d1fSMauro Carvalho Chehab                    # Everything looks ok, don't install
1181*abd61d1fSMauro Carvalho Chehab                    break
1182*abd61d1fSMauro Carvalho Chehab
1183*abd61d1fSMauro Carvalho Chehab                # emit a code to setup missing USE
1184*abd61d1fSMauro Carvalho Chehab                if install:
1185*abd61d1fSMauro Carvalho Chehab                    extra_cmds += (f"sudo su -c 'echo \"{portage}\" > {use_base}/{fname}'\n")
1186*abd61d1fSMauro Carvalho Chehab
1187*abd61d1fSMauro Carvalho Chehab        # Now, we can use emerge and let it respect USE
1188*abd61d1fSMauro Carvalho Chehab        return self.get_install_progs(progs,
1189*abd61d1fSMauro Carvalho Chehab                                      "emerge --ask --changed-use --binpkg-respect-use=y",
1190*abd61d1fSMauro Carvalho Chehab                                      extra_cmds)
1191*abd61d1fSMauro Carvalho Chehab
1192*abd61d1fSMauro Carvalho Chehab    def get_install(self):
1193*abd61d1fSMauro Carvalho Chehab        """
1194*abd61d1fSMauro Carvalho Chehab        OS-specific hints logic. Seeks for a hinter. If found, use it to
1195*abd61d1fSMauro Carvalho Chehab        provide package-manager specific install commands.
1196*abd61d1fSMauro Carvalho Chehab
1197*abd61d1fSMauro Carvalho Chehab        Otherwise, outputs install instructions for the meta-packages.
1198*abd61d1fSMauro Carvalho Chehab
1199*abd61d1fSMauro Carvalho Chehab        Returns a string with the command to be executed to install the
1200*abd61d1fSMauro Carvalho Chehab        the needed packages, if distro found. Otherwise, return just a
1201*abd61d1fSMauro Carvalho Chehab        list of packages that require installation.
1202*abd61d1fSMauro Carvalho Chehab        """
1203*abd61d1fSMauro Carvalho Chehab        os_hints = {
1204*abd61d1fSMauro Carvalho Chehab            re.compile("Red Hat Enterprise Linux"):   self.give_redhat_hints,
1205*abd61d1fSMauro Carvalho Chehab            re.compile("Fedora"):                     self.give_redhat_hints,
1206*abd61d1fSMauro Carvalho Chehab            re.compile("AlmaLinux"):                  self.give_redhat_hints,
1207*abd61d1fSMauro Carvalho Chehab            re.compile("Amazon Linux"):               self.give_redhat_hints,
1208*abd61d1fSMauro Carvalho Chehab            re.compile("CentOS"):                     self.give_redhat_hints,
1209*abd61d1fSMauro Carvalho Chehab            re.compile("openEuler"):                  self.give_redhat_hints,
1210*abd61d1fSMauro Carvalho Chehab            re.compile("Oracle Linux Server"):        self.give_redhat_hints,
1211*abd61d1fSMauro Carvalho Chehab            re.compile("Rocky Linux"):                self.give_redhat_hints,
1212*abd61d1fSMauro Carvalho Chehab            re.compile("Springdale Open Enterprise"): self.give_redhat_hints,
1213*abd61d1fSMauro Carvalho Chehab
1214*abd61d1fSMauro Carvalho Chehab            re.compile("Ubuntu"):                     self.give_debian_hints,
1215*abd61d1fSMauro Carvalho Chehab            re.compile("Debian"):                     self.give_debian_hints,
1216*abd61d1fSMauro Carvalho Chehab            re.compile("Devuan"):                     self.give_debian_hints,
1217*abd61d1fSMauro Carvalho Chehab            re.compile("Kali"):                       self.give_debian_hints,
1218*abd61d1fSMauro Carvalho Chehab            re.compile("Mint"):                       self.give_debian_hints,
1219*abd61d1fSMauro Carvalho Chehab
1220*abd61d1fSMauro Carvalho Chehab            re.compile("openSUSE"):                   self.give_opensuse_hints,
1221*abd61d1fSMauro Carvalho Chehab
1222*abd61d1fSMauro Carvalho Chehab            re.compile("Mageia"):                     self.give_mageia_hints,
1223*abd61d1fSMauro Carvalho Chehab            re.compile("OpenMandriva"):               self.give_mageia_hints,
1224*abd61d1fSMauro Carvalho Chehab
1225*abd61d1fSMauro Carvalho Chehab            re.compile("Arch Linux"):                 self.give_arch_linux_hints,
1226*abd61d1fSMauro Carvalho Chehab            re.compile("Gentoo"):                     self.give_gentoo_hints,
1227*abd61d1fSMauro Carvalho Chehab        }
1228*abd61d1fSMauro Carvalho Chehab
1229*abd61d1fSMauro Carvalho Chehab        # If the OS is detected, use per-OS hint logic
1230*abd61d1fSMauro Carvalho Chehab        for regex, os_hint in os_hints.items():
1231*abd61d1fSMauro Carvalho Chehab            if regex.search(self.system_release):
1232*abd61d1fSMauro Carvalho Chehab                return os_hint()
1233*abd61d1fSMauro Carvalho Chehab
1234*abd61d1fSMauro Carvalho Chehab        #
1235*abd61d1fSMauro Carvalho Chehab        # Fall-back to generic hint code for other distros
1236*abd61d1fSMauro Carvalho Chehab        # That's far from ideal, specially for LaTeX dependencies.
1237*abd61d1fSMauro Carvalho Chehab        #
1238*abd61d1fSMauro Carvalho Chehab        progs = {"sphinx-build": "sphinx"}
1239*abd61d1fSMauro Carvalho Chehab        if self.pdf:
1240*abd61d1fSMauro Carvalho Chehab            self.check_missing_tex()
1241*abd61d1fSMauro Carvalho Chehab
1242*abd61d1fSMauro Carvalho Chehab        self.distro_msg = \
1243*abd61d1fSMauro Carvalho Chehab            f"I don't know distro {self.system_release}.\n" \
1244*abd61d1fSMauro Carvalho Chehab            "So, I can't provide you a hint with the install procedure.\n" \
1245*abd61d1fSMauro Carvalho Chehab            "There are likely missing dependencies."
1246*abd61d1fSMauro Carvalho Chehab
1247*abd61d1fSMauro Carvalho Chehab        return self.get_install_progs(progs, None)
1248*abd61d1fSMauro Carvalho Chehab
1249*abd61d1fSMauro Carvalho Chehab    #
1250*abd61d1fSMauro Carvalho Chehab    # Common dependencies
1251*abd61d1fSMauro Carvalho Chehab    #
1252*abd61d1fSMauro Carvalho Chehab    def deactivate_help(self):
1253*abd61d1fSMauro Carvalho Chehab        """
1254*abd61d1fSMauro Carvalho Chehab        Print a helper message to disable a virtual environment.
1255*abd61d1fSMauro Carvalho Chehab        """
1256*abd61d1fSMauro Carvalho Chehab
1257*abd61d1fSMauro Carvalho Chehab        print("\n    If you want to exit the virtualenv, you can use:")
1258*abd61d1fSMauro Carvalho Chehab        print("\tdeactivate")
1259*abd61d1fSMauro Carvalho Chehab
1260*abd61d1fSMauro Carvalho Chehab    def get_virtenv(self):
1261*abd61d1fSMauro Carvalho Chehab        """
1262*abd61d1fSMauro Carvalho Chehab        Give a hint about how to activate an already-existing virtual
1263*abd61d1fSMauro Carvalho Chehab        environment containing sphinx-build.
1264*abd61d1fSMauro Carvalho Chehab
1265*abd61d1fSMauro Carvalho Chehab        Returns a tuble with (activate_cmd_path, sphinx_version) with
1266*abd61d1fSMauro Carvalho Chehab        the newest available virtual env.
1267*abd61d1fSMauro Carvalho Chehab        """
1268*abd61d1fSMauro Carvalho Chehab
1269*abd61d1fSMauro Carvalho Chehab        cwd = os.getcwd()
1270*abd61d1fSMauro Carvalho Chehab
1271*abd61d1fSMauro Carvalho Chehab        activates = []
1272*abd61d1fSMauro Carvalho Chehab
1273*abd61d1fSMauro Carvalho Chehab        # Add all sphinx prefixes with possible version numbers
1274*abd61d1fSMauro Carvalho Chehab        for p in self.virtenv_prefix:
1275*abd61d1fSMauro Carvalho Chehab            activates += glob(f"{cwd}/{p}[0-9]*/bin/activate")
1276*abd61d1fSMauro Carvalho Chehab
1277*abd61d1fSMauro Carvalho Chehab        activates.sort(reverse=True, key=str.lower)
1278*abd61d1fSMauro Carvalho Chehab
1279*abd61d1fSMauro Carvalho Chehab        # Place sphinx_latest first, if it exists
1280*abd61d1fSMauro Carvalho Chehab        for p in self.virtenv_prefix:
1281*abd61d1fSMauro Carvalho Chehab            activates = glob(f"{cwd}/{p}*latest/bin/activate") + activates
1282*abd61d1fSMauro Carvalho Chehab
1283*abd61d1fSMauro Carvalho Chehab        ver = (0, 0, 0)
1284*abd61d1fSMauro Carvalho Chehab        for f in activates:
1285*abd61d1fSMauro Carvalho Chehab            # Discard too old Sphinx virtual environments
1286*abd61d1fSMauro Carvalho Chehab            match = re.search(r"(\d+)\.(\d+)\.(\d+)", f)
1287*abd61d1fSMauro Carvalho Chehab            if match:
1288*abd61d1fSMauro Carvalho Chehab                ver = (int(match.group(1)), int(match.group(2)), int(match.group(3)))
1289*abd61d1fSMauro Carvalho Chehab
1290*abd61d1fSMauro Carvalho Chehab                if ver < self.min_version:
1291*abd61d1fSMauro Carvalho Chehab                    continue
1292*abd61d1fSMauro Carvalho Chehab
1293*abd61d1fSMauro Carvalho Chehab            sphinx_cmd = f.replace("activate", "sphinx-build")
1294*abd61d1fSMauro Carvalho Chehab            if not os.path.isfile(sphinx_cmd):
1295*abd61d1fSMauro Carvalho Chehab                continue
1296*abd61d1fSMauro Carvalho Chehab
1297*abd61d1fSMauro Carvalho Chehab            ver = self.get_sphinx_version(sphinx_cmd)
1298*abd61d1fSMauro Carvalho Chehab
1299*abd61d1fSMauro Carvalho Chehab            if not ver:
1300*abd61d1fSMauro Carvalho Chehab                venv_dir = f.replace("/bin/activate", "")
1301*abd61d1fSMauro Carvalho Chehab                print(f"Warning: virtual environment {venv_dir} is not working.\n" \
1302*abd61d1fSMauro Carvalho Chehab                      "Python version upgrade? Remove it with:\n\n" \
1303*abd61d1fSMauro Carvalho Chehab                      "\trm -rf {venv_dir}\n\n")
1304*abd61d1fSMauro Carvalho Chehab            else:
1305*abd61d1fSMauro Carvalho Chehab                if self.need_sphinx and ver >= self.min_version:
1306*abd61d1fSMauro Carvalho Chehab                    return (f, ver)
1307*abd61d1fSMauro Carvalho Chehab                elif parse_version(ver) > self.cur_version:
1308*abd61d1fSMauro Carvalho Chehab                    return (f, ver)
1309*abd61d1fSMauro Carvalho Chehab
1310*abd61d1fSMauro Carvalho Chehab        return ("", ver)
1311*abd61d1fSMauro Carvalho Chehab
1312*abd61d1fSMauro Carvalho Chehab    def recommend_sphinx_upgrade(self):
1313*abd61d1fSMauro Carvalho Chehab        """
1314*abd61d1fSMauro Carvalho Chehab        Check if Sphinx needs to be upgraded.
1315*abd61d1fSMauro Carvalho Chehab
1316*abd61d1fSMauro Carvalho Chehab        Returns a tuple with the higest available Sphinx version if found.
1317*abd61d1fSMauro Carvalho Chehab        Otherwise, returns None to indicate either that no upgrade is needed
1318*abd61d1fSMauro Carvalho Chehab        or no venv was found.
1319*abd61d1fSMauro Carvalho Chehab        """
1320*abd61d1fSMauro Carvalho Chehab
1321*abd61d1fSMauro Carvalho Chehab        # Avoid running sphinx-builds from venv if cur_version is good
1322*abd61d1fSMauro Carvalho Chehab        if self.cur_version and self.cur_version >= RECOMMENDED_VERSION:
1323*abd61d1fSMauro Carvalho Chehab            self.latest_avail_ver = self.cur_version
1324*abd61d1fSMauro Carvalho Chehab            return None
1325*abd61d1fSMauro Carvalho Chehab
1326*abd61d1fSMauro Carvalho Chehab        # Get the highest version from sphinx_*/bin/sphinx-build and the
1327*abd61d1fSMauro Carvalho Chehab        # corresponding command to activate the venv/virtenv
1328*abd61d1fSMauro Carvalho Chehab        self.activate_cmd, self.venv_ver = self.get_virtenv()
1329*abd61d1fSMauro Carvalho Chehab
1330*abd61d1fSMauro Carvalho Chehab        # Store the highest version from Sphinx existing virtualenvs
1331*abd61d1fSMauro Carvalho Chehab        if self.activate_cmd and self.venv_ver > self.cur_version:
1332*abd61d1fSMauro Carvalho Chehab            self.latest_avail_ver = self.venv_ver
1333*abd61d1fSMauro Carvalho Chehab        else:
1334*abd61d1fSMauro Carvalho Chehab            if self.cur_version:
1335*abd61d1fSMauro Carvalho Chehab                self.latest_avail_ver = self.cur_version
1336*abd61d1fSMauro Carvalho Chehab            else:
1337*abd61d1fSMauro Carvalho Chehab                self.latest_avail_ver = (0, 0, 0)
1338*abd61d1fSMauro Carvalho Chehab
1339*abd61d1fSMauro Carvalho Chehab        # As we don't know package version of Sphinx, and there's no
1340*abd61d1fSMauro Carvalho Chehab        # virtual environments, don't check if upgrades are needed
1341*abd61d1fSMauro Carvalho Chehab        if not self.virtualenv:
1342*abd61d1fSMauro Carvalho Chehab            if not self.latest_avail_ver:
1343*abd61d1fSMauro Carvalho Chehab                return None
1344*abd61d1fSMauro Carvalho Chehab
1345*abd61d1fSMauro Carvalho Chehab            return self.latest_avail_ver
1346*abd61d1fSMauro Carvalho Chehab
1347*abd61d1fSMauro Carvalho Chehab        # Either there are already a virtual env or a new one should be created
1348*abd61d1fSMauro Carvalho Chehab        self.need_pip = True
1349*abd61d1fSMauro Carvalho Chehab
1350*abd61d1fSMauro Carvalho Chehab        if not self.latest_avail_ver:
1351*abd61d1fSMauro Carvalho Chehab            return None
1352*abd61d1fSMauro Carvalho Chehab
1353*abd61d1fSMauro Carvalho Chehab        # Return if the reason is due to an upgrade or not
1354*abd61d1fSMauro Carvalho Chehab        if self.latest_avail_ver != (0, 0, 0):
1355*abd61d1fSMauro Carvalho Chehab            if self.latest_avail_ver < RECOMMENDED_VERSION:
1356*abd61d1fSMauro Carvalho Chehab                self.rec_sphinx_upgrade = 1
1357*abd61d1fSMauro Carvalho Chehab
1358*abd61d1fSMauro Carvalho Chehab        return self.latest_avail_ver
1359*abd61d1fSMauro Carvalho Chehab
1360*abd61d1fSMauro Carvalho Chehab    def recommend_package(self):
1361*abd61d1fSMauro Carvalho Chehab        """
1362*abd61d1fSMauro Carvalho Chehab        Recommend installing Sphinx as a distro-specific package.
1363*abd61d1fSMauro Carvalho Chehab        """
1364*abd61d1fSMauro Carvalho Chehab
1365*abd61d1fSMauro Carvalho Chehab        print("\n2) As a package with:")
1366*abd61d1fSMauro Carvalho Chehab
1367*abd61d1fSMauro Carvalho Chehab        old_need = self.deps.need
1368*abd61d1fSMauro Carvalho Chehab        old_optional = self.deps.optional
1369*abd61d1fSMauro Carvalho Chehab
1370*abd61d1fSMauro Carvalho Chehab        self.pdf = False
1371*abd61d1fSMauro Carvalho Chehab        self.deps.optional = 0
1372*abd61d1fSMauro Carvalho Chehab        old_verbose = self.verbose_warn_install
1373*abd61d1fSMauro Carvalho Chehab        self.verbose_warn_install = 0
1374*abd61d1fSMauro Carvalho Chehab
1375*abd61d1fSMauro Carvalho Chehab        self.deps.clear_deps()
1376*abd61d1fSMauro Carvalho Chehab
1377*abd61d1fSMauro Carvalho Chehab        self.deps.add_package("python-sphinx", DepManager.PYTHON_MANDATORY)
1378*abd61d1fSMauro Carvalho Chehab
1379*abd61d1fSMauro Carvalho Chehab        cmd = self.get_install()
1380*abd61d1fSMauro Carvalho Chehab        if cmd:
1381*abd61d1fSMauro Carvalho Chehab            print(cmd)
1382*abd61d1fSMauro Carvalho Chehab
1383*abd61d1fSMauro Carvalho Chehab        self.deps.need = old_need
1384*abd61d1fSMauro Carvalho Chehab        self.deps.optional = old_optional
1385*abd61d1fSMauro Carvalho Chehab        self.verbose_warn_install = old_verbose
1386*abd61d1fSMauro Carvalho Chehab
1387*abd61d1fSMauro Carvalho Chehab    def recommend_sphinx_version(self, virtualenv_cmd):
1388*abd61d1fSMauro Carvalho Chehab        """
1389*abd61d1fSMauro Carvalho Chehab        Provide recommendations for installing or upgrading Sphinx based
1390*abd61d1fSMauro Carvalho Chehab        on current version.
1391*abd61d1fSMauro Carvalho Chehab
1392*abd61d1fSMauro Carvalho Chehab        The logic here is complex, as it have to deal with different versions:
1393*abd61d1fSMauro Carvalho Chehab
1394*abd61d1fSMauro Carvalho Chehab        - minimal supported version;
1395*abd61d1fSMauro Carvalho Chehab        - minimal PDF version;
1396*abd61d1fSMauro Carvalho Chehab        - recommended version.
1397*abd61d1fSMauro Carvalho Chehab
1398*abd61d1fSMauro Carvalho Chehab        It also needs to work fine with both distro's package and
1399*abd61d1fSMauro Carvalho Chehab        venv/virtualenv
1400*abd61d1fSMauro Carvalho Chehab        """
1401*abd61d1fSMauro Carvalho Chehab
1402*abd61d1fSMauro Carvalho Chehab        if self.recommend_python:
1403*abd61d1fSMauro Carvalho Chehab            cur_ver = sys.version_info[:3]
1404*abd61d1fSMauro Carvalho Chehab            if cur_ver < MIN_PYTHON_VERSION:
1405*abd61d1fSMauro Carvalho Chehab                print(f"\nPython version {cur_ver} is incompatible with doc build.\n" \
1406*abd61d1fSMauro Carvalho Chehab                    "Please upgrade it and re-run.\n")
1407*abd61d1fSMauro Carvalho Chehab                return
1408*abd61d1fSMauro Carvalho Chehab
1409*abd61d1fSMauro Carvalho Chehab        # Version is OK. Nothing to do.
1410*abd61d1fSMauro Carvalho Chehab        if self.cur_version != (0, 0, 0) and self.cur_version >= RECOMMENDED_VERSION:
1411*abd61d1fSMauro Carvalho Chehab            return
1412*abd61d1fSMauro Carvalho Chehab
1413*abd61d1fSMauro Carvalho Chehab        if self.latest_avail_ver:
1414*abd61d1fSMauro Carvalho Chehab            latest_avail_ver = ver_str(self.latest_avail_ver)
1415*abd61d1fSMauro Carvalho Chehab
1416*abd61d1fSMauro Carvalho Chehab        if not self.need_sphinx:
1417*abd61d1fSMauro Carvalho Chehab            # sphinx-build is present and its version is >= $min_version
1418*abd61d1fSMauro Carvalho Chehab
1419*abd61d1fSMauro Carvalho Chehab            # only recommend enabling a newer virtenv version if makes sense.
1420*abd61d1fSMauro Carvalho Chehab            if self.latest_avail_ver and self.latest_avail_ver > self.cur_version:
1421*abd61d1fSMauro Carvalho Chehab                print(f"\nYou may also use the newer Sphinx version {latest_avail_ver} with:")
1422*abd61d1fSMauro Carvalho Chehab                if f"{self.virtenv_prefix}" in os.getcwd():
1423*abd61d1fSMauro Carvalho Chehab                    print("\tdeactivate")
1424*abd61d1fSMauro Carvalho Chehab                print(f"\t. {self.activate_cmd}")
1425*abd61d1fSMauro Carvalho Chehab                self.deactivate_help()
1426*abd61d1fSMauro Carvalho Chehab                return
1427*abd61d1fSMauro Carvalho Chehab
1428*abd61d1fSMauro Carvalho Chehab            if self.latest_avail_ver and self.latest_avail_ver >= RECOMMENDED_VERSION:
1429*abd61d1fSMauro Carvalho Chehab                return
1430*abd61d1fSMauro Carvalho Chehab
1431*abd61d1fSMauro Carvalho Chehab        if not self.virtualenv:
1432*abd61d1fSMauro Carvalho Chehab            # No sphinx either via package or via virtenv. As we can't
1433*abd61d1fSMauro Carvalho Chehab            # Compare the versions here, just return, recommending the
1434*abd61d1fSMauro Carvalho Chehab            # user to install it from the package distro.
1435*abd61d1fSMauro Carvalho Chehab            if not self.latest_avail_ver or self.latest_avail_ver == (0, 0, 0):
1436*abd61d1fSMauro Carvalho Chehab                return
1437*abd61d1fSMauro Carvalho Chehab
1438*abd61d1fSMauro Carvalho Chehab            # User doesn't want a virtenv recommendation, but he already
1439*abd61d1fSMauro Carvalho Chehab            # installed one via virtenv with a newer version.
1440*abd61d1fSMauro Carvalho Chehab            # So, print commands to enable it
1441*abd61d1fSMauro Carvalho Chehab            if self.latest_avail_ver > self.cur_version:
1442*abd61d1fSMauro Carvalho Chehab                print(f"\nYou may also use the Sphinx virtualenv version {latest_avail_ver} with:")
1443*abd61d1fSMauro Carvalho Chehab                if f"{self.virtenv_prefix}" in os.getcwd():
1444*abd61d1fSMauro Carvalho Chehab                    print("\tdeactivate")
1445*abd61d1fSMauro Carvalho Chehab                print(f"\t. {self.activate_cmd}")
1446*abd61d1fSMauro Carvalho Chehab                self.deactivate_help()
1447*abd61d1fSMauro Carvalho Chehab                return
1448*abd61d1fSMauro Carvalho Chehab            print("\n")
1449*abd61d1fSMauro Carvalho Chehab        else:
1450*abd61d1fSMauro Carvalho Chehab            if self.need_sphinx:
1451*abd61d1fSMauro Carvalho Chehab                self.deps.need += 1
1452*abd61d1fSMauro Carvalho Chehab
1453*abd61d1fSMauro Carvalho Chehab        # Suggest newer versions if current ones are too old
1454*abd61d1fSMauro Carvalho Chehab        if self.latest_avail_ver and self.latest_avail_ver >= self.min_version:
1455*abd61d1fSMauro Carvalho Chehab            if self.latest_avail_ver >= RECOMMENDED_VERSION:
1456*abd61d1fSMauro Carvalho Chehab                print(f"\nNeed to activate Sphinx (version {latest_avail_ver}) on virtualenv with:")
1457*abd61d1fSMauro Carvalho Chehab                print(f"\t. {self.activate_cmd}")
1458*abd61d1fSMauro Carvalho Chehab                self.deactivate_help()
1459*abd61d1fSMauro Carvalho Chehab                return
1460*abd61d1fSMauro Carvalho Chehab
1461*abd61d1fSMauro Carvalho Chehab            # Version is above the minimal required one, but may be
1462*abd61d1fSMauro Carvalho Chehab            # below the recommended one. So, print warnings/notes
1463*abd61d1fSMauro Carvalho Chehab            if self.latest_avail_ver < RECOMMENDED_VERSION:
1464*abd61d1fSMauro Carvalho Chehab                print(f"Warning: It is recommended at least Sphinx version {RECOMMENDED_VERSION}.")
1465*abd61d1fSMauro Carvalho Chehab
1466*abd61d1fSMauro Carvalho Chehab        # At this point, either it needs Sphinx or upgrade is recommended,
1467*abd61d1fSMauro Carvalho Chehab        # both via pip
1468*abd61d1fSMauro Carvalho Chehab
1469*abd61d1fSMauro Carvalho Chehab        if self.rec_sphinx_upgrade:
1470*abd61d1fSMauro Carvalho Chehab            if not self.virtualenv:
1471*abd61d1fSMauro Carvalho Chehab                print("Instead of install/upgrade Python Sphinx pkg, you could use pip/pypi with:\n\n")
1472*abd61d1fSMauro Carvalho Chehab            else:
1473*abd61d1fSMauro Carvalho Chehab                print("To upgrade Sphinx, use:\n\n")
1474*abd61d1fSMauro Carvalho Chehab        else:
1475*abd61d1fSMauro Carvalho Chehab            print("\nSphinx needs to be installed either:\n1) via pip/pypi with:\n")
1476*abd61d1fSMauro Carvalho Chehab
1477*abd61d1fSMauro Carvalho Chehab        if not virtualenv_cmd:
1478*abd61d1fSMauro Carvalho Chehab            print("   Currently not possible.\n")
1479*abd61d1fSMauro Carvalho Chehab            print("   Please upgrade Python to a newer version and run this script again")
1480*abd61d1fSMauro Carvalho Chehab        else:
1481*abd61d1fSMauro Carvalho Chehab            print(f"\t{virtualenv_cmd} {self.virtenv_dir}")
1482*abd61d1fSMauro Carvalho Chehab            print(f"\t. {self.virtenv_dir}/bin/activate")
1483*abd61d1fSMauro Carvalho Chehab            print(f"\tpip install -r {self.requirement_file}")
1484*abd61d1fSMauro Carvalho Chehab            self.deactivate_help()
1485*abd61d1fSMauro Carvalho Chehab
1486*abd61d1fSMauro Carvalho Chehab        if self.package_supported:
1487*abd61d1fSMauro Carvalho Chehab            self.recommend_package()
1488*abd61d1fSMauro Carvalho Chehab
1489*abd61d1fSMauro Carvalho Chehab        print("\n" \
1490*abd61d1fSMauro Carvalho Chehab              "   Please note that Sphinx currentlys produce false-positive\n" \
1491*abd61d1fSMauro Carvalho Chehab              "   warnings when the same name is used for more than one type (functions,\n" \
1492*abd61d1fSMauro Carvalho Chehab              "   structs, enums,...). This is known Sphinx bug. For more details, see:\n" \
1493*abd61d1fSMauro Carvalho Chehab              "\thttps://github.com/sphinx-doc/sphinx/pull/8313")
1494*abd61d1fSMauro Carvalho Chehab
1495*abd61d1fSMauro Carvalho Chehab    def check_needs(self):
1496*abd61d1fSMauro Carvalho Chehab        """
1497*abd61d1fSMauro Carvalho Chehab        Main method that checks needed dependencies and provides
1498*abd61d1fSMauro Carvalho Chehab        recommendations.
1499*abd61d1fSMauro Carvalho Chehab        """
1500*abd61d1fSMauro Carvalho Chehab        self.python_cmd = sys.executable
1501*abd61d1fSMauro Carvalho Chehab
1502*abd61d1fSMauro Carvalho Chehab        # Check if Sphinx is already accessible from current environment
1503*abd61d1fSMauro Carvalho Chehab        self.check_sphinx(self.conf)
1504*abd61d1fSMauro Carvalho Chehab
1505*abd61d1fSMauro Carvalho Chehab        if self.system_release:
1506*abd61d1fSMauro Carvalho Chehab            print(f"Detected OS: {self.system_release}.")
1507*abd61d1fSMauro Carvalho Chehab        else:
1508*abd61d1fSMauro Carvalho Chehab            print("Unknown OS")
1509*abd61d1fSMauro Carvalho Chehab        if self.cur_version != (0, 0, 0):
1510*abd61d1fSMauro Carvalho Chehab            ver = ver_str(self.cur_version)
1511*abd61d1fSMauro Carvalho Chehab            print(f"Sphinx version: {ver}\n")
1512*abd61d1fSMauro Carvalho Chehab
1513*abd61d1fSMauro Carvalho Chehab        # Check the type of virtual env, depending on Python version
1514*abd61d1fSMauro Carvalho Chehab        virtualenv_cmd = None
1515*abd61d1fSMauro Carvalho Chehab
1516*abd61d1fSMauro Carvalho Chehab        if sys.version_info < MIN_PYTHON_VERSION:
1517*abd61d1fSMauro Carvalho Chehab            min_ver = ver_str(MIN_PYTHON_VERSION)
1518*abd61d1fSMauro Carvalho Chehab            print(f"ERROR: at least python {min_ver} is required to build the kernel docs")
1519*abd61d1fSMauro Carvalho Chehab            self.need_sphinx = 1
1520*abd61d1fSMauro Carvalho Chehab
1521*abd61d1fSMauro Carvalho Chehab        self.venv_ver = self.recommend_sphinx_upgrade()
1522*abd61d1fSMauro Carvalho Chehab
1523*abd61d1fSMauro Carvalho Chehab        if self.need_pip:
1524*abd61d1fSMauro Carvalho Chehab            if sys.version_info < MIN_PYTHON_VERSION:
1525*abd61d1fSMauro Carvalho Chehab                self.need_pip = False
1526*abd61d1fSMauro Carvalho Chehab                print("Warning: python version is not supported.")
1527*abd61d1fSMauro Carvalho Chehab            else:
1528*abd61d1fSMauro Carvalho Chehab                virtualenv_cmd = f"{self.python_cmd} -m venv"
1529*abd61d1fSMauro Carvalho Chehab                self.check_python_module("ensurepip")
1530*abd61d1fSMauro Carvalho Chehab
1531*abd61d1fSMauro Carvalho Chehab        # Check for needed programs/tools
1532*abd61d1fSMauro Carvalho Chehab        self.check_perl_module("Pod::Usage", DepManager.SYSTEM_MANDATORY)
1533*abd61d1fSMauro Carvalho Chehab
1534*abd61d1fSMauro Carvalho Chehab        self.check_program("make", DepManager.SYSTEM_MANDATORY)
1535*abd61d1fSMauro Carvalho Chehab        self.check_program("which", DepManager.SYSTEM_MANDATORY)
1536*abd61d1fSMauro Carvalho Chehab
1537*abd61d1fSMauro Carvalho Chehab        self.check_program("dot", DepManager.SYSTEM_OPTIONAL)
1538*abd61d1fSMauro Carvalho Chehab        self.check_program("convert", DepManager.SYSTEM_OPTIONAL)
1539*abd61d1fSMauro Carvalho Chehab
1540*abd61d1fSMauro Carvalho Chehab        self.check_python_module("yaml")
1541*abd61d1fSMauro Carvalho Chehab
1542*abd61d1fSMauro Carvalho Chehab        if self.pdf:
1543*abd61d1fSMauro Carvalho Chehab            self.check_program("xelatex", DepManager.PDF_MANDATORY)
1544*abd61d1fSMauro Carvalho Chehab            self.check_program("rsvg-convert", DepManager.PDF_MANDATORY)
1545*abd61d1fSMauro Carvalho Chehab            self.check_program("latexmk", DepManager.PDF_MANDATORY)
1546*abd61d1fSMauro Carvalho Chehab
1547*abd61d1fSMauro Carvalho Chehab        # Do distro-specific checks and output distro-install commands
1548*abd61d1fSMauro Carvalho Chehab        cmd = self.get_install()
1549*abd61d1fSMauro Carvalho Chehab        if cmd:
1550*abd61d1fSMauro Carvalho Chehab            print(cmd)
1551*abd61d1fSMauro Carvalho Chehab
1552*abd61d1fSMauro Carvalho Chehab        # If distro requires some special instructions, print here.
1553*abd61d1fSMauro Carvalho Chehab        # Please notice that get_install() needs to be called first.
1554*abd61d1fSMauro Carvalho Chehab        if self.distro_msg:
1555*abd61d1fSMauro Carvalho Chehab            print("\n" + self.distro_msg)
1556*abd61d1fSMauro Carvalho Chehab
1557*abd61d1fSMauro Carvalho Chehab        if not self.python_cmd:
1558*abd61d1fSMauro Carvalho Chehab            if self.need == 1:
1559*abd61d1fSMauro Carvalho Chehab                sys.exit("Can't build as 1 mandatory dependency is missing")
1560*abd61d1fSMauro Carvalho Chehab            elif self.need:
1561*abd61d1fSMauro Carvalho Chehab                sys.exit(f"Can't build as {self.need} mandatory dependencies are missing")
1562*abd61d1fSMauro Carvalho Chehab
1563*abd61d1fSMauro Carvalho Chehab        # Check if sphinx-build is called sphinx-build-3
1564*abd61d1fSMauro Carvalho Chehab        if self.need_symlink:
1565*abd61d1fSMauro Carvalho Chehab            sphinx_path = self.which("sphinx-build-3")
1566*abd61d1fSMauro Carvalho Chehab            if sphinx_path:
1567*abd61d1fSMauro Carvalho Chehab                print(f"\tsudo ln -sf {sphinx_path} /usr/bin/sphinx-build\n")
1568*abd61d1fSMauro Carvalho Chehab
1569*abd61d1fSMauro Carvalho Chehab        self.recommend_sphinx_version(virtualenv_cmd)
1570*abd61d1fSMauro Carvalho Chehab        print("")
1571*abd61d1fSMauro Carvalho Chehab
1572*abd61d1fSMauro Carvalho Chehab        if not self.deps.optional:
1573*abd61d1fSMauro Carvalho Chehab            print("All optional dependencies are met.")
1574*abd61d1fSMauro Carvalho Chehab
1575*abd61d1fSMauro Carvalho Chehab        if self.deps.need == 1:
1576*abd61d1fSMauro Carvalho Chehab            sys.exit("Can't build as 1 mandatory dependency is missing")
1577*abd61d1fSMauro Carvalho Chehab        elif self.deps.need:
1578*abd61d1fSMauro Carvalho Chehab            sys.exit(f"Can't build as {self.deps.need} mandatory dependencies are missing")
1579*abd61d1fSMauro Carvalho Chehab
1580*abd61d1fSMauro Carvalho Chehab        print("Needed package dependencies are met.")
1581*abd61d1fSMauro Carvalho Chehab
1582*abd61d1fSMauro Carvalho ChehabDESCRIPTION = """
1583*abd61d1fSMauro Carvalho ChehabProcess some flags related to Sphinx installation and documentation build.
1584*abd61d1fSMauro Carvalho Chehab"""
1585*abd61d1fSMauro Carvalho Chehab
1586*abd61d1fSMauro Carvalho Chehab
1587*abd61d1fSMauro Carvalho Chehabdef main():
1588*abd61d1fSMauro Carvalho Chehab    """Main function"""
1589*abd61d1fSMauro Carvalho Chehab    parser = argparse.ArgumentParser(description=DESCRIPTION)
1590*abd61d1fSMauro Carvalho Chehab
1591*abd61d1fSMauro Carvalho Chehab    parser.add_argument(
1592*abd61d1fSMauro Carvalho Chehab        "--no-virtualenv",
1593*abd61d1fSMauro Carvalho Chehab        action="store_false",
1594*abd61d1fSMauro Carvalho Chehab        dest="virtualenv",
1595*abd61d1fSMauro Carvalho Chehab        help="Recommend installing Sphinx instead of using a virtualenv",
1596*abd61d1fSMauro Carvalho Chehab    )
1597*abd61d1fSMauro Carvalho Chehab
1598*abd61d1fSMauro Carvalho Chehab    parser.add_argument(
1599*abd61d1fSMauro Carvalho Chehab        "--no-pdf",
1600*abd61d1fSMauro Carvalho Chehab        action="store_false",
1601*abd61d1fSMauro Carvalho Chehab        dest="pdf",
1602*abd61d1fSMauro Carvalho Chehab        help="Don't check for dependencies required to build PDF docs",
1603*abd61d1fSMauro Carvalho Chehab    )
1604*abd61d1fSMauro Carvalho Chehab
1605*abd61d1fSMauro Carvalho Chehab    parser.add_argument(
1606*abd61d1fSMauro Carvalho Chehab        "--version-check",
1607*abd61d1fSMauro Carvalho Chehab        action="store_true",
1608*abd61d1fSMauro Carvalho Chehab        dest="version_check",
1609*abd61d1fSMauro Carvalho Chehab        help="If version is compatible, don't check for missing dependencies",
1610*abd61d1fSMauro Carvalho Chehab    )
1611*abd61d1fSMauro Carvalho Chehab
1612*abd61d1fSMauro Carvalho Chehab    args = parser.parse_args()
1613*abd61d1fSMauro Carvalho Chehab
1614*abd61d1fSMauro Carvalho Chehab    checker = SphinxDependencyChecker(args)
1615*abd61d1fSMauro Carvalho Chehab
1616*abd61d1fSMauro Carvalho Chehab    checker.check_python()
1617*abd61d1fSMauro Carvalho Chehab    checker.check_needs()
1618*abd61d1fSMauro Carvalho Chehab
1619*abd61d1fSMauro Carvalho Chehab# Call main if not used as module
1620*abd61d1fSMauro Carvalho Chehabif __name__ == "__main__":
1621*abd61d1fSMauro Carvalho Chehab    main()
1622