Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'pre-install' into docs-mw

Mauro says:

that's the second version of the patch series which converts
sphinx-pre-install to Python.

The core patches are basically the same as on v1, but it has lots of
fixes over the original script after testing sphinx-install on 22
distros.

Please notice that I have a separate patch series addressing issues
that are specific to PDF generation.

Test Results Summary:
====================

PASSED - AlmaLinux release 9.6 (Sage Margay) (4 tests)
PASSED - Amazon Linux release 2023 (Amazon Linux) (4 tests)
PASSED - Arch Linux (4 tests)
PASSED - CentOS Stream release 9 (4 tests)
PASSED - Debian GNU/Linux 12 (4 tests)
PASSED - Devuan GNU/Linux 5 (4 tests)
PASSED - Fedora release 42 (Adams) (4 tests)
PASSED - Gentoo Base System release 2.17 (4 tests)
PASSED - Kali GNU/Linux 2025.2 (4 tests)
PASSED - Mageia 9 (4 tests)
PASSED - Linux Mint 22 (4 tests)
PASSED - openEuler release 25.03 (4 tests)
PARTIAL - OpenMandriva Lx 4.3 (4 tests)
ensurepip package doesn't exist there. So, venv install failed.
Installed via package worked
PASSED - openSUSE Leap 15.6 (4 tests)
PASSED - openSUSE Tumbleweed (4 tests)
PASSED - Oracle Linux Server release 9.6 (4 tests)
FAILED - Red Hat Enterprise Linux release 8.10 (Ootpa) (4 tests)
I couldn't test properly, as it requires a repository under
paywall. I suspect It should work fine
PARTIAL - Rocky Linux release 8.9 (Green Obsidian) (4 tests)
Install via package didn't work. Instaling via venv works.
PASSED - Rocky Linux release 9.6 (Blue Onyx) (4 tests)
PARTIAL - Springdale Open Enterprise Linux release 9.2 (Parma) (4 tests)
Failed to install ImageMagick (affects pdf only)
PASSED - Ubuntu 24.04.2 LTS (4 tests)
PASSED - Ubuntu 25.04 (4 tests)

In short, I expect that, for all the above, the script will properly
recommend the right packages to have sphinx-build working.

A more detailed list of tests that passed/failed and detected Sphinx
versions can be seeing below:

AlmaLinux release 9.6 (Sage Margay):
------------------------------------
PASSED: OS detection: AlmaLinux release 9.6 (Sage Margay)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Amazon Linux release 2023 (Amazon Linux):
-----------------------------------------
PASSED: OS detection: Amazon Linux release 2023 (Amazon Linux)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Arch Linux:
-----------
PASSED: OS detection: Arch Linux
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.2.3

CentOS Stream release 9:
------------------------
PASSED: OS detection: CentOS Stream release 9
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Debian GNU/Linux 12:
--------------------
PASSED: OS detection: Debian GNU/Linux 12
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 5.3.0

Devuan GNU/Linux 5:
-------------------
PASSED: OS detection: Devuan GNU/Linux 5
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 5.3.0

Fedora release 42 (Adams):
--------------------------
PASSED: OS detection: Fedora release 42 (Adams)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.1.3

Gentoo Base System release 2.17:
--------------------------------
PASSED: OS detection: Gentoo Base System release 2.17
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.2.3

Kali GNU/Linux 2025.2:
----------------------
PASSED: OS detection: Kali GNU/Linux 2025.2
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.1.3

Mageia 9:
---------
PASSED: OS detection: Mageia 9
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 6.1.3
PASSED: Sphinx package: Sphinx 6.1.3

Linux Mint 22:
--------------
PASSED: OS detection: Linux Mint 22
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.1.3
PASSED: Sphinx package: Sphinx 4.3.2

openEuler release 25.03:
------------------------
PASSED: OS detection: openEuler release 25.03
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.1.3

OpenMandriva Lx 4.3:
--------------------
PASSED: OS detection: OpenMandriva Lx 4.3
FAILED: System packages: Error: Unable to find a match: ensurepip
FAILED: Sphinx on venv: Installation failed
PASSED: Sphinx package: Sphinx 4.3.2

openSUSE Leap 15.6:
-------------------
PASSED: OS detection: openSUSE Leap 15.6
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 7.2.6

openSUSE Tumbleweed:
--------------------
PASSED: OS detection: openSUSE Tumbleweed
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.2.3

