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