Oracle Linux Server release 9.6:
--------------------------------
PASSED: OS detection: Oracle Linux Server release 9.6
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Red Hat Enterprise Linux release 8.10 (Ootpa):
----------------------------------------------
PASSED: OS detection: Red Hat Enterprise Linux release 8.10 (Ootpa)
FAILED: System packages: Error: Unable to find a match: google-noto-sans-cjk-ttc-fonts librsvg2-tools
texlive-amscls texlive-amsfonts texlive-amsmath texlive-anyfontsize texlive-capt-of texlive-cmap
texlive-collection-fontsrecommended texlive-collection-latex texlive-ec texlive-eqparbox texlive-euenc
texlive-fancybox texlive-fancyvrb texlive-float texlive-fncychap texlive-framed texlive-luatex85
texlive-mdwtools texlive-multirow texlive-needspace texlive-oberdiek texlive-parskip texlive-polyglossia
texlive-psnfss texlive-tabulary texlive-threeparttable texlive-titlesec texlive-tools texlive-ucs
texlive-upquote texlive-wrapfig texlive-xecjk texlive-xetex-bin
FAILED: Sphinx on venv: No Sphinx version detected
FAILED: Sphinx package: No Sphinx version detected

Rocky Linux release 8.9 (Green Obsidian):
-----------------------------------------
PASSED: OS detection: Rocky Linux release 8.9 (Green Obsidian)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
FAILED: Sphinx package: No Sphinx version detected

Rocky Linux release 9.6 (Blue Onyx):
------------------------------------
PASSED: OS detection: Rocky Linux release 9.6 (Blue Onyx)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Springdale Open Enterprise Linux release 9.2 (Parma):
-----------------------------------------------------
PASSED: OS detection: Springdale Open Enterprise Linux release 9.2 (Parma)
FAILED: System packages: Error: Problem: package ImageMagick-6.9.13.25-1.el9.x86_64 requires
libMagickCore-6.Q16.so.7()(64bit), but none of the providers can be installed - package
ImageMagick-6.9.13.25-1.el9.x86_64 requires libMagickWand-6.Q16.so.7()(64bit), but none of the providers can
be installed - package ImageMagick-6.9.13.25-1.el9.x86_64 requires ImageMagick-libs(x86-64) =
6.9.13.25-1.el9, but none of the providers can be installed - conflicting requests - nothing provides
libraw_r.so.23()(64bit) needed by ImageMagick-libs-6.9.13.25-1.el9.x86_64
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Ubuntu 24.04.2 LTS:
-------------------
PASSED: OS detection: Ubuntu 24.04.2 LTS
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 7.2.6

Ubuntu 25.04:
-------------
PASSED: OS detection: Ubuntu 25.04
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.1.3

Summary
=======
PASSED - AlmaLinux release 9.6 (Sage Margay) (4 tests)
PASSED - Amazon Linux release 2023 (Amazon Linux) (4 tests)
PASSED - Arch Linux (4 tests)
PASSED - CentOS Stream release 9 (4 tests)
PASSED - Debian GNU/Linux 12 (4 tests)
PASSED - Devuan GNU/Linux 5 (4 tests)
PASSED - Fedora release 42 (Adams) (4 tests)
PASSED - Gentoo Base System release 2.17 (4 tests)
PASSED - Kali GNU/Linux 2025.2 (4 tests)
PASSED - Mageia 9 (4 tests)
PASSED - Linux Mint 22 (4 tests)
PASSED - openEuler release 25.03 (4 tests)
FAILED - OpenMandriva Lx 4.3 (4 tests)
PASSED - openSUSE Leap 15.6 (4 tests)
PASSED - openSUSE Tumbleweed (4 tests)
PASSED - Oracle Linux Server release 9.6 (4 tests)
FAILED - Red Hat Enterprise Linux release 8.10 (Ootpa) (4 tests)
FAILED - Rocky Linux release 8.9 (Green Obsidian) (4 tests)
PASSED - Rocky Linux release 9.6 (Blue Onyx) (4 tests)
FAILED - Springdale Open Enterprise Linux release 9.2 (Parma) (4 tests)
PASSED - Ubuntu 24.04.2 LTS (4 tests)
PASSED - Ubuntu 25.04 (4 tests)
(base) mchehab@foz /new_devel/mchehab_scripts $ ktap_reader.py /tmp/test_logs/*

AlmaLinux release 9.6 (Sage Margay):
------------------------------------
PASSED: OS detection: AlmaLinux release 9.6 (Sage Margay)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Amazon Linux release 2023 (Amazon Linux):
-----------------------------------------
PASSED: OS detection: Amazon Linux release 2023 (Amazon Linux)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Arch Linux:
-----------
PASSED: OS detection: Arch Linux
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.2.3

CentOS Stream release 9:
------------------------
PASSED: OS detection: CentOS Stream release 9
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Debian GNU/Linux 12:
--------------------
PASSED: OS detection: Debian GNU/Linux 12
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 5.3.0

Devuan GNU/Linux 5:
-------------------
PASSED: OS detection: Devuan GNU/Linux 5
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 5.3.0

Fedora release 42 (Adams):
--------------------------
PASSED: OS detection: Fedora release 42 (Adams)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.1.3

Gentoo Base System release 2.17:
--------------------------------
PASSED: OS detection: Gentoo Base System release 2.17
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.2.3

Kali GNU/Linux 2025.2:
----------------------
PASSED: OS detection: Kali GNU/Linux 2025.2
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.1.3

Mageia 9:
---------
PASSED: OS detection: Mageia 9
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 6.1.3
PASSED: Sphinx package: Sphinx 6.1.3

Linux Mint 22:
--------------
PASSED: OS detection: Linux Mint 22
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.1.3
PASSED: Sphinx package: Sphinx 4.3.2

openEuler release 25.03:
------------------------
PASSED: OS detection: openEuler release 25.03
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.1.3

OpenMandriva Lx 4.3:
--------------------
PASSED: OS detection: OpenMandriva Lx 4.3
FAILED: System packages: Error: Unable to find a match: ensurepip
PARTIAL: Sphinx on venv: Installation failed
PASSED: Sphinx package: Sphinx 4.3.2

openSUSE Leap 15.6:
-------------------
PASSED: OS detection: openSUSE Leap 15.6
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 7.2.6

openSUSE Tumbleweed:
--------------------
PASSED: OS detection: openSUSE Tumbleweed
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.2.3

Oracle Linux Server release 9.6:
--------------------------------
PASSED: OS detection: Oracle Linux Server release 9.6
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Red Hat Enterprise Linux release 8.10 (Ootpa):
----------------------------------------------
PASSED: OS detection: Red Hat Enterprise Linux release 8.10 (Ootpa)
FAILED: System packages: Error: Unable to find a match: google-noto-sans-cjk-ttc-fonts librsvg2-tools
texlive-amscls texlive-amsfonts texlive-amsmath texlive-anyfontsize texlive-capt-of texlive-cmap
texlive-collection-fontsrecommended texlive-collection-latex texlive-ec texlive-eqparbox texlive-euenc
texlive-fancybox texlive-fancyvrb texlive-float texlive-fncychap texlive-framed texlive-luatex85
texlive-mdwtools texlive-multirow texlive-needspace texlive-oberdiek texlive-parskip texlive-polyglossia
texlive-psnfss texlive-tabulary texlive-threeparttable texlive-titlesec texlive-tools texlive-ucs
texlive-upquote texlive-wrapfig texlive-xecjk texlive-xetex-bin
PARTIAL: Sphinx on venv: No Sphinx version detected
PARTIAL: Sphinx package: No Sphinx version detected

Rocky Linux release 8.9 (Green Obsidian):
-----------------------------------------
PASSED: OS detection: Rocky Linux release 8.9 (Green Obsidian)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PARTIAL: Sphinx package: No Sphinx version detected

Rocky Linux release 9.6 (Blue Onyx):
------------------------------------
PASSED: OS detection: Rocky Linux release 9.6 (Blue Onyx)
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Springdale Open Enterprise Linux release 9.2 (Parma):
-----------------------------------------------------
PASSED: OS detection: Springdale Open Enterprise Linux release 9.2 (Parma)
FAILED: System packages: Error: Problem: package ImageMagick-6.9.13.25-1.el9.x86_64 requires
libMagickCore-6.Q16.so.7()(64bit), but none of the providers can be installed - package
ImageMagick-6.9.13.25-1.el9.x86_64 requires libMagickWand-6.Q16.so.7()(64bit), but none of the providers can
be installed - package ImageMagick-6.9.13.25-1.el9.x86_64 requires ImageMagick-libs(x86-64) =
6.9.13.25-1.el9, but none of the providers can be installed - conflicting requests - nothing provides
libraw_r.so.23()(64bit) needed by ImageMagick-libs-6.9.13.25-1.el9.x86_64
PASSED: Sphinx on venv: Sphinx 7.4.7
PASSED: Sphinx package: Sphinx 3.4.3

Ubuntu 24.04.2 LTS:
-------------------
PASSED: OS detection: Ubuntu 24.04.2 LTS
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 7.2.6

Ubuntu 25.04:
-------------
PASSED: OS detection: Ubuntu 25.04
PASSED: System packages: Packages installed
PASSED: Sphinx on venv: Sphinx 8.2.3
PASSED: Sphinx package: Sphinx 8.1.3

